JAL-3026 srcjar files for VARNA and log4j
authorhansonr <hansonr@stolaf.edu>
Tue, 3 Jul 2018 13:46:54 +0000 (14:46 +0100)
committerhansonr <hansonr@stolaf.edu>
Tue, 3 Jul 2018 13:46:54 +0000 (14:46 +0100)
398 files changed:
srcjar/fr/orsay/lri/varna/README_SWINGJS.txt [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/VARNA.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/VARNAPanel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/AlignmentDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/BasicINI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/FileNameExtensionFilter.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/NussinovDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/NussinovDesignDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/SecStrConsensus.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/SuperpositionDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAConsoleDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAGUI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAOnlineDemo.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAPrinter.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/VARNAcmd.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqAnnotationDataModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellRenderer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqFileModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqGUI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqNode.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqRNASecStrModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTree.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTreeModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/fragseq/Watcher.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUICellEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIRenderer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITree.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITreeModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/newGUI/Watcher.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/Connection.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/Couple.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/GraphicalTemplateElement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/Helix.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/MouseControler.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditorPanelUI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEdits.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplatePanel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/applications/templateEditor/UnpairedRegion.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/ActionEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/ActionRenderer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/AnnotationTableModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/BaseSpecialColorEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/BaseTableModel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/ColorRenderer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/GradientEditorPanel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/ReorderableJList.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/VARNAConsole.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/components/ZoomWindow.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurBPHeightIncrement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurBaseSpecialColorEditor.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurBlinkingThread.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurBorder.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurClicMovement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurDemoTextField.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurDraggedMolette.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRescale.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurInterpolator.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurJCheckBoxMenuItem.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurMenu.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurMolette.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurNumPeriod.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurScriptParser.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurSelectionHighlight.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurSliderLabel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurSpaceBetweenBases.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurTableAnnotations.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurVARNAPanelKeys.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurVueAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/controlers/ControleurZoom.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionDrawingAlgorithm.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionEdgeEndpointAlreadyConnected.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionExportFailed.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionFileFormatOrSyntax.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionInvalidRNATemplate.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionJPEGEncoding.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionLoadingFailed.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionModeleStyleBaseSyntaxError.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionNAViewAlgorithm.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionNonEqualLength.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionParameterError.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionPermissionDenied.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionUnmatchedClosingParentheses.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionWritingForbidden.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionXMLGeneration.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/ExceptionXmlLoading.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/exceptions/MappingException.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/factories/RNAAlignment.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/factories/RNAFactory.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/factories/StockholmIO.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceParameterLoader.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceParseExport.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNABasesListener.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAListener.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAObservable.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNARNAListener.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNASelectionListener.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/BaseList.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/FullBackup.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/VARNAConfig.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/VARNAConfigLoader.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/VARNAEdits.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/annotations/ChemProbAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/annotations/HighlightRegionAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/annotations/TextAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/ArcCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/CircleCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/ColorCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/FillCircleCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/FillPolygonCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/FontCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/GraphicElement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/LineCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/PSExport.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/PolygonCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/RectangleCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/SVGExport.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/SecStrDrawingProducer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/SecStrProducerGraphics.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/SwingGraphics.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/TextCommand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/TikzExport.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/VueVARNAGraphics.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/export/XFIGExport.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/ComputeArcCenter.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/ComputeEllipseAxis.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/CubicBezierCurve.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/HalfEllipse.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/LinesIntersect.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/geom/MiscGeom.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/Base.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/Connection.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/Loop.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/NAView.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/Radloop.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/naView/Region.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/DrawRNATemplate.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/Mapping.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModelBaseStyle.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBP.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBPStyle.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBackbone.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBackboneElement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBase.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBaseNucleotide.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleBasesComparison.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleColorMap.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/ModeleStrand.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/RNA.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/StructureTemp.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/VARNAPoint.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/rna/VARNASecDraw.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmark.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmarkPrepare.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/Benchmark.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateCurveMethod.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateMethod.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNANodeValue2TemplateDistance.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplate.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBasePair.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBrokenBasePair.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateSequence.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNATemplate.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNATemplateAlign.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNATemplateDrawingAlgorithmException.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMapping.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMappingException.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/templates/TODO.txt [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/AlignedNode.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance2.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance3.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/GraphvizDrawableNodeValue.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2WrongTypeException.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNATree.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNATree2.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/RNATree2Exception.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/Tree.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeAlign.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignException.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceAsymmetric.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceSymmetric.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignResult.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/models/treealign/TreeGraphviz.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/utils/RNAMLParser.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/utils/TranslateFormatRNaseP.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/utils/VARNASessionParser.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/utils/XMLUtils.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/Imprimer.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/PrintTest.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueAboutPanel.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBPHeightIncrement.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBPList.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBPThickness.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBPType.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBaseValues.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBases.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueBorder.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueChemProbAnnotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueColorMapStyle.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueFont.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueGlobalRescale.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueGlobalRotation.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueHighlightRegionEdit.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueJPEG.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueListeAnnotations.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueLoadColorMapValues.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueManualInput.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueMenu.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueNumPeriod.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueRNAList.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueSpaceBetweenBases.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueStyleBP.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueUI.java [new file with mode: 0644]
srcjar/fr/orsay/lri/varna/views/VueZoom.java [new file with mode: 0644]
srcjar/org/apache/log4j/Appender.java [new file with mode: 0644]
srcjar/org/apache/log4j/AppenderSkeleton.java [new file with mode: 0644]
srcjar/org/apache/log4j/AsyncAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/BasicConfigurator.java [new file with mode: 0644]
srcjar/org/apache/log4j/Category.java [new file with mode: 0644]
srcjar/org/apache/log4j/CategoryKey.java [new file with mode: 0644]
srcjar/org/apache/log4j/ConsoleAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/DailyRollingFileAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/DefaultCategoryFactory.java [new file with mode: 0644]
srcjar/org/apache/log4j/DefaultThrowableRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/Dispatcher.java [new file with mode: 0644]
srcjar/org/apache/log4j/EnhancedPatternLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/EnhancedThrowableRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/FileAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/HTMLLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/Hierarchy.java [new file with mode: 0644]
srcjar/org/apache/log4j/Layout.java [new file with mode: 0644]
srcjar/org/apache/log4j/Level.java [new file with mode: 0644]
srcjar/org/apache/log4j/LogMF.java [new file with mode: 0644]
srcjar/org/apache/log4j/LogManager.java [new file with mode: 0644]
srcjar/org/apache/log4j/LogSF.java [new file with mode: 0644]
srcjar/org/apache/log4j/LogXF.java [new file with mode: 0644]
srcjar/org/apache/log4j/Logger.java [new file with mode: 0644]
srcjar/org/apache/log4j/MDC.java [new file with mode: 0644]
srcjar/org/apache/log4j/NDC.java [new file with mode: 0644]
srcjar/org/apache/log4j/PatternLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/Priority.java [new file with mode: 0644]
srcjar/org/apache/log4j/PropertyConfigurator.java [new file with mode: 0644]
srcjar/org/apache/log4j/ProvisionNode.java [new file with mode: 0644]
srcjar/org/apache/log4j/README.txt [new file with mode: 0644]
srcjar/org/apache/log4j/RollingFileAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/SimpleLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/TTCCLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/WriterAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/config/PropertyGetter.java [new file with mode: 0644]
srcjar/org/apache/log4j/config/PropertyPrinter.java [new file with mode: 0644]
srcjar/org/apache/log4j/config/PropertySetter.java [new file with mode: 0644]
srcjar/org/apache/log4j/config/PropertySetterException.java [new file with mode: 0644]
srcjar/org/apache/log4j/config/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/AppenderAttachableImpl.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/BoundedFIFO.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/CountingQuietWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/CyclicBuffer.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/DateLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/DateTimeDateFormat.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/FileWatchdog.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/FormattingInfo.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/ISO8601DateFormat.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/Loader.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/LogLog.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/MDCKeySetExtractor.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/NullEnumeration.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/OnlyOnceErrorHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/OptionConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/PatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/PatternParser.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/QuietWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/RelativeTimeDateFormat.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/SyslogQuietWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/SyslogWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/ThreadLocalMap.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/Transform.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/UtilLoggingLevel.java [new file with mode: 0644]
srcjar/org/apache/log4j/helpers/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/jdbc/JDBCAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/jdbc/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/AbstractDynamicMBean.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/Agent.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/AppenderDynamicMBean.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/HierarchyDynamicMBean.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/LayoutDynamicMBean.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/LoggerDynamicMBean.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/MethodUnion.java [new file with mode: 0644]
srcjar/org/apache/log4j/jmx/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/net/JMSAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/JMSSink.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SMTPAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SimpleSocketServer.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SocketAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SocketHubAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SocketNode.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SocketServer.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/SyslogAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/TelnetAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/ZeroConfSupport.java [new file with mode: 0644]
srcjar/org/apache/log4j/net/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/nt/NTEventLogAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/nt/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/or/DefaultRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/ObjectRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/RendererMap.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/ThreadGroupRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/jms/MessageRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/jms/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/or/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/or/sax/AttributesRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/or/sax/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/BridgePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/BridgePatternParser.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/CachedDateFormat.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/ClassNamePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/DatePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/FileDatePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/FileLocationPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/FormattingInfo.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/FullLocationPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/IntegerPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LevelPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LineLocationPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LineSeparatorPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LiteralPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LogEvent.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LoggerPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/LoggingEventPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/MessagePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/MethodLocationPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/NDCPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/NameAbbreviator.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/NamePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/PatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/PatternParser.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/PropertiesPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/RelativeTimePatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/SequenceNumberPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/ThreadPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/ThrowableInformationPatternConverter.java [new file with mode: 0644]
srcjar/org/apache/log4j/pattern/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/rewrite/MapRewritePolicy.java [new file with mode: 0644]
srcjar/org/apache/log4j/rewrite/PropertyRewritePolicy.java [new file with mode: 0644]
srcjar/org/apache/log4j/rewrite/ReflectionRewritePolicy.java [new file with mode: 0644]
srcjar/org/apache/log4j/rewrite/RewriteAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/rewrite/RewritePolicy.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/AppenderAttachable.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/Configurator.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/DefaultRepositorySelector.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/ErrorCode.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/ErrorHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/Filter.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/HierarchyEventListener.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/LocationInfo.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/LoggerFactory.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/LoggerRepository.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/LoggingEvent.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/NOPLogger.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/NOPLoggerRepository.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/NullWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/OptionHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/RendererSupport.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/RepositorySelector.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/RootCategory.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/RootLogger.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/ThrowableInformation.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/ThrowableRenderer.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/ThrowableRendererSupport.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/TriggeringEventEvaluator.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/VectorWriter.java [new file with mode: 0644]
srcjar/org/apache/log4j/spi/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/varia/DenyAllFilter.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/ExternallyRolledFileAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/FallbackErrorHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/LevelMatchFilter.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/LevelRangeFilter.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/NullAppender.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/ReloadingPropertyConfigurator.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/Roller.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/StringMatchFilter.java [new file with mode: 0644]
srcjar/org/apache/log4j/varia/package.html [new file with mode: 0644]
srcjar/org/apache/log4j/xml/DOMConfigurator.java [new file with mode: 0644]
srcjar/org/apache/log4j/xml/Log4jEntityResolver.java [new file with mode: 0644]
srcjar/org/apache/log4j/xml/SAXErrorHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/xml/UnrecognizedElementHandler.java [new file with mode: 0644]
srcjar/org/apache/log4j/xml/XMLLayout.java [new file with mode: 0644]
srcjar/org/apache/log4j/xml/package.html [new file with mode: 0644]

diff --git a/srcjar/fr/orsay/lri/varna/README_SWINGJS.txt b/srcjar/fr/orsay/lri/varna/README_SWINGJS.txt
new file mode 100644 (file)
index 0000000..4847972
--- /dev/null
@@ -0,0 +1,156 @@
+
+Status
+======
+
+1/10/2018
+
+fixes VueUI handling of JFileChooser and JColorChooser callbacks not using revised SelectedFile and SelectedColor property names
+implements blinking iterator when dragging to reposition RNA blob
+implements annotations 
+
+1/7/2018
+
+JTable implemented, including editing; needs better efficiency 
+Modal dialogs are working, including file open.
+Drag-and-drop of Files is working 
+ - note that the dropped File object has bytes field with data
+Animated interpolation working; switched to simple JTimer mechanism for Java and JavaScript
+
+1/2/2018
+
+Varna is running.
+modal JOptionPane implemented fully
+JColorChooser implemented fully
+JTable implemented; still some minor issues
+
+Modal dialogs are working, except for FileOpen.
+Popup menu is working.
+
+
+Issues
+======
+
+- JTable has minor issues:
+ - Headings are not shaded
+ - needs attention to higher efficiency 
+
+- DnD only implemented for files. 
+  - probably x,y coord are off - untested
+  - needs checking for isolated frames (works in applet)
+  
+
+Modifications for SwingJS
+=========================
+
+Search for "@j2sNative", "BH", or "SwingJS"
+
+
+VARNA.java 
+----------
+
+moved to fr.orsay.lri.varna (all SwingJS project files must be in packages)
+
+adds default RNA JavaScript:
+
+         if (!thisApplet.__Info.sequenceDBN) {
+          thisApplet.__Info.sequenceDBN = "GGGGCCAAUAUGGCCAUCC";
+          thisApplet.__Info.structureDBN = "((((((.....))))..))";
+          thisApplet.__Info.title = prompt("Title?","Hello RNA world, from SwingJS!");
+         } 
+
+
+
+fr.orsay.lri.varna.factories.RNAFactory
+---------------------------------------
+
+Cannot depend upon Java ArrayIndexOutOfBounds for trapping when testing formats
+
+JAVA fix: Removing unnecessary exception print stack traces during testing for formats
+JAVA fix: RNAFactory was not closing file reader
+
+
+
+fr.orsay.lri.varna.applications.VARNAEditor
+-------------------------------------------
+
+switched to RNAFactory.loadSecStr((File) o) for drag-drop allows passing byte data
+
+
+fr.orsay.lri.varna.applications.VARNAGUI
+----------------------------------------
+
+switched to RNAFactory.loadSecStr((File) o) for drag-drop allows passing byte data
+
+
+fr.orsay.lri.varna.controlers.ControleurBaseSpecialColorEditor
+--------------------------------------------------------------
+
+Since the editor is not modal, we have to catch the window hiding event 
+before closing the editor.
+
+
+
+fr.orsay.lri.varna.controlers.ControleurInterpolator
+----------------------------------------------------
+
+switch to JTimer for interpolation
+JavaScript uses 2-second delay; Java uses 15-second delay
+
+
+
+fr.orsay.lri.varna.views.PrinterTest.java
+-----------------------------------------
+
+simpler test that does not use java.awt.font.TextLayout, which is not implemented.
+
+
+
+fr.orsay.lri.varna.VarnaPanel.java
+----------------------------------
+
+now implements PropertyChangeListener for asynchronous callback.
+
+
+
+fr.orsay.lri.varna.views.VueUI.java
+------------------------------------
+
+All JOptionPane, JFileChooser, and JColorChooser action made asynchronous. 
+Basically, the results OK, CANCEL, YES, NO, CLOSED, and custom button index
+are delivered to instances of runnable via a PropertyChangeListener callback
+to the indicated parent frame. 
+
+Simple ERROR_OPTION and WARN_OPTION messages are handled via JavaScript Alert;
+fall back options for simple showConfirmDialog and showInputDialog are used
+automatically if the parent frame does not implement PropertyChangeListener. 
+
+Initial JavaScript-only return is:
+
+ for int-returning methods, NaN, 
+   testable as value != Math.floor(value), where value is an int, and
+ for Object-returning methods, an Object that implements UIResource,  
+   testable as event.getNewValue() instanceof UIResource. 
+This allows full compatibility in Java and JavaScript.
+
+
+See notes in fr.orsay.lri.varna.views.VueUI.java.
+
+
+fr.orsay.lri.varna.views.VueMenu.java
+-------------------------------------
+
+changes JLabel to JMenuItem - not 100% sure why that is necessary. 
+changes JSeparator to JPopupMenu.Separator
+
+
+
+fr.orsay.lri.varna.views.VueAbout.java
+--------------------------------------
+
+added simple JTimer for asynchronous animation
+added asynchronous callback modal option for JavaScript
+
diff --git a/srcjar/fr/orsay/lri/varna/VARNA.java b/srcjar/fr/orsay/lri/varna/VARNA.java
new file mode 100644 (file)
index 0000000..1f98d50
--- /dev/null
@@ -0,0 +1,260 @@
+package fr.orsay.lri.varna;
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JApplet;
+import javax.swing.JOptionPane;
+
+import fr.orsay.lri.varna.controlers.ControleurScriptParser;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.interfaces.InterfaceParameterLoader;
+import fr.orsay.lri.varna.models.VARNAConfigLoader;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+// @j2s issues -- see README_SWINGJS.txt
+
+public class VARNA extends JApplet implements InterfaceParameterLoader,DropTargetListener {
+       ArrayList<VARNAPanel> _vpl = null;
+       static{
+               /**
+                * 
+                * @j2sNative
+                * 
+                * 
+                * if (!thisApplet.__Info.sequenceDBN) {
+                *  thisApplet.__Info.sequenceDBN = "GGGGCCAAUAUGGCCAUCC";
+                *  thisApplet.__Info.structureDBN = "((((((.....))))..))";
+                *  thisApplet.__Info.title = "Hello RNA world, from SwingJS!";//prompt("Title?","Hello RNA world!");
+                * } 
+                * 
+                * 
+                * 
+                */             
+       }
+
+       
+//     private static final_long serialVersionUID = -2598221520127067670L;
+
+       public VARNA() {
+               super();
+       }
+
+       public void init() {
+               try {
+                       VARNAConfigLoader VARNAcfg = new VARNAConfigLoader(this);
+                       
+                       try {
+                               _vpl = VARNAcfg.createVARNAPanels();
+                               for (int i=0;i<_vpl.size();i++)
+                               {
+                                   new DropTarget(_vpl.get(i), this);
+                               }
+                       } catch (IOException e) {
+                               JOptionPane.showMessageDialog(this, e.getMessage(),
+                                               "VARNA Error", JOptionPane.ERROR_MESSAGE);
+                       } catch (ExceptionFileFormatOrSyntax e) {
+                               JOptionPane.showMessageDialog(this, e.getMessage(),
+                                               "VARNA Error", JOptionPane.ERROR_MESSAGE);
+                       } catch (ExceptionLoadingFailed e) {
+                               JOptionPane.showMessageDialog(this, e.getMessage(),
+                                               "VARNA Error", JOptionPane.ERROR_MESSAGE);
+                       }
+                       setLayout(new GridLayout(VARNAcfg.getNbColumns(), VARNAcfg
+                                       .getNbRows()));
+                       for (int i = 0; i < _vpl.size(); i++) {
+                               getContentPane().add(_vpl.get(i));
+                       }
+               } catch (ExceptionParameterError e) {
+                       VARNAPanel.errorDialogStatic(e, this);
+               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                       VARNAPanel.errorDialogStatic(e, this);
+               } catch (ExceptionNonEqualLength e) {
+                       VARNAPanel.errorDialogStatic(e, this);
+               }
+
+       }
+
+       public void start() {
+               //setVisible(true);
+               //repaint();
+               //getContentPane().setVisible(true);            
+               //getContentPane().repaint();           
+       }
+       
+       public void update() {
+               System.out.println("update");
+       }
+       
+       public String getParameterValue(String key, String def) {
+               if (getParameter(key) == null) {
+                       return def;
+               } else {
+                       return getParameter(key);
+               }
+       }
+
+       public String[][] getParameterInfo() {
+               return VARNAConfigLoader.getParameterInfo();
+       }
+       
+       public ArrayList<VARNAPanel> getPanels()
+       {
+               return _vpl;
+       }
+       
+       public String getSelection()
+       {
+               return getSelection(0);
+       }
+       
+       public String getSelection(int panel)
+       {
+               String result = "[";
+               VARNAPanel v = _vpl.get(panel);
+               List<Integer> l = v.getSelectionIndices();
+               for(int i=0;i<l.size();i++)
+               {
+                       int n = l.get(i);
+                       if (i>0)
+                       {result += ",";}
+                       result += n;
+                       
+               }
+               result += "]";
+               return result;
+       }
+
+       public void runScript(String script)
+       {
+               if (_vpl.size()>0)
+               { 
+                       VARNAPanel _vp = _vpl.get(0);
+                       try {
+                               ControleurScriptParser.executeScript(_vp, script);
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                       }
+               }
+       }
+       
+       
+       public void setRNA(String seq, String str) 
+       {
+               if (_vpl.size()>0)
+               { 
+                       try {
+                               _vpl.get(0).drawRNA(seq, str);
+                       } catch (ExceptionNonEqualLength e) {
+                               e.printStackTrace();
+                       } 
+               }
+       }
+
+       public void setSmoothedRNA(String seq, String str) 
+       {
+               if (_vpl.size()>0)
+               { 
+                       try {
+                                 
+                                 _vpl.get(0).drawRNAInterpolated(seq, str);
+                                 _vpl.get(0).repaint();
+                       } catch (ExceptionNonEqualLength e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } 
+               }
+       }
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+       }
+
+       public void drop(DropTargetDropEvent dtde) 
+       {
+         try 
+         {
+           Transferable tr = dtde.getTransferable();
+           DataFlavor[] flavors = tr.getTransferDataFlavors();
+           for (int i = 0; i < flavors.length; i++) 
+           {
+             if (flavors[i].isFlavorJavaFileListType()) 
+             {
+                 dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+                 List list = (List) tr.getTransferData(flavors[i]);
+                 for (int j = 0; j < list.size(); j++) 
+                 {
+                         Object o = list.get(j);
+                         if (dtde.getSource() instanceof DropTarget)
+                         {
+                                 DropTarget dt = (DropTarget) dtde.getSource();
+                                 Component c = dt.getComponent();
+                                 if (c instanceof VARNAPanel)
+                                 {
+                                         VARNAPanel vp = (VARNAPanel) c;
+                                         // BH -- in JavaScript, the File object has a .bytes 
+                                         // property that we need to maintain.
+                                         //String path = o.toString();
+                                         vp.loadFile((File) o,true);
+                                         //vp.repaint(); BH unnecessary
+                                 }
+                         }
+                 }
+                 dtde.dropComplete(true);
+                 return;
+             }
+           }
+        dtde.rejectDrop();
+        } 
+        catch (Exception e) 
+        {
+                e.printStackTrace();
+            dtde.rejectDrop();
+         }
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+       }
+
+       
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/VARNAPanel.java b/srcjar/fr/orsay/lri/varna/VARNAPanel.java
new file mode 100644 (file)
index 0000000..13906af
--- /dev/null
@@ -0,0 +1,4526 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2012  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.9.
+ VARNA version 3.9 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.9 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+
+/*
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+ software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+ to take away your freedom to share and change the works.  By contrast,
+ the GNU General Public License is intended to guarantee your freedom to
+ share and change all versions of a program--to make sure it remains free
+ software for all its users.  We, the Free Software Foundation, use the
+ GNU General Public License for most of our software; it applies also to
+ any other work released this way by its authors.  You can apply it to
+ your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+ price.  Our General Public Licenses are designed to make sure that you
+ have the freedom to distribute copies of free software (and charge for
+ them if you wish), that you receive source code or can get it if you
+ want it, that you can change the software or use pieces of it in new
+ free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+ these rights or asking you to surrender the rights.  Therefore, you have
+ certain responsibilities if you distribute copies of the software, or if
+ you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+ gratis or for a fee, you must pass on to the recipients the same
+ freedoms that you received.  You must make sure that they, too, receive
+ or can get the source code.  And you must show them these terms so they
+ know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+ (1) assert copyright on the software, and (2) offer you this License
+ giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+ that there is no warranty for this free software.  For both users' and
+ authors' sake, the GPL requires that modified versions be marked as
+ changed, so that their problems will not be attributed erroneously to
+ authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+ modified versions of the software inside them, although the manufacturer
+ can do so.  This is fundamentally incompatible with the aim of
+ protecting users' freedom to change the software.  The systematic
+ pattern of such abuse occurs in the area of products for individuals to
+ use, which is precisely where it is most unacceptable.  Therefore, we
+ have designed this version of the GPL to prohibit the practice for those
+ products.  If such problems arise substantially in other domains, we
+ stand ready to extend this provision to those domains in future versions
+ of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+ States should not allow patents to restrict development and use of
+ software on general-purpose computers, but in those that do, we wish to
+ avoid the special danger that patents applied to a free program could
+ make it effectively proprietary.  To prevent this, the GPL assures that
+ patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+ works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+ License.  Each licensee is addressed as "you".  "Licensees" and
+ "recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+ in a fashion requiring copyright permission, other than the making of an
+ exact copy.  The resulting work is called a "modified version" of the
+ earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+ on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+ permission, would make you directly or secondarily liable for
+ infringement under applicable copyright law, except executing it on a
+ computer or modifying a private copy.  Propagation includes copying,
+ distribution (with or without modification), making available to the
+ public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+ parties to make or receive copies.  Mere interaction with a user through
+ a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+ to the extent that it includes a convenient and prominently visible
+ feature that (1) displays an appropriate copyright notice, and (2)
+ tells the user that there is no warranty for the work (except to the
+ extent that warranties are provided), that licensees may convey the
+ work under this License, and how to view a copy of this License.  If
+ the interface presents a list of user commands or options, such as a
+ menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+ for making modifications to it.  "Object code" means any non-source
+ form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+ standard defined by a recognized standards body, or, in the case of
+ interfaces specified for a particular programming language, one that
+ is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+ than the work as a whole, that (a) is included in the normal form of
+ packaging a Major Component, but which is not part of that Major
+ Component, and (b) serves only to enable use of the work with that
+ Major Component, or to implement a Standard Interface for which an
+ implementation is available to the public in source code form.  A
+ "Major Component", in this context, means a major essential component
+ (kernel, window system, and so on) of the specific operating system
+ (if any) on which the executable work runs, or a compiler used to
+ produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+ the source code needed to generate, install, and (for an executable
+ work) run the object code and to modify the work, including scripts to
+ control those activities.  However, it does not include the work's
+ System Libraries, or general-purpose tools or generally available free
+ programs which are used unmodified in performing those activities but
+ which are not part of the work.  For example, Corresponding Source
+ includes interface definition files associated with source files for
+ the work, and the source code for shared libraries and dynamically
+ linked subprograms that the work is specifically designed to require,
+ such as by intimate data communication or control flow between those
+ subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+ can regenerate automatically from other parts of the Corresponding
+ Source.
+
+ The Corresponding Source for a work in source code form is that
+ same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+ copyright on the Program, and are irrevocable provided the stated
+ conditions are met.  This License explicitly affirms your unlimited
+ permission to run the unmodified Program.  The output from running a
+ covered work is covered by this License only if the output, given its
+ content, constitutes a covered work.  This License acknowledges your
+ rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+ convey, without conditions so long as your license otherwise remains
+ in force.  You may convey covered works to others for the sole purpose
+ of having them make modifications exclusively for you, or provide you
+ with facilities for running those works, provided that you comply with
+ the terms of this License in conveying all material for which you do
+ not control copyright.  Those thus making or running the covered works
+ for you must do so exclusively on your behalf, under your direction
+ and control, on terms that prohibit them from making any copies of
+ your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+ the conditions stated below.  Sublicensing is not allowed; section 10
+ makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+ measure under any applicable law fulfilling obligations under article
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
+ similar laws prohibiting or restricting circumvention of such
+ measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+ circumvention of technological measures to the extent such circumvention
+ is effected by exercising rights under this License with respect to
+ the covered work, and you disclaim any intention to limit operation or
+ modification of the work as a means of enforcing, against the work's
+ users, your or third parties' legal rights to forbid circumvention of
+ technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+ receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright notice;
+ keep intact all notices stating that this License and any
+ non-permissive terms added in accord with section 7 apply to the code;
+ keep intact all notices of the absence of any warranty; and give all
+ recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+ and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+ produce it from the Program, in the form of source code under the
+ terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7.  This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy.  This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged.  This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+ works, which are not by their nature extensions of the covered work,
+ and which are not combined with it such as to form a larger program,
+ in or on a volume of a storage or distribution medium, is called an
+ "aggregate" if the compilation and its resulting copyright are not
+ used to limit the access or legal rights of the compilation's users
+ beyond what the individual works permit.  Inclusion of a covered work
+ in an aggregate does not cause this License to apply to the other
+ parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+ of sections 4 and 5, provided that you also convey the
+ machine-readable Corresponding Source under the terms of this License,
+ in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source.  This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge.  You need not require recipients to copy the
+ Corresponding Source along with the object code.  If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source.  Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+ from the Corresponding Source as a System Library, need not be
+ included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+ tangible personal property which is normally used for personal, family,
+ or household purposes, or (2) anything designed or sold for incorporation
+ into a dwelling.  In determining whether a product is a consumer product,
+ doubtful cases shall be resolved in favor of coverage.  For a particular
+ product received by a particular user, "normally used" refers to a
+ typical or common use of that class of product, regardless of the status
+ of the particular user or of the way in which the particular user
+ actually uses, or expects or is expected to use, the product.  A product
+ is a consumer product regardless of whether the product has substantial
+ commercial, industrial or non-consumer uses, unless such uses represent
+ the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+ procedures, authorization keys, or other information required to install
+ and execute modified versions of a covered work in that User Product from
+ a modified version of its Corresponding Source.  The information must
+ suffice to ensure that the continued functioning of the modified object
+ code is in no case prevented or interfered with solely because
+ modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+ specifically for use in, a User Product, and the conveying occurs as
+ part of a transaction in which the right of possession and use of the
+ User Product is transferred to the recipient in perpetuity or for a
+ fixed term (regardless of how the transaction is characterized), the
+ Corresponding Source conveyed under this section must be accompanied
+ by the Installation Information.  But this requirement does not apply
+ if neither you nor any third party retains the ability to install
+ modified object code on the User Product (for example, the work has
+ been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+ requirement to continue to provide support service, warranty, or updates
+ for a work that has been modified or installed by the recipient, or for
+ the User Product in which it has been modified or installed.  Access to a
+ network may be denied when the modification itself materially and
+ adversely affects the operation of the network or violates the rules and
+ protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+ in accord with this section must be in a format that is publicly
+ documented (and with an implementation available to the public in
+ source code form), and must require no special password or key for
+ unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+ License by making exceptions from one or more of its conditions.
+ Additional permissions that are applicable to the entire Program shall
+ be treated as though they were included in this License, to the extent
+ that they are valid under applicable law.  If additional permissions
+ apply only to part of the Program, that part may be used separately
+ under those permissions, but the entire Program remains governed by
+ this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+ remove any additional permissions from that copy, or from any part of
+ it.  (Additional permissions may be written to require their own
+ removal in certain cases when you modify the work.)  You may place
+ additional permissions on material, added by you to a covered work,
+ for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+ add to a covered work, you may (if authorized by the copyright holders of
+ that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+ restrictions" within the meaning of section 10.  If the Program as you
+ received it, or any part of it, contains a notice stating that it is
+ governed by this License along with a term that is a further
+ restriction, you may remove that term.  If a license document contains
+ a further restriction but permits relicensing or conveying under this
+ License, you may add to a covered work material governed by the terms
+ of that license document, provided that the further restriction does
+ not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+ must place, in the relevant source files, a statement of the
+ additional terms that apply to those files, or a notice indicating
+ where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+ form of a separately written license, or stated as exceptions;
+ the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+ provided under this License.  Any attempt otherwise to propagate or
+ modify it is void, and will automatically terminate your rights under
+ this License (including any patent licenses granted under the third
+ paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+ license from a particular copyright holder is reinstated (a)
+ provisionally, unless and until the copyright holder explicitly and
+ finally terminates your license, and (b) permanently, if the copyright
+ holder fails to notify you of the violation by some reasonable means
+ prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+ reinstated permanently if the copyright holder notifies you of the
+ violation by some reasonable means, this is the first time you have
+ received notice of violation of this License (for any work) from that
+ copyright holder, and you cure the violation prior to 30 days after
+ your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+ licenses of parties who have received copies or rights from you under
+ this License.  If your rights have been terminated and not permanently
+ reinstated, you do not qualify to receive new licenses for the same
+ material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+ run a copy of the Program.  Ancillary propagation of a covered work
+ occurring solely as a consequence of using peer-to-peer transmission
+ to receive a copy likewise does not require acceptance.  However,
+ nothing other than this License grants you permission to propagate or
+ modify any covered work.  These actions infringe copyright if you do
+ not accept this License.  Therefore, by modifying or propagating a
+ covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+ receives a license from the original licensors, to run, modify and
+ propagate that work, subject to this License.  You are not responsible
+ for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+ organization, or substantially all assets of one, or subdividing an
+ organization, or merging organizations.  If propagation of a covered
+ work results from an entity transaction, each party to that
+ transaction who receives a copy of the work also receives whatever
+ licenses to the work the party's predecessor in interest had or could
+ give under the previous paragraph, plus a right to possession of the
+ Corresponding Source of the work from the predecessor in interest, if
+ the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+ rights granted or affirmed under this License.  For example, you may
+ not impose a license fee, royalty, or other charge for exercise of
+ rights granted under this License, and you may not initiate litigation
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
+ any patent claim is infringed by making, using, selling, offering for
+ sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+ License of the Program or a work on which the Program is based.  The
+ work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+ owned or controlled by the contributor, whether already acquired or
+ hereafter acquired, that would be infringed by some manner, permitted
+ by this License, of making, using, or selling its contributor version,
+ but do not include claims that would be infringed only as a
+ consequence of further modification of the contributor version.  For
+ purposes of this definition, "control" includes the right to grant
+ patent sublicenses in a manner consistent with the requirements of
+ this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+ patent license under the contributor's essential patent claims, to
+ make, use, sell, offer for sale, import and otherwise run, modify and
+ propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+ agreement or commitment, however denominated, not to enforce a patent
+ (such as an express permission to practice a patent or covenant not to
+ sue for patent infringement).  To "grant" such a patent license to a
+ party means to make such an agreement or commitment not to enforce a
+ patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+ and the Corresponding Source of the work is not available for anyone
+ to copy, free of charge and under the terms of this License, through a
+ publicly available network server or other readily accessible means,
+ then you must either (1) cause the Corresponding Source to be so
+ available, or (2) arrange to deprive yourself of the benefit of the
+ patent license for this particular work, or (3) arrange, in a manner
+ consistent with the requirements of this License, to extend the patent
+ license to downstream recipients.  "Knowingly relying" means you have
+ actual knowledge that, but for the patent license, your conveying the
+ covered work in a country, or your recipient's use of the covered work
+ in a country, would infringe one or more identifiable patents in that
+ country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+ arrangement, you convey, or propagate by procuring conveyance of, a
+ covered work, and grant a patent license to some of the parties
+ receiving the covered work authorizing them to use, propagate, modify
+ or convey a specific copy of the covered work, then the patent license
+ you grant is automatically extended to all recipients of the covered
+ work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+ the scope of its coverage, prohibits the exercise of, or is
+ conditioned on the non-exercise of one or more of the rights that are
+ specifically granted under this License.  You may not convey a covered
+ work if you are a party to an arrangement with a third party that is
+ in the business of distributing software, under which you make payment
+ to the third party based on the extent of your activity of conveying
+ the work, and under which the third party grants, to any of the
+ parties who would receive the covered work from you, a discriminatory
+ patent license (a) in connection with copies of the covered work
+ conveyed by you (or copies made from those copies), or (b) primarily
+ for and in connection with specific products or compilations that
+ contain the covered work, unless you entered into that arrangement,
+ or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+ any implied license or other defenses to infringement that may
+ otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License.  If you cannot convey a
+ covered work so as to satisfy simultaneously your obligations under this
+ License and any other pertinent obligations, then as a consequence you may
+ not convey it at all.  For example, if you agree to terms that obligate you
+ to collect a royalty for further conveying from those to whom you convey
+ the Program, the only way you could satisfy both those terms and this
+ License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+ permission to link or combine any covered work with a work licensed
+ under version 3 of the GNU Affero General Public License into a single
+ combined work, and to convey the resulting work.  The terms of this
+ License will continue to apply to the part which is the covered work,
+ but the special requirements of the GNU Affero General Public License,
+ section 13, concerning interaction through a network will apply to the
+ combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+ the GNU General Public License from time to time.  Such new versions will
+ be similar in spirit to the present version, but may differ in detail to
+ address new problems or concerns.
+
+ Each version is given a distinguishing version number.  If the
+ Program specifies that a certain numbered version of the GNU General
+ Public License "or any later version" applies to it, you have the
+ option of following the terms and conditions either of that numbered
+ version or of any later version published by the Free Software
+ Foundation.  If the Program does not specify a version number of the
+ GNU General Public License, you may choose any version ever published
+ by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+ versions of the GNU General Public License can be used, that proxy's
+ public statement of acceptance of a version permanently authorizes you
+ to choose that version for the Program.
+
+ Later license versions may give you additional or different
+ permissions.  However, no additional obligations are imposed on any
+ author or copyright holder as a result of your choosing to follow a
+ later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+ APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+ IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+ above cannot be given local legal effect according to their terms,
+ reviewing courts shall apply local law that most closely approximates
+ an absolute waiver of all civil liability in connection with the
+ Program, unless a warranty or assumption of liability accompanies a
+ copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+ */
+
+package fr.orsay.lri.varna;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Set;
+
+import javax.print.attribute.HashPrintRequestAttributeSet;
+import javax.print.attribute.PrintRequestAttributeSet;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.undo.UndoManager;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.controlers.ControleurBlinkingThread;
+import fr.orsay.lri.varna.controlers.ControleurClicMovement;
+import fr.orsay.lri.varna.controlers.ControleurDraggedMolette;
+import fr.orsay.lri.varna.controlers.ControleurInterpolator;
+import fr.orsay.lri.varna.controlers.ControleurMolette;
+import fr.orsay.lri.varna.controlers.ControleurVARNAPanelKeys;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNABasesListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.FullBackup;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.export.SwingGraphics;
+import fr.orsay.lri.varna.models.export.VueVARNAGraphics;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBackbone;
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement.BackboneType;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBasesComparison;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.utils.VARNASessionParser;
+import fr.orsay.lri.varna.views.VueMenu;
+import fr.orsay.lri.varna.views.VueUI;
+
+/**
+ * 
+ * BH j2s SwingJS Added PropertyChangeListener for returns from VueUI.  
+ * 
+ *  
+ *  
+ * 
+ * The RNA 2D Panel is a lightweight component that allows for an automatic
+ * basic drawing of an RNA secondary structures. The drawing algorithms do not
+ * ensure a non-overlapping drawing of helices, thus it is possible to "spin the
+ * helices" through a click-and-drag approach. A typical usage of the class from
+ * within the constructor of a <code>JFrame</code> would be the following:<br/>
+ * <code>
+ * &nbsp;&nbsp;VARNAPanel _rna = new VARNAPanel("CCCCAUAUGGGGACC","((((....))))...");<br />
+ * &nbsp;&nbsp;this.getContentPane().add(_rna);
+ * </code>
+ * 
+ * @version 3.4
+ * @author Yann Ponty & Kevin Darty
+ * 
+ */
+
+public class VARNAPanel extends JPanel implements PropertyChangeListener {
+       
+       /**
+        * SwingJS uses a PropertyChangeEvent to signal that a pseudo-modal dialog has been closed.
+        *   
+        * @param event
+        */
+       @Override
+       public void propertyChange(PropertyChangeEvent event) {
+               Object val = event.getNewValue();
+               switch (event.getPropertyName()) {
+               case "value":
+                       _UI.onDialogReturn(val == null ? JOptionPane.CLOSED_OPTION : ((Integer) val).intValue());
+                       return;
+               case "SelectedFile":
+               case "SelectedColor":
+               case "inputValue":
+                       _UI.onDialogReturn(val);
+                       break;
+               }
+       }
+
+       
+       private static final long serialVersionUID = 8194421570308956001L;
+
+       private RNA _RNA = new RNA();
+
+       private boolean _debug = false;
+
+       private VARNAConfig _conf = new VARNAConfig();
+
+       private ArrayList<InterfaceVARNAListener> _VARNAListeners = new ArrayList<InterfaceVARNAListener>();
+       private ArrayList<InterfaceVARNASelectionListener> _selectionListeners = new ArrayList<InterfaceVARNASelectionListener>();
+       private ArrayList<InterfaceVARNARNAListener> _RNAListeners = new ArrayList<InterfaceVARNARNAListener>();
+       private ArrayList<InterfaceVARNABasesListener> _basesListeners = new ArrayList<InterfaceVARNABasesListener>();
+
+       UndoManager _manager;
+
+       // private boolean _foldMode = true;
+
+       private Point2D.Double[] _realCoords = new Point2D.Double[0];
+       private Point2D.Double[] _realCenters = new Point2D.Double[0];
+       private double _scaleFactor = 1.0;
+       private Point2D.Double _offsetPanel = new Point2D.Double();
+       private Point2D.Double _offsetRNA = new Point2D.Double();
+
+       private double _offX;
+       private double _offY;
+
+       private ControleurBlinkingThread _blink;
+       private BaseList _selectedBases = new BaseList("selection");
+       private ArrayList<ModeleBase> _backupSelection = new ArrayList<ModeleBase>();
+       private Integer _nearestBase = null;
+       private Point2D.Double _lastSelectedCoord = new Point2D.Double(0.0, 0.0);
+
+       private Point2D.Double _linkOrigin = null;
+       private Point2D.Double _linkDestination = null;
+
+       private Rectangle _selectionRectangle = null;
+
+       private boolean _highlightAnnotation = false;
+
+       private int _titleHeight;
+       private Dimension _border = new Dimension(0, 0);
+
+       private boolean _drawBBox = false;
+       private boolean _drawBorder = false;
+
+       // private Point _positionRelativeSouris;
+       private Point _translation;
+       private boolean _horsCadre;
+       private boolean _premierAffichage;
+
+       private ControleurInterpolator _interpolator;
+       /**
+        * If comparison mode is TRUE (ON), then the application will be used to
+        * display a super-structure resulting on an RNA secondary structure
+        * comparison. Else, the application is used by default.
+        */
+
+       private VueMenu _popup = new VueMenu(this);
+
+       private VueUI _UI = new VueUI(this);
+
+       private TextAnnotation _selectedAnnotation;
+
+       /**
+        * Creates an RNA 2D panel with initially displays the empty structure.
+        * 
+        * @throws ExceptionNonEqualLength
+        * 
+        */
+       public VARNAPanel() {
+               init();
+               drawRNA();
+       }
+
+       /**
+        * Creates an RNA 2D panel, and creates and displays an RNA coupled with its
+        * secondary structure formatted as a well-balanced parenthesis with dots
+        * word (DBN format).
+        * 
+        * @param seq
+        *            The raw nucleotide sequence
+        * @param str
+        *            The secondary structure in DBN format
+        * @throws ExceptionNonEqualLength
+        */
+
+       public VARNAPanel(String seq, String str) throws ExceptionNonEqualLength {
+               this(seq, str, RNA.DRAW_MODE_RADIATE);
+       }
+
+       /**
+        * Creates a VARNAPanel instance, and creates and displays an RNA coupled
+        * with its secondary structure formatted as a well-balanced parenthesis
+        * with dots word (DBN format). Allows the user to choose the drawing
+        * algorithm to be used.
+        * 
+        * @param seq
+        *            The raw nucleotide sequence
+        * @param str
+        *            The secondary structure in DBN format
+        * @param drawMode
+        *            The drawing mode
+        * @throws ExceptionNonEqualLength
+        * @see RNA#DRAW_MODE_RADIATE
+        * @see RNA#DRAW_MODE_CIRCULAR
+        * @see RNA#DRAW_MODE_NAVIEW
+        */
+       public VARNAPanel(String seq, String str, int drawMode)
+                       throws ExceptionNonEqualLength {
+               this(seq, str, drawMode, "");
+       }
+
+       public VARNAPanel(Reader r) throws ExceptionNonEqualLength,
+                       ExceptionFileFormatOrSyntax {
+               this(r, RNA.DRAW_MODE_RADIATE);
+       }
+
+       public VARNAPanel(Reader r, int drawMode) throws ExceptionNonEqualLength,
+                       ExceptionFileFormatOrSyntax {
+               this(r, drawMode, "");
+       }
+
+       public VARNAPanel(Reader r, int drawMode, String title)
+                       throws ExceptionNonEqualLength, ExceptionFileFormatOrSyntax {
+               init();
+               drawRNA(r, drawMode);
+               setTitle(title);
+       }
+
+       public void setOriginLink(Point2D.Double p) {
+               _linkOrigin = (p);
+       }
+
+       public void setDestinationLink(Point2D.Double p) {
+               _linkDestination = (p);
+       }
+
+       public void removeLink() {
+               _linkOrigin = null;
+               _linkDestination = null;
+       }
+
+       /**
+        * Creates a VARNAPanel instance, and displays an RNA.
+        * 
+        * @param r
+        *            The RNA to be displayed within this panel
+        */
+
+       public VARNAPanel(RNA r) {
+               showRNA(r);
+               init();
+       }
+
+       /**
+        * Creates a VARNAPanel instance, and creates and displays an RNA coupled
+        * with its secondary structure formatted as a well-balanced parenthesis
+        * with dots word (DBN format). Allows the user to choose the drawing
+        * algorithm to be used. Additionally, sets the panel's title.
+        * 
+        * @param seq
+        *            The raw nucleotide sequence
+        * @param str
+        *            The secondary structure in DBN format
+        * @param drawMode
+        *            The drawing mode
+        * @param title
+        *            The panel title
+        * @throws ExceptionNonEqualLength
+        * @see RNA#DRAW_MODE_CIRCULAR
+        * @see RNA#DRAW_MODE_RADIATE
+        * @see RNA#DRAW_MODE_NAVIEW
+        */
+
+       public VARNAPanel(String seq, String str, int drawMode, String title)
+                       throws ExceptionNonEqualLength {
+               drawRNA(seq, str, drawMode);
+               init();
+               setTitle(title);
+               // VARNASecDraw._vp = this;
+       }
+
+       public VARNAPanel(String seq1, String struct1, String seq2, String struct2,
+                       int drawMode, String title) {
+               _conf._comparisonMode = true;
+               drawRNA(seq1, struct1, seq2, struct2, drawMode);
+               init();
+               setTitle(title);
+       }
+
+       private void init() {
+               setBackground(VARNAConfig.DEFAULT_BACKGROUND_COLOR);
+               _manager = new UndoManager();
+               _manager.setLimit(10000);
+               _UI.addUndoableEditListener(_manager);
+
+               _blink = new ControleurBlinkingThread(this,
+                               ControleurBlinkingThread.DEFAULT_FREQUENCY, 0, 1.0, 0.0, 0.2);
+               _blink.start();
+
+               _premierAffichage = true;
+               _translation = new Point(0, 0);
+
+               _horsCadre = false;
+               this.setFont(_conf._fontBasesGeneral);
+
+               // ajout des controleurs au VARNAPanel
+               ControleurClicMovement controleurClicMovement = new ControleurClicMovement(
+                               this);
+               this.addMouseListener(controleurClicMovement);
+               this.addMouseMotionListener(controleurClicMovement);
+               this.addMouseWheelListener(new ControleurMolette(this));
+
+               ControleurDraggedMolette ctrlDraggedMolette = new ControleurDraggedMolette(
+                               this);
+               this.addMouseMotionListener(ctrlDraggedMolette);
+               this.addMouseListener(ctrlDraggedMolette);
+
+               ControleurVARNAPanelKeys ctrlKey = new ControleurVARNAPanelKeys(this);
+               this.addKeyListener(ctrlKey);
+               this.addFocusListener(ctrlKey);
+
+               _interpolator = new ControleurInterpolator(this);
+               /**
+                * 
+                * BH SwingJS do not start this thread
+                * 
+                * @j2sNative 
+                */
+               {
+               _interpolator.start();
+               }
+
+       }
+
+       public void undo() {
+               if (_manager.canUndo())
+                       _manager.undo();
+       }
+
+       public void redo() {
+               if (_manager.canRedo())
+                       _manager.redo();
+       }
+
+       /**
+        * Sets the new style of the title font.
+        * 
+        * @param newStyle
+        *            An int that describes the new font style ("PLAIN","BOLD",
+        *            "BOLDITALIC", or "ITALIC")
+        */
+       public void setTitleFontStyle(int newStyle) {
+               _conf._titleFont = _conf._titleFont.deriveFont(newStyle);
+               updateTitleHeight();
+       }
+
+       /**
+        * Sets the new size of the title font.
+        * 
+        * @param newSize
+        *            The new size of the title font
+        */
+       public void setTitleFontSize(float newSize) {
+               //System.err.println("Applying title size "+newSize);
+               _conf._titleFont = _conf._titleFont.deriveFont(newSize);
+               updateTitleHeight();
+       }
+
+       /**
+        * Sets the new font family to be used for the title. Available fonts are
+        * system-specific, yet it seems that "Arial", "Dialog", and "MonoSpaced"
+        * are almost always available.
+        * 
+        * @param newFamily
+        *            New font family used for the title
+        */
+       public void setTitleFontFamily(String newFamily) {
+               _conf._titleFont = new Font(newFamily, _conf._titleFont.getStyle(),
+                               _conf._titleFont.getSize());
+               updateTitleHeight();
+       }
+
+       /**
+        * Sets the color to be used for the title.
+        * 
+        * @param newColor
+        *            A color used to draw the title
+        */
+       public void setTitleFontColor(Color newColor) {
+               _conf._titleColor = newColor;
+               updateTitleHeight();
+       }
+
+       /**
+        * Sets the font size for displaying bases
+        * 
+        * @param size
+        *            Font size for base caption
+        */
+
+       public void setBaseFontSize(Float size) {
+               _conf._fontBasesGeneral = _conf._fontBasesGeneral.deriveFont(size);
+       }
+
+       /**
+        * Sets the font size for displaying base numbers
+        * 
+        * @param size
+        *            Font size for base numbers
+        */
+
+       public void setNumbersFontSize(Float size) {
+               _conf._numbersFont = _conf._numbersFont.deriveFont(size);
+       }
+
+       /**
+        * Sets the font style for displaying bases
+        * 
+        * @param style
+        *            An int that describes the new font style ("PLAIN","BOLD",
+        *            "BOLDITALIC", or "ITALIC")
+        */
+
+       public void setBaseFontStyle(int style) {
+               _conf._fontBasesGeneral = _conf._fontBasesGeneral.deriveFont(style);
+       }
+
+       private void updateTitleHeight() {
+               if (!getTitle().equals("")) {
+                       _titleHeight = (int) (_conf._titleFont.getSize() * 1.5);
+               } else {
+                       _titleHeight = 0;
+               }
+               if (Math.abs(this.getZoom() - 1) < .02) {
+                       _translation.y = (int) (-getTitleHeight() / 2.0);
+               }
+       }
+
+       /**
+        * Sets the panel's title, giving a short description of the RNA secondary
+        * structure.
+        * 
+        * @param title
+        *            The new title
+        */
+       public void setTitle(String title) {
+               _RNA.setName(title);
+               updateTitleHeight();
+       }
+
+       /**
+        * Sets the distance between consecutive base numbers. Please notice that :
+        * <ul>
+        * <li>The first and last base are always numbered</li>
+        * <li>The numbering is based on the base numbers, not on the indices. So
+        * base numbers may appear more frequently than expected if bases are
+        * skipped</li>
+        * <li>The periodicity is measured starting from 0. This means that for a
+        * period of 10 and bases numbered from 1 to 52, the base numbers
+        * [1,10,20,30,40,50,52] will be drawn.</li>
+        * </ul>
+        * 
+        * @param n
+        *            New numbering period
+        */
+       public void setNumPeriod(int n) {
+               _conf._numPeriod = n;
+       }
+
+       /**
+        * Returns the current numbering period. Please notice that :
+        * <ul>
+        * <li>The first and last base are always numbered</li>
+        * <li>The numbering is based on the base numbers, not on the indices. So
+        * base numbers may appear more frequently than expected if bases are
+        * skipped</li>
+        * <li>The periodicity is measured starting from 0. This means that for a
+        * period of 10 and bases numbered from 1 to 52, the base numbers
+        * [1,10,20,30,40,50,52] will be drawn.</li>
+        * </ul>
+        * 
+        * @return Current numbering period
+        */
+       public int getNumPeriod() {
+               return _conf._numPeriod;
+       }
+
+       private void setScaleFactor(double d) {
+               _scaleFactor = d;
+       }
+
+       private double getScaleFactor() {
+               return _scaleFactor;
+       }
+
+       private void setAutoFit(boolean fit) {
+               _conf._autoFit = fit;
+               repaint();
+       }
+
+       public void lockScrolling() {
+               setAutoFit(false);
+               setAutoCenter(false);
+       }
+
+       public void unlockScrolling() {
+               setAutoFit(true);
+               setAutoCenter(true);
+       }
+
+       private void drawStringOutline(VueVARNAGraphics g2D, String res, double x,
+                       double y, double margin) {
+               Dimension d = g2D.getStringDimension(res);
+               x -= (double) d.width / 2.0;
+               y += (double) d.height / 2.0;
+               g2D.setColor(Color.GRAY);
+               g2D.setSelectionStroke();
+               g2D.drawRect((x - margin), (y - d.height - margin),
+                               (d.width + 2.0 * margin), (d.height + 2.0 * margin));
+       }
+
+       private void drawSymbol(VueVARNAGraphics g2D, double posx, double posy,
+                       double normx, double normy, double radius, boolean isCIS,
+                       ModeleBP.Edge e) {
+               Color bck = g2D.getColor();
+               switch (e) {
+               case WC:
+                       if (isCIS) {
+                               g2D.setColor(bck);
+                               g2D.fillCircle((posx - (radius) / 2.0),
+                                               (posy - (radius) / 2.0), radius);
+                               g2D.drawCircle((posx - (radius) / 2.0),
+                                               (posy - (radius) / 2.0), radius);
+                       } else {
+                               g2D.setColor(Color.white);
+                               g2D.fillCircle(posx - (radius) / 2.0, (posy - (radius) / 2.0),
+                                               (radius));
+                               g2D.setColor(bck);
+                               g2D.drawCircle((posx - (radius) / 2.0),
+                                               (posy - (radius) / 2.0), (radius));
+                       }
+                       break;
+               case HOOGSTEEN: {
+                       GeneralPath p2 = new GeneralPath();
+                       radius /= 1.05;
+                       p2.moveTo((float) (posx - radius * normx / 2.0 - radius * normy
+                                       / 2.0), (float) (posy - radius * normy / 2.0 + radius
+                                       * normx / 2.0));
+                       p2.lineTo((float) (posx + radius * normx / 2.0 - radius * normy
+                                       / 2.0), (float) (posy + radius * normy / 2.0 + radius
+                                       * normx / 2.0));
+                       p2.lineTo((float) (posx + radius * normx / 2.0 + radius * normy
+                                       / 2.0), (float) (posy + radius * normy / 2.0 - radius
+                                       * normx / 2.0));
+                       p2.lineTo((float) (posx - radius * normx / 2.0 + radius * normy
+                                       / 2.0), (float) (posy - radius * normy / 2.0 - radius
+                                       * normx / 2.0));
+                       p2.closePath();
+
+                       if (isCIS) {
+                               g2D.setColor(bck);
+                               g2D.fill(p2);
+                               g2D.draw(p2);
+                       } else {
+                               g2D.setColor(Color.white);
+                               g2D.fill(p2);
+                               g2D.setColor(bck);
+                               g2D.draw(p2);
+                       }
+               }
+                       break;
+               case SUGAR: {
+                       double ix = radius * normx / 2.0;
+                       double iy = radius * normy / 2.0;
+                       double jx = radius * normy / 2.0;
+                       double jy = -radius * normx / 2.0;
+
+                       GeneralPath p2 = new GeneralPath();
+                       p2.moveTo((float) (posx - ix + jx), (float) (posy - iy + jy));
+                       p2.lineTo((float) (posx + ix + jx), (float) (posy + iy + jy));
+                       p2.lineTo((float) (posx - jx), (float) (posy - jy));
+                       p2.closePath();
+
+                       if (isCIS) {
+                               g2D.setColor(bck);
+                               g2D.fill(p2);
+                               g2D.draw(p2);
+                       } else {
+                               g2D.setColor(Color.white);
+                               g2D.fill(p2);
+                               g2D.setColor(bck);
+                               g2D.draw(p2);
+                       }
+               }
+                       break;
+               }
+               g2D.setColor(bck);
+       }
+
+       private void drawBasePairArc(VueVARNAGraphics g2D, int i, int j,
+                       Point2D.Double orig, Point2D.Double dest, double scaleFactor,
+                       ModeleBP style, double newRadius) {
+               double distance, coef;
+               if (j - i == 1)
+                       coef = getBPHeightIncrement() * 1.75;
+               else
+                       coef = getBPHeightIncrement();
+               distance = dest.x - orig.x;
+               switch (_conf._mainBPStyle) {
+               case LW: {
+                       double radiusCircle = ((RNA.BASE_PAIR_DISTANCE - _RNA.BASE_RADIUS) / 5.0)
+                                       * scaleFactor;
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((orig.x != dest.x) || (orig.y != dest.y)) {
+                                               g2D.drawArc((dest.x + orig.x) / 2., dest.y
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0,
+                                                               (distance - scaleFactor * _RNA.BASE_RADIUS
+                                                                               / 3.0), (distance * coef - scaleFactor
+                                                                               * _RNA.BASE_RADIUS / 3.0), 0, 180);
+                                               g2D.drawArc((dest.x + orig.x) / 2., dest.y
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0,
+                                                               (distance + scaleFactor * _RNA.BASE_RADIUS
+                                                                               / 3.0), (distance * coef + scaleFactor
+                                                                               * _RNA.BASE_RADIUS / 3.0), 0, 180);
+                                       }
+                               } else if (style.isCanonicalAU()) {
+                                       g2D.drawArc((dest.x + orig.x) / 2., dest.y - scaleFactor
+                                                       * _RNA.BASE_RADIUS / 2.0, (distance),
+                                                       (distance * coef), 0, 180);
+                               } else if (style.isWobbleUG()) {
+                                       Point2D.Double midtop = new Point2D.Double(
+                                                       (dest.x + orig.x) / 2., dest.y - distance * coef
+                                                                       / 2. - scaleFactor * _RNA.BASE_RADIUS / 2.0);
+                                       g2D.drawArc(midtop.x, dest.y - scaleFactor
+                                                       * _RNA.BASE_RADIUS / 2.0, (distance),
+                                                       (distance * coef), 0, 180);
+                                       drawSymbol(g2D, midtop.x, midtop.y, 1., 0., radiusCircle,
+                                                       false, ModeleBP.Edge.WC);
+                               } else {
+                                       Point2D.Double midtop = new Point2D.Double(
+                                                       (dest.x + orig.x) / 2., dest.y - distance * coef
+                                                                       / 2. - scaleFactor * _RNA.BASE_RADIUS / 2.0);
+                                       g2D.drawArc(midtop.x, dest.y - scaleFactor
+                                                       * _RNA.BASE_RADIUS / 2.0, (distance),
+                                                       (distance * coef), 0, 180);
+                                       drawSymbol(g2D, midtop.x, midtop.y, 1., 0., radiusCircle,
+                                                       style.isCIS(), style.getEdgePartner5());
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               Point2D.Double midtop = new Point2D.Double(
+                                               (dest.x + orig.x) / 2., dest.y - distance * coef / 2.
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0);
+                               g2D.drawArc(midtop.x, dest.y - scaleFactor * _RNA.BASE_RADIUS
+                                               / 2.0, (distance), (distance * coef), 0, 180);
+                               if (p1 == p2) {
+                                       drawSymbol(g2D, midtop.x, midtop.y, 1., 0., radiusCircle,
+                                                       false, style.getEdgePartner5());
+                               } else {
+                                       drawSymbol(g2D, midtop.x - scaleFactor * _RNA.BASE_RADIUS,
+                                                       midtop.y, 1., 0., radiusCircle, style.isCIS(), p1);
+                                       drawSymbol(g2D, midtop.x + scaleFactor * _RNA.BASE_RADIUS,
+                                                       midtop.y, -1., 0., radiusCircle, style.isCIS(), p2);
+                               }
+                       }
+               }
+                       break;
+               case LW_ALT: {
+                       double radiusCircle = ((RNA.BASE_PAIR_DISTANCE - _RNA.BASE_RADIUS) / 5.0)
+                                       * scaleFactor;
+                       double distFromBaseCenter = DISTANCE_FACT*scaleFactor;
+                       orig = new Point2D.Double(orig.x,orig.y-(distFromBaseCenter+newRadius));
+                       dest = new Point2D.Double(dest.x,dest.y-(distFromBaseCenter+newRadius));
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((orig.x != dest.x) || (orig.y != dest.y)) {
+                                               g2D.drawArc((dest.x + orig.x) / 2., dest.y
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0,
+                                                               (distance - scaleFactor * _RNA.BASE_RADIUS
+                                                                               / 3.0), (distance * coef - scaleFactor
+                                                                               * _RNA.BASE_RADIUS / 3.0), 0, 180);
+                                               g2D.drawArc((dest.x + orig.x) / 2., dest.y
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0,
+                                                               (distance + scaleFactor * _RNA.BASE_RADIUS
+                                                                               / 3.0), (distance * coef + scaleFactor
+                                                                               * _RNA.BASE_RADIUS / 3.0), 0, 180);
+                                       }
+                               } else if (style.isCanonicalAU()) {
+                                       g2D.drawArc((dest.x + orig.x) / 2., dest.y - scaleFactor
+                                                       * _RNA.BASE_RADIUS / 2.0, (distance),
+                                                       (distance * coef), 0, 180);
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               Point2D.Double midtop = new Point2D.Double(
+                                               (dest.x + orig.x) / 2., dest.y - distance * coef / 2.
+                                                               - scaleFactor * _RNA.BASE_RADIUS / 2.0);
+                               g2D.drawArc(midtop.x, dest.y - scaleFactor * _RNA.BASE_RADIUS
+                                               / 2.0, (distance), (distance * coef), 0, 180);
+                               drawSymbol(g2D, orig.x,
+                                                       orig.y-radiusCircle*.95, 1., 0., radiusCircle, style.isCIS(), p1);
+                               drawSymbol(g2D, dest.x,
+                                                       dest.y-radiusCircle*.95, -1., 0., radiusCircle, style.isCIS(), p2);
+                       }
+               }
+                       break;
+               default:
+                       g2D.drawArc((dest.x + orig.x) / 2., dest.y - scaleFactor
+                                       * _RNA.BASE_RADIUS / 2.0, (distance), (distance * coef), 0,
+                                       180);
+                       break;
+               }
+
+       }
+
+       public static double DISTANCE_FACT = 2.;
+
+       
+       private void drawBasePair(VueVARNAGraphics g2D, Point2D.Double orig,
+                       Point2D.Double dest, ModeleBP style, double newRadius,
+                       double scaleFactor) {
+
+               double dx = dest.x - orig.x;
+               double dy = dest.y - orig.y;
+               double dist = Math.sqrt((dest.x - orig.x) * (dest.x - orig.x)
+                               + (dest.y - orig.y) * (dest.y - orig.y));
+               dx /= dist;
+               dy /= dist;
+               double nx = -dy;
+               double ny = dx;
+               orig = new Point2D.Double(orig.x + newRadius * dx, orig.y + newRadius
+                               * dy);
+               dest = new Point2D.Double(dest.x - newRadius * dx, dest.y - newRadius
+                               * dy);
+               switch (_conf._mainBPStyle) {
+               case LW: {
+                       double radiusCircle = ((RNA.BASE_PAIR_DISTANCE - _RNA.BASE_RADIUS) / 5.0)
+                                       * scaleFactor;
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((orig.x != dest.x) || (orig.y != dest.y)) {
+                                               nx *= scaleFactor * _RNA.BASE_RADIUS / 4.0;
+                                               ny *= scaleFactor * _RNA.BASE_RADIUS / 4.0;
+                                               g2D.drawLine((orig.x + nx), (orig.y + ny),
+                                                               (dest.x + nx), (dest.y + ny));
+                                               g2D.drawLine((orig.x - nx), (orig.y - ny),
+                                                               (dest.x - nx), (dest.y - ny));
+                                       }
+                               } else if (style.isCanonicalAU()) {
+                                       g2D.drawLine(orig.x, orig.y, dest.x, dest.y);
+                               } else if (style.isWobbleUG()) {
+                                       double cx = (dest.x + orig.x) / 2.0;
+                                       double cy = (dest.y + orig.y) / 2.0;
+                                       g2D.drawLine(orig.x, orig.y, dest.x, dest.y);
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle, false,
+                                                       ModeleBP.Edge.WC);
+                               } else {
+                                       double cx = (dest.x + orig.x) / 2.0;
+                                       double cy = (dest.y + orig.y) / 2.0;
+                                       g2D.drawLine(orig.x, orig.y, dest.x, dest.y);
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), style.getEdgePartner5());
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               double cx = (dest.x + orig.x) / 2.0;
+                               double cy = (dest.y + orig.y) / 2.0;
+                               g2D.drawLine(orig.x, orig.y, dest.x, dest.y);
+                               if (p1 == p2) {
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1);
+
+                               } else {
+                                       double vdx = (dest.x - orig.x);
+                                       double vdy = (dest.y - orig.y);
+                                       vdx /= 6.0;
+                                       vdy /= 6.0;
+                                       drawSymbol(g2D, cx + vdx, cy + vdy, -nx, -ny, radiusCircle,
+                                                       style.isCIS(), p2);
+                                       drawSymbol(g2D, cx - vdx, cy - vdy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1);
+                               }
+                       }
+               }
+                       break;
+               case LW_ALT: {
+                       double radiusCircle = ((RNA.BASE_PAIR_DISTANCE - _RNA.BASE_RADIUS) / 5.0)
+                                       * scaleFactor;
+                       double distFromBaseCenter = DISTANCE_FACT*scaleFactor;
+                       Point2D.Double norig = new Point2D.Double(orig.x+(distFromBaseCenter+.5*newRadius)*dx,orig.y+(distFromBaseCenter+.5*newRadius)*dy);
+                       Point2D.Double ndest = new Point2D.Double(dest.x-(distFromBaseCenter+.5*newRadius)*dx,dest.y-(distFromBaseCenter+.5*newRadius)*dy);
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((norig.x != ndest.x) || (norig.y != ndest.y)) {
+                                               nx *= scaleFactor * _RNA.BASE_RADIUS / 4.0;
+                                               ny *= scaleFactor * _RNA.BASE_RADIUS / 4.0;
+                                               g2D.drawLine((norig.x + nx), (norig.y + ny),
+                                                               (ndest.x + nx), (ndest.y + ny));
+                                               g2D.drawLine((norig.x - nx), (norig.y - ny),
+                                                               (ndest.x - nx), (ndest.y - ny));
+                                       }
+                               } else if (style.isCanonicalAU()) {
+                                       g2D.drawLine(norig.x, norig.y, ndest.x, ndest.y);
+                               } else if (style.isWobbleUG()) {
+                                       double cx = (ndest.x + norig.x) / 2.0;
+                                       double cy = (ndest.y + norig.y) / 2.0;
+                                       g2D.drawLine(norig.x, norig.y, ndest.x, ndest.y);
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle, false,
+                                                       ModeleBP.Edge.WC);
+                               } else {
+                                       double cx = (ndest.x + norig.x) / 2.0;
+                                       double cy = (ndest.y + norig.y) / 2.0;
+                                       g2D.drawLine(norig.x, norig.y, ndest.x, ndest.y);
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), style.getEdgePartner5());
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               double cx = (ndest.x + norig.x) / 2.0;
+                               double cy = (ndest.y + norig.y) / 2.0;
+                               g2D.drawLine(norig.x, norig.y, ndest.x, ndest.y);
+                               if (p1 == p2) {
+                                       drawSymbol(g2D, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1);
+
+                               } else {
+                                       double fac = .4;
+                                       drawSymbol(g2D, ndest.x - fac*radiusCircle*dx, ndest.y - fac*radiusCircle*dy, -nx, -ny, radiusCircle,
+                                                       style.isCIS(), p2);
+                                       drawSymbol(g2D, norig.x + fac*radiusCircle*dx, norig.y + fac*radiusCircle*dy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1);
+                               }
+                       }
+               }
+                       break;
+               case SIMPLE:
+                       g2D.drawLine(orig.x, orig.y, dest.x, dest.y);
+                       break;
+               case RNAVIZ:
+                       double xcenter = (orig.x + dest.x) / 2.0;
+                       double ycenter = (orig.y + dest.y) / 2.0;
+                       double radius = Math.max(4.0 * scaleFactor, 1.0);
+                       g2D.fillCircle((xcenter - radius), (ycenter - radius),
+                                       (2.0 * radius));
+                       break;
+               case NONE:
+                       break;
+               }
+       }
+
+       private Color getHighlightedVersion(Color c1, Color c2) {
+               int r1 = c1.getRed();
+               int g1 = c1.getGreen();
+               int b1 = c1.getBlue();
+               int r2 = c2.getRed();
+               int g2 = c2.getGreen();
+               int b2 = c2.getBlue();
+               double val = _blink.getVal();
+               int nr = Math.max(0,
+                               Math.min((int) ((r1 * val + r2 * (1.0 - val))), 255));
+               int ng = Math.max(0,
+                               Math.min((int) ((g1 * val + g2 * (1.0 - val))), 255));
+               int nb = Math.max(0,
+                               Math.min((int) ((b1 * val + b2 * (1.0 - val))), 255));
+               return new Color(nr, ng, nb);
+       }
+
+       private Color highlightFilter(int index, Color initialColor, Color c1,
+                       Color c2, boolean localView) {
+               if (_selectedBases.contains(_RNA.getBaseAt(index)) && localView) {
+                       return getHighlightedVersion(c1, c2);
+               } else
+                       return initialColor;
+       }
+
+       public static Point2D.Double computeExcentricUnitVector(int i,
+                       Point2D.Double[] points, Point2D.Double[] centers) {
+               double dist = points[i].distance(centers[i]);
+               Point2D.Double byCenter = new Point2D.Double(
+                               (points[i].x - centers[i].x) / dist,
+                               (points[i].y - centers[i].y) / dist);
+               if ((i > 0) && (i < points.length - 1)) {
+                       Point2D.Double p0 = points[i - 1];
+                       Point2D.Double p1 = points[i];
+                       Point2D.Double p2 = points[i + 1];
+                       double dist1 = p2.distance(p1);
+                       Point2D.Double v1 = new Point2D.Double((p2.x - p1.x) / dist1,
+                                       (p2.y - p1.y) / dist1);
+                       Point2D.Double vn1 = new Point2D.Double(v1.y, -v1.x);
+                       double dist2 = p1.distance(p0);
+                       Point2D.Double v2 = new Point2D.Double((p1.x - p0.x) / dist2,
+                                       (p1.y - p0.y) / dist2);
+                       Point2D.Double vn2 = new Point2D.Double(v2.y, -v2.x);
+                       Point2D.Double vn = new Point2D.Double((vn1.x + vn2.x) / 2.0,
+                                       (vn1.y + vn2.y) / 2.0);
+                       double D = vn.distance(new Point2D.Double(0.0, 0.0));
+                       vn.x /= D;
+                       vn.y /= D;
+                       if (byCenter.x * vn.x + byCenter.y * vn.y < 0) {
+                               vn.x = -vn.x;
+                               vn.y = -vn.y;
+                       }
+                       return vn;
+               } 
+               else if (((i==0) || (i==points.length-1)) && (points.length>1)) {
+                       int a = (i==0)?0:points.length-1;
+                       int b = (i==0)?1:points.length-2;
+                       double D = points[a].distance(points[b]);
+                       return new Point2D.Double(
+                                       (points[a].x - points[b].x) / D,
+                                       (points[a].y - points[b].y) / D);
+               }
+               else {
+                       return byCenter;
+               }
+       }
+
+       private void drawBase(VueVARNAGraphics g2D, int i, Point2D.Double[] points,
+                       Point2D.Double[] centers, double newRadius, double _scaleFactor,
+                       boolean localView) {
+               Point2D.Double p = points[i];
+               ModeleBase mb = _RNA.get_listeBases().get(i);
+               g2D.setFont(_conf._fontBasesGeneral);
+               Color baseInnerColor = highlightFilter(i,
+                               _RNA.getBaseInnerColor(i, _conf), Color.white,
+                               _RNA.getBaseInnerColor(i, _conf), localView);
+               Color baseOuterColor = highlightFilter(i,
+                               _RNA.getBaseOuterColor(i, _conf),
+                               _RNA.getBaseOuterColor(i, _conf), Color.white, localView);
+               Color baseNameColor = highlightFilter(i,
+                               _RNA.getBaseNameColor(i, _conf),
+                               _RNA.getBaseNameColor(i, _conf), Color.white, localView);
+               if ( RNA.whiteLabelPreferrable(baseInnerColor))
+               {
+                       baseNameColor=Color.white;
+               }
+
+               if (mb instanceof ModeleBaseNucleotide) {
+                       ModeleBaseNucleotide mbn = (ModeleBaseNucleotide) mb;
+                       String res = mbn.getBase();
+                       if (_hoveredBase == mb && localView && isModifiable()) {
+                               g2D.setColor(_conf._hoverColor);
+                               g2D.fillCircle(p.getX() - 1.5 * newRadius, p.getY() - 1.5
+                                               * newRadius, 3.0 * newRadius);
+                               g2D.setColor(_conf._hoverColor.darker());
+                               g2D.drawCircle(p.getX() - 1.5 * newRadius, p.getY() - 1.5
+                                               * newRadius, 3.0 * newRadius);
+                               g2D.setPlainStroke();
+                       }
+                       if (_conf._fillBases) {
+                               // Filling inner circle
+                               g2D.setColor(baseInnerColor);
+                               g2D.fillCircle(p.getX() - newRadius, p.getY() - newRadius,
+                                               2.0 * newRadius);
+                       }
+
+                       if (_conf._drawOutlineBases) {
+                               // Drawing outline
+                               g2D.setColor(baseOuterColor);
+                               g2D.setStrokeThickness(_conf._baseThickness * _scaleFactor);
+                               g2D.drawCircle(p.getX() - newRadius, p.getY() - newRadius,
+                                               2.0 * newRadius);
+                       }
+                       // Drawing label
+                       g2D.setColor(baseNameColor);
+                       g2D.drawStringCentered(String.valueOf(res), p.getX(), p.getY());
+               } else if (mb instanceof ModeleBasesComparison) {
+
+                       ModeleBasesComparison mbc = (ModeleBasesComparison) mb;
+
+                       // On lui donne l'aspect voulue (on a un trait droit)
+                       g2D.setPlainStroke(); // On doit avoir un trait droit, sans arrondit
+                       g2D.setStrokeThickness(_conf._baseThickness * _scaleFactor);
+
+                       // On dessine l'étiquette, rectangle aux bords arrondies.
+                       g2D.setColor(baseInnerColor);
+                       g2D.fillRoundRect((p.getX() - 1.5 * newRadius),
+                                       (p.getY() - newRadius), (3.0 * newRadius),
+                                       (2.0 * newRadius), 10 * _scaleFactor, 10 * _scaleFactor);
+
+                       /* Dessin du rectangle exterieur (bords) */
+                       g2D.setColor(baseOuterColor);
+                       g2D.drawRoundRect((p.getX() - 1.5 * newRadius),
+                                       (p.getY() - newRadius), (3 * newRadius), (2 * newRadius),
+                                       10 * _scaleFactor, 10 * _scaleFactor);
+
+                       // On le dessine au centre de l'étiquette.
+                       g2D.drawLine((p.getX()), (p.getY() + newRadius) - 1, (p.getX()),
+                                       (p.getY() - newRadius) + 1);
+
+                       /* Dessin du nom de la base (A,C,G,U,etc...) */
+                       // On créer le texte des Ã©tiquettes
+                       String label1 = String.valueOf(mbc.getBase1());
+                       String label2 = String.valueOf(mbc.getBase2());
+
+                       // On leur donne une couleur
+                       g2D.setColor(getRNA().get_listeBases().get(i).getStyleBase()
+                                       .getBaseNameColor());
+
+                       // Et on les dessine.
+                       g2D.drawStringCentered(label1, p.getX() - (.75 * newRadius),
+                                       p.getY());
+                       g2D.drawStringCentered(label2, p.getX() + (.75 * newRadius),
+                                       p.getY());
+               }
+
+               // Drawing base number
+               if (_RNA.isNumberDrawn(mb, getNumPeriod())) {
+
+                       Point2D.Double vn = computeExcentricUnitVector(i, points, centers);
+                       g2D.setColor(mb.getStyleBase().getBaseNumberColor());
+                       g2D.setFont(_conf._numbersFont);
+                       double factorMin = Math.min(.5, _conf._distNumbers);
+                       double factorMax = Math.min(_conf._distNumbers - 1.5,
+                                       _conf._distNumbers);
+                       g2D.drawLine(p.x + vn.x * ((1 + factorMin) * newRadius), p.y + vn.y
+                                       * ((1 + factorMin) * newRadius), p.x + vn.x
+                                       * ((1 + factorMax) * newRadius), p.y + vn.y
+                                       * ((1 + factorMax) * newRadius));
+                       g2D.drawStringCentered(mb.getLabel(), p.x + vn.x
+                                       * ((1 + _conf._distNumbers) * newRadius), p.y + vn.y
+                                       * ((1 + _conf._distNumbers) * newRadius));
+
+               }
+       }
+
+       void drawChemProbAnnotation(VueVARNAGraphics g2D, ChemProbAnnotation cpa,
+                       Point2D.Double anchor, double scaleFactor) {
+               g2D.setColor(cpa.getColor());
+               g2D.setStrokeThickness(RNA.CHEM_PROB_ARROW_THICKNESS * scaleFactor
+                               * cpa.getIntensity());
+               g2D.setPlainStroke();
+               Point2D.Double v = cpa.getDirVector();
+               Point2D.Double vn = cpa.getNormalVector();
+               Point2D.Double base = new Point2D.Double(
+                               (anchor.x + _RNA.CHEM_PROB_DIST * scaleFactor * v.x),
+                               (anchor.y + _RNA.CHEM_PROB_DIST * scaleFactor * v.y));
+               Point2D.Double edge = new Point2D.Double(
+                               (base.x + _RNA.CHEM_PROB_BASE_LENGTH * cpa.getIntensity()
+                                               * scaleFactor * v.x),
+                               (base.y + _RNA.CHEM_PROB_BASE_LENGTH * cpa.getIntensity()
+                                               * scaleFactor * v.y));
+               switch (cpa.getType()) {
+               case ARROW: {
+                       Point2D.Double arrowTip1 = new Point2D.Double(
+                                       (base.x + cpa.getIntensity()
+                                                       * scaleFactor
+                                                       * (_RNA.CHEM_PROB_ARROW_WIDTH * vn.x + _RNA.CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.x)),
+                                       (base.y + cpa.getIntensity()
+                                                       * scaleFactor
+                                                       * (_RNA.CHEM_PROB_ARROW_WIDTH * vn.y + _RNA.CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.y)));
+                       Point2D.Double arrowTip2 = new Point2D.Double(
+                                       (base.x + cpa.getIntensity()
+                                                       * scaleFactor
+                                                       * (-_RNA.CHEM_PROB_ARROW_WIDTH * vn.x + _RNA.CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.x)),
+                                       (base.y + cpa.getIntensity()
+                                                       * scaleFactor
+                                                       * (-_RNA.CHEM_PROB_ARROW_WIDTH * vn.y + _RNA.CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.y)));
+                       g2D.drawLine(base.x, base.y, edge.x, edge.y);
+                       g2D.drawLine(base.x, base.y, arrowTip1.x, arrowTip1.y);
+                       g2D.drawLine(base.x, base.y, arrowTip2.x, arrowTip2.y);
+               }
+                       break;
+               case PIN: {
+                       Point2D.Double side1 = new Point2D.Double(
+                                       (edge.x - cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * v.x)),
+                                       (edge.y - cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * v.y)));
+                       Point2D.Double side2 = new Point2D.Double(
+                                       (edge.x - cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * vn.x)),
+                                       (edge.y - cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * vn.y)));
+                       Point2D.Double side3 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * v.x)),
+                                       (edge.y + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * v.y)));
+                       Point2D.Double side4 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * vn.x)),
+                                       (edge.y + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_PIN_SEMIDIAG * vn.y)));
+                       GeneralPath p2 = new GeneralPath();
+                       p2.moveTo((float) side1.x, (float) side1.y);
+                       p2.lineTo((float) side2.x, (float) side2.y);
+                       p2.lineTo((float) side3.x, (float) side3.y);
+                       p2.lineTo((float) side4.x, (float) side4.y);
+                       p2.closePath();
+                       g2D.fill(p2);
+                       g2D.drawLine(base.x, base.y, edge.x, edge.y);
+               }
+                       break;
+               case TRIANGLE: {
+                       Point2D.Double arrowTip1 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_TRIANGLE_WIDTH * vn.x)),
+                                       (edge.y + cpa.getIntensity() * scaleFactor
+                                                       * (_RNA.CHEM_PROB_TRIANGLE_WIDTH * vn.y)));
+                       Point2D.Double arrowTip2 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity() * scaleFactor
+                                                       * (-_RNA.CHEM_PROB_TRIANGLE_WIDTH * vn.x)),
+                                       (edge.y + cpa.getIntensity() * scaleFactor
+                                                       * (-_RNA.CHEM_PROB_TRIANGLE_WIDTH * vn.y)));
+                       GeneralPath p2 = new GeneralPath();
+                       p2.moveTo((float) base.x, (float) base.y);
+                       p2.lineTo((float) arrowTip1.x, (float) arrowTip1.y);
+                       p2.lineTo((float) arrowTip2.x, (float) arrowTip2.y);
+                       p2.closePath();
+                       g2D.fill(p2);
+               }
+                       break;
+               case DOT: {
+                       Double radius = scaleFactor * _RNA.CHEM_PROB_DOT_RADIUS
+                                       * cpa.getIntensity();
+                       Point2D.Double center = new Point2D.Double((base.x + radius * v.x),
+                                       (base.y + radius * v.y));
+                       g2D.fillCircle((center.x - radius), (center.y - radius),
+                                       (2 * radius));
+               }
+                       break;
+               }
+       }
+
+       Point2D.Double buildCaptionPosition(ModeleBase mb, double scaleFactor,
+                       double heightEstimate) {
+               double radius = 2.0;
+               if (_RNA.isNumberDrawn(mb, getNumPeriod())) {
+                       radius += _conf._distNumbers;
+               }
+               Point2D.Double center = mb.getCenter();
+               Point2D.Double p = mb.getCoords();
+               double realDistance = _RNA.BASE_RADIUS * radius + heightEstimate;
+               return new Point2D.Double(center.getX() + (p.getX() - center.getX())
+                               * ((p.distance(center) + realDistance) / p.distance(center)),
+                               center.getY()
+                                               + (p.getY() - center.getY())
+                                               * ((p.distance(center) + realDistance) / p
+                                                               .distance(center)));
+       }
+
+       private void renderAnnotations(VueVARNAGraphics g2D, double offX,
+                       double offY, double rnaBBoxX, double rnaBBoxY, double scaleFactor) {
+               for (TextAnnotation textAnnotation : _RNA.getAnnotations()) {
+                       g2D.setColor(textAnnotation.getColor());
+                       g2D.setFont(textAnnotation
+                                       .getFont()
+                                       .deriveFont(
+                                                       (float) (2.0 * textAnnotation.getFont().getSize() * scaleFactor)));
+                       Point2D.Double position = textAnnotation.getCenterPosition();
+                       if (textAnnotation.getType() == TextAnnotation.AnchorType.BASE) {
+                               ModeleBase mb = (ModeleBase) textAnnotation.getAncrage();
+                               double fontHeight = Math.ceil(textAnnotation.getFont()
+                                               .getSize());
+                               position = buildCaptionPosition(mb, scaleFactor, fontHeight);
+                       }
+                       position = transformCoord(position, offX, offY, rnaBBoxX, rnaBBoxY,
+                                       scaleFactor);
+                       g2D.drawStringCentered(textAnnotation.getTexte(), position.x,
+                                       position.y);
+                       if ((_selectedAnnotation == textAnnotation)
+                                       && (_highlightAnnotation)) {
+                               drawStringOutline(g2D, textAnnotation.getTexte(), position.x,
+                                               position.y, 5);
+                       }
+               }
+               for (ChemProbAnnotation cpa : _RNA.getChemProbAnnotations()) {
+                       Point2D.Double anchor = transformCoord(cpa.getAnchorPosition(),
+                                       offX, offY, rnaBBoxX, rnaBBoxY, scaleFactor);
+                       drawChemProbAnnotation(g2D, cpa, anchor, scaleFactor);
+               }
+
+       }
+
+       public Rectangle2D.Double getExtendedRNABBox() {
+               // We get the logical bounding box
+               Rectangle2D.Double rnabbox = _RNA.getBBox();
+               rnabbox.y -= _conf._distNumbers * _RNA.BASE_RADIUS;
+               rnabbox.height += 2.0 * _conf._distNumbers * _RNA.BASE_RADIUS;
+               rnabbox.x -= _conf._distNumbers * _RNA.BASE_RADIUS;
+               rnabbox.width += 2.0 * _conf._distNumbers * _RNA.BASE_RADIUS;
+               if (_RNA.hasVirtualLoops()) {
+                       rnabbox.y -= RNA.VIRTUAL_LOOP_RADIUS;
+                       rnabbox.height += 2.0 * RNA.VIRTUAL_LOOP_RADIUS;
+                       rnabbox.x -= RNA.VIRTUAL_LOOP_RADIUS;
+                       rnabbox.width += 2.0 * RNA.VIRTUAL_LOOP_RADIUS;
+               }
+               return rnabbox;
+       }
+
+       public void drawBackbone(VueVARNAGraphics g2D, Point2D.Double[] newCoords,
+                       double newRadius, double _scaleFactor) {
+               // Drawing backbone
+               if (getDrawBackbone()) {
+                       g2D.setStrokeThickness(1.5 * _scaleFactor);
+                       g2D.setColor(_conf._backboneColor);
+                       
+                       ModeleBackbone bck = _RNA.getBackbone();
+
+
+                       for (int i = 1; i < _RNA.get_listeBases().size(); i++) {
+                               Point2D.Double p1 = newCoords[i - 1];
+                               Point2D.Double p2 = newCoords[i];
+                               double dist = p1.distance(p2);
+                               int a = _RNA.getBaseAt(i - 1).getElementStructure();
+                               int b = _RNA.getBaseAt(i).getElementStructure();
+                               boolean consecutivePair = (a == i) && (b == i - 1);
+
+                               if ((dist > 0)) {
+                                       Point2D.Double vbp = new Point2D.Double();
+                                       vbp.x = (p2.x - p1.x) / dist;
+                                       vbp.y = (p2.y - p1.y) / dist;
+                                       
+                                       BackboneType bt = bck.getTypeBefore(i);
+                                       if (bt!=BackboneType.DISCONTINUOUS_TYPE)
+                                       {
+                                               if (bt==BackboneType.MISSING_PART_TYPE) {
+                                                       g2D.setSelectionStroke();
+                                               } else {
+                                                       g2D.setPlainStroke();
+                                               }
+                                               g2D.setColor(bck.getColorBefore(i, _conf._backboneColor));
+                                               
+                                               if (consecutivePair
+                                                               && (_RNA.getDrawMode() != RNA.DRAW_MODE_LINEAR)
+                                                               && (_RNA.getDrawMode() != RNA.DRAW_MODE_CIRCULAR)) {
+                                                       int dir = 0;
+                                                       if (i + 1 < newCoords.length) {
+                                                               dir = (_RNA.testDirectionality(i - 1, i, i + 1) ? -1
+                                                                               : 1);
+                                                       } else if (i - 2 >= 0) {
+                                                               dir = (_RNA.testDirectionality(i - 2, i - 1, i) ? -1
+                                                                               : 1);
+                                                       }
+                                                       Point2D.Double vn = new Point2D.Double(dir * vbp.y,
+                                                                       -dir * vbp.x);
+                                                       Point2D.Double centerSeg = new Point2D.Double(
+                                                                       (p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0);
+                                                       double distp1CenterSeq = p1.distance(centerSeg);
+                                                       double centerDist = Math
+                                                                       .sqrt((RNA.VIRTUAL_LOOP_RADIUS * _scaleFactor
+                                                                                       * RNA.VIRTUAL_LOOP_RADIUS * _scaleFactor)
+                                                                                       - distp1CenterSeq * distp1CenterSeq);
+                                                       Point2D.Double centerLoop = new Point2D.Double(
+                                                                       centerSeg.x + centerDist * vn.x, centerSeg.y
+                                                                                       + centerDist * vn.y);
+                                                       double radius = centerLoop.distance(p1);
+                                                       double a1 = 360.
+                                                                       * (Math.atan2(-(p1.y - centerLoop.y),
+                                                                                       (p1.x - centerLoop.x)))
+                                                                       / (2. * Math.PI);
+                                                       double a2 = 360.
+                                                                       * (Math.atan2(-(p2.y - centerLoop.y),
+                                                                                       (p2.x - centerLoop.x)))
+                                                                       / (2. * Math.PI);
+                                                       double angle = (a2 - a1);
+                                                       if (-dir * angle < 0) {
+                                                               angle += -dir * 360.;
+                                                       }
+                                                       // if (angle<0.) angle += 360.;
+                                                       // angle = -dir*(360-dir*angle);
+                                                       g2D.drawArc(centerLoop.x + .8 * newRadius * vn.x,
+                                                                       centerLoop.y + .8 * newRadius * vn.y,
+                                                                       2 * radius, 2 * radius, a1, angle);
+                                               } else {
+                                                       g2D.drawLine((newCoords[i - 1].x + newRadius * vbp.x),
+                                                                       (newCoords[i - 1].y + newRadius * vbp.y),
+                                                                       (newCoords[i].x - newRadius * vbp.x),
+                                                                       (newCoords[i].y - newRadius * vbp.y));
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public Point2D.Double logicToPanel(Point2D.Double logicPoint) {
+               return new Point2D.Double(_offX
+                               + (getScaleFactor() * (logicPoint.x - _offsetRNA.x)), _offY
+                               + (getScaleFactor() * (logicPoint.y - _offsetRNA.y)));
+
+       }
+
+       public Rectangle2D.Double renderRNA(VueVARNAGraphics g2D,
+                       Rectangle2D.Double bbox) {
+               return renderRNA(g2D, bbox, false, true);
+       }
+
+       private double computeScaleFactor(Rectangle2D.Double bbox,
+                       boolean localView, boolean autoCenter) {
+               Rectangle2D.Double rnabbox = getExtendedRNABBox();
+               double scaleFactor = Math.min((double) bbox.width
+                               / (double) rnabbox.width, (double) bbox.height
+                               / (double) rnabbox.height);
+
+               // Use it to get an estimate of the font size for numbers ...
+               float newFontSize = Math.max(1,
+                               (int) ((1.7 * _RNA.BASE_RADIUS) * scaleFactor));
+               // ... and increase bounding box accordingly
+               rnabbox.y -= newFontSize;
+               rnabbox.height += newFontSize;
+               if (_conf._drawColorMap) {
+                       rnabbox.height += getColorMapHeight();
+               }
+               rnabbox.x -= newFontSize;
+               rnabbox.width += newFontSize;
+
+               // Now, compute the final scaling factor and corresponding font size
+               scaleFactor = Math.min((double) bbox.width / (double) rnabbox.width,
+                               (double) bbox.height / (double) rnabbox.height);
+               if (localView) {
+                       if (_conf._autoFit)
+                               setScaleFactor(scaleFactor);
+                       scaleFactor = getScaleFactor();
+               }
+               return scaleFactor;
+       }
+
+       public synchronized Rectangle2D.Double renderRNA(VueVARNAGraphics g2D,
+                       Rectangle2D.Double bbox, boolean localView, boolean autoCenter) {
+               Rectangle2D.Double rnaMultiBox = new Rectangle2D.Double(0, 0, 1, 1);
+               double scaleFactor = computeScaleFactor(bbox, localView, autoCenter);
+               float newFontSize = Math.max(1,
+                               (int) ((1.7 * _RNA.BASE_RADIUS) * scaleFactor));
+               double newRadius = Math.max(1.0, (scaleFactor * _RNA.BASE_RADIUS));
+               setBaseFontSize(newFontSize);
+               setNumbersFontSize(newFontSize);
+               double offX = bbox.x;
+               double offY = bbox.y;
+               Rectangle2D.Double rnabbox = getExtendedRNABBox();
+
+               if (_RNA.getSize() != 0) {
+
+                       Point2D.Double offsetRNA = new Point2D.Double(rnabbox.x, rnabbox.y);
+
+                       if (autoCenter) {
+                               offX = (bbox.x + (bbox.width - Math.round(rnabbox.width
+                                               * scaleFactor)) / 2.0);
+                               offY = (bbox.y + (bbox.height - Math.round(rnabbox.height
+                                               * scaleFactor)) / 2.0);
+                               if (localView) {
+                                       _offX = offX;
+                                       _offY = offY;
+                                       _offsetPanel = new Point2D.Double(_offX, _offY);
+                                       _offsetRNA = new Point2D.Double(rnabbox.x, rnabbox.y);
+                               }
+                       }
+
+                       if (localView) {
+                               offX = _offX;
+                               offY = _offY;
+                               offsetRNA = _offsetRNA;
+                       }
+
+                       // Re-scaling once and for all
+                       Point2D.Double[] newCoords = new Point2D.Double[_RNA
+                                       .get_listeBases().size()];
+                       Point2D.Double[] newCenters = new Point2D.Double[_RNA
+                                       .get_listeBases().size()];
+                       for (int i = 0; i < _RNA.get_listeBases().size(); i++) {
+                               ModeleBase mb = _RNA.getBaseAt(i);
+                               newCoords[i] = new Point2D.Double(offX
+                                               + (scaleFactor * (mb.getCoords().x - offsetRNA.x)),
+                                               offY + (scaleFactor * (mb.getCoords().y - offsetRNA.y)));
+
+                               Point2D.Double centerBck = _RNA.getCenter(i);
+                               // si la base est dans un angle entre une boucle et une helice
+                               if (_RNA.get_drawMode() == RNA.DRAW_MODE_NAVIEW
+                                               || _RNA.get_drawMode() == RNA.DRAW_MODE_RADIATE) {
+                                       if ((mb.getElementStructure() != -1)
+                                                       && i < _RNA.get_listeBases().size() - 1 && i > 1) {
+                                               ModeleBase b1 = _RNA.get_listeBases().get(i - 1);
+                                               ModeleBase b2 = _RNA.get_listeBases().get(i + 1);
+                                               int j1 = b1.getElementStructure();
+                                               int j2 = b2.getElementStructure();
+                                               if ((j1 == -1) ^ (j2 == -1)) {
+                                                       // alors la position du nombre associé doit etre
+                                                       Point2D.Double a1 = b1.getCoords();
+                                                       Point2D.Double a2 = b2.getCoords();
+                                                       Point2D.Double c1 = b1.getCenter();
+                                                       Point2D.Double c2 = b2.getCenter();
+
+                                                       centerBck.x = mb.getCoords().x + (c1.x - a1.x)
+                                                                       / c1.distance(a1) + (c2.x - a2.x)
+                                                                       / c2.distance(a2);
+                                                       centerBck.y = mb.getCoords().y + (c1.y - a1.y)
+                                                                       / c1.distance(a1) + (c2.y - a2.y)
+                                                                       / c2.distance(a2);
+                                               }
+                                       }
+                               }
+                               newCenters[i] = new Point2D.Double(offX
+                                               + (scaleFactor * (centerBck.x - offsetRNA.x)), offY
+                                               + (scaleFactor * (centerBck.y - offsetRNA.y)));
+                       }
+                       // Keep track of coordinates for mouse interactions
+                       if (localView) {
+                               _realCoords = newCoords;
+                               _realCenters = newCenters;
+                       }
+
+                       g2D.setStrokeThickness(1.5 * scaleFactor);
+                       g2D.setPlainStroke();
+                       g2D.setFont(_conf._fontBasesGeneral);
+
+                       // Drawing region highlights Annotation
+                       drawRegionHighlightsAnnotation(g2D, _realCoords, _realCenters,
+                                       scaleFactor);
+                       drawBackbone(g2D, newCoords, newRadius, scaleFactor);
+
+                       // Drawing base-pairs
+                       // pour chaque base
+                       for (int i = 0; i < _RNA.get_listeBases().size(); i++) {
+                               int j = _RNA.get_listeBases().get(i).getElementStructure();
+                               // si c'est une parenthese ouvrante (premiere base du
+                               // couple)
+                               if (j > i) {
+                                       ModeleBP msbp = _RNA.get_listeBases().get(i).getStyleBP();
+                                       // System.err.println(msbp);
+                                       if (msbp.isCanonical() || _conf._drawnNonCanonicalBP) {
+                                               if (_RNA.get_drawMode() == RNA.DRAW_MODE_LINEAR) {
+                                                       g2D.setStrokeThickness(_RNA.getBasePairThickness(
+                                                                       msbp, _conf)
+                                                                       * 2.0
+                                                                       * scaleFactor
+                                                                       * _conf._bpThickness);
+                                               } else {
+                                                       g2D.setStrokeThickness(_RNA.getBasePairThickness(
+                                                                       msbp, _conf) * 1.5 * scaleFactor);
+                                               }
+                                               g2D.setColor(_RNA.getBasePairColor(msbp, _conf));
+
+                                               if (_RNA.get_drawMode() == RNA.DRAW_MODE_LINEAR) {
+                                                       drawBasePairArc(g2D, i, j, newCoords[i],
+                                                                       newCoords[j], scaleFactor, msbp, newRadius);
+                                               } else {
+                                                       drawBasePair(g2D, newCoords[i], newCoords[j], msbp,
+                                                                       newRadius, scaleFactor);
+                                               }
+                                       }
+                               }
+                       }
+
+                       // Liaisons additionelles (non planaires)
+                       if (_conf._drawnNonPlanarBP) {
+                               ArrayList<ModeleBP> bpaux = _RNA.getStructureAux();
+                               for (int k = 0; k < bpaux.size(); k++) {
+                                       ModeleBP msbp = bpaux.get(k);
+                                       if (msbp.isCanonical() || _conf._drawnNonCanonicalBP) {
+                                               int i = msbp.getPartner5().getIndex();
+                                               int j = msbp.getPartner3().getIndex();
+                                               if (_RNA.get_drawMode() == RNA.DRAW_MODE_LINEAR) {
+                                                       g2D.setStrokeThickness(_RNA.getBasePairThickness(
+                                                                       msbp, _conf)
+                                                                       * 2.5
+                                                                       * scaleFactor
+                                                                       * _conf._bpThickness);
+                                                       g2D.setPlainStroke();
+                                               } else {
+                                                       g2D.setStrokeThickness(_RNA.getBasePairThickness(
+                                                                       msbp, _conf) * 1.5 * scaleFactor);
+                                                       g2D.setPlainStroke();
+                                               }
+
+                                               g2D.setColor(_RNA.getBasePairColor(msbp, _conf));
+                                               if (j > i) {
+                                                       if (_RNA.get_drawMode() == RNA.DRAW_MODE_LINEAR) {
+                                                               drawBasePairArc(g2D, i, j, newCoords[i],
+                                                                               newCoords[j], scaleFactor, msbp, newRadius);
+                                                       } else {
+                                                               drawBasePair(g2D, newCoords[i], newCoords[j],
+                                                                               msbp, newRadius, scaleFactor);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       // Drawing bases
+                       g2D.setPlainStroke();
+                       for (int i = 0; i < Math.min(_RNA.get_listeBases().size(),
+                                       newCoords.length); i++) {
+                               drawBase(g2D, i, newCoords, newCenters, newRadius, scaleFactor,
+                                               localView);
+                       }
+
+                       rnaMultiBox = new Rectangle2D.Double(offX, offY,
+                                       (scaleFactor * rnabbox.width) - 1,
+                                       (scaleFactor * rnabbox.height) - 1);
+
+                       if (localView) {
+                               // Drawing bbox
+                               if (_debug || _drawBBox) {
+                                       g2D.setColor(Color.RED);
+                                       g2D.setSelectionStroke();
+                                       g2D.drawRect(rnaMultiBox.x, rnaMultiBox.y,
+                                                       rnaMultiBox.width, rnaMultiBox.height);
+                               }
+
+                               // Draw color map
+                               if (_conf._drawColorMap) {
+                                       drawColorMap(g2D, scaleFactor, rnabbox);
+                               }
+
+                               if (_debug || _drawBBox) {
+                                       g2D.setColor(Color.GRAY);
+                                       g2D.setSelectionStroke();
+                                       g2D.drawRect(0, 0, getWidth() - 1, getHeight()
+                                                       - getTitleHeight() - 1);
+                               }
+                       }
+                       // Draw annotations
+                       renderAnnotations(g2D, offX, offY, offsetRNA.x, offsetRNA.y,
+                                       scaleFactor);
+                       // Draw additional debug shape
+                       if (_RNA._debugShape != null) {
+                               Color c = new Color(255, 0, 0, 50);
+                               g2D.setColor(c);
+                               AffineTransform at = new AffineTransform();
+                               at.translate(offX - scaleFactor * rnabbox.x, offY - scaleFactor
+                                               * rnabbox.y);
+                               at.scale(scaleFactor, scaleFactor);
+                               Shape s = at.createTransformedShape(_RNA._debugShape);
+                               if (s instanceof GeneralPath) {
+                                       g2D.fill((GeneralPath) s);
+                               }
+                       }
+               } else {
+                       g2D.setColor(VARNAConfig.DEFAULT_MESSAGE_COLOR);
+                       g2D.setFont(VARNAConfig.DEFAULT_MESSAGE_FONT);
+                       rnaMultiBox = new Rectangle2D.Double(0,0,10,10);
+                       g2D.drawStringCentered("No RNA here", bbox.getCenterX(),bbox.getCenterY());
+               }
+               return rnaMultiBox;
+       }
+
+       public void centerViewOn(double x, double y) {
+               Rectangle2D.Double r = _RNA.getBBox();
+               _target = new Point2D.Double(x, y);
+               Point2D.Double q = logicToPanel(_target);
+               Point p = new Point((int) (-q.x), (int) (-q.y));
+               setTranslation(p);
+               repaint();
+       }
+
+       Point2D.Double _target = new Point2D.Double(0, 0);
+       Point2D.Double _target2 = new Point2D.Double(0, 0);
+
+       public ModeleBase getBaseAt(Point2D.Double po) {
+               ModeleBase mb = null;
+               Point2D.Double p = panelToLogicPoint(po);
+               double dist = Double.MAX_VALUE;
+               for (ModeleBase tmp : _RNA.get_listeBases()) {
+                       double ndist = tmp.getCoords().distance(p);
+                       if (dist > ndist) {
+                               mb = tmp;
+                               dist = ndist;
+                       }
+               }
+               return mb;
+       }
+
+       public void setColorMapValues(Double[] values) {
+               _RNA.setColorMapValues(values, _conf._cm, true);
+               _conf._drawColorMap = true;
+               repaint();
+       }
+
+       public void setColorMapMaxValue(double d) {
+               _conf._cm.setMaxValue(d);
+       }
+
+       public void setColorMapMinValue(double d) {
+               _conf._cm.setMinValue(d);
+       }
+
+       public ModeleColorMap getColorMap() {
+               return _conf._cm;
+       }
+
+       public void setColorMap(ModeleColorMap cm) {
+               //_RNA.adaptColorMapToValues(cm);
+               _conf._cm = cm;
+               repaint();
+       }
+
+       public void setColorMapCaption(String caption) {
+               _conf._colorMapCaption = caption;
+               repaint();
+       }
+
+       public String getColorMapCaption() {
+               return _conf._colorMapCaption;
+       }
+
+       public void drawColorMap(boolean draw) {
+               _conf._drawColorMap = draw;
+       }
+
+       private double getColorMapHeight() {
+               double result = VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE
+                               + _conf._colorMapHeight;
+               if (!_conf._colorMapCaption.equals(""))
+                       result += VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE;
+               return result;
+       }
+
+       private void drawColorMap(VueVARNAGraphics g2D, double scaleFactor,
+                       Rectangle2D.Double rnabbox) {
+               double v1 = _conf._cm.getMinValue();
+               double v2 = _conf._cm.getMaxValue();
+               double x, y;
+               g2D.setPlainStroke();
+
+               double xSpaceAvail = 0;
+               double ySpaceAvail = Math
+                               .min((getHeight() - rnabbox.height * scaleFactor - getTitleHeight()) / 2.0,
+                                               scaleFactor
+                                                               * (_conf._colorMapHeight + VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE));
+               if ((int) ySpaceAvail == 0) {
+                       xSpaceAvail = Math.min(
+                                       (getWidth() - rnabbox.width * scaleFactor) / 2, scaleFactor
+                                                       * (_conf._colorMapWidth)
+                                                       + VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH);
+               }
+               double xBase = (xSpaceAvail + _offX + scaleFactor
+                               * (rnabbox.width - _conf._colorMapWidth - _conf._colorMapXOffset));
+               double hcaption = VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE;
+               double yBase = (ySpaceAvail + _offY + scaleFactor
+                               * (rnabbox.height - _conf._colorMapHeight
+                                               - _conf._colorMapYOffset - hcaption));
+
+               for (int i = 0; i < _conf._colorMapWidth; i++) {
+                       double ratio = (((double) i) / ((double) _conf._colorMapWidth));
+                       double val = v1 + (v2 - v1) * ratio;
+                       g2D.setColor(_conf._cm.getColorForValue(val));
+                       x = (xBase + scaleFactor * i);
+                       y = yBase;
+                       g2D.fillRect(x, y, scaleFactor
+                                       * VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH,
+                                       (scaleFactor * _conf._colorMapHeight));
+               }
+               g2D.setColor(VARNAConfig.DEFAULT_COLOR_MAP_OUTLINE);
+               g2D.drawRect(xBase, yBase,
+                               (VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH - 1 + scaleFactor
+                                               * _conf._colorMapWidth),
+                               ((scaleFactor * _conf._colorMapHeight)));
+               g2D.setFont(getFont()
+                               .deriveFont(
+                                               (float) (scaleFactor * VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE)));
+               g2D.setColor(VARNAConfig.DEFAULT_COLOR_MAP_FONT_COLOR);
+               NumberFormat nf = NumberFormat.getInstance();
+               nf.setMaximumFractionDigits(2);
+               nf.setMinimumFractionDigits(0);
+               g2D.drawStringCentered(nf.format(_conf._cm.getMinValue()), xBase, 
+                               yBase
+                               + scaleFactor * (_conf._colorMapHeight+(VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7)));
+               g2D.drawStringCentered(nf.format(_conf._cm.getMaxValue()), xBase
+                               + VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH + scaleFactor
+                               * _conf._colorMapWidth, 
+                               yBase
+                               + scaleFactor * (_conf._colorMapHeight+(VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7)));
+               if (!_conf._colorMapCaption.equals(""))
+                       g2D.drawStringCentered(
+                                       "" + _conf._colorMapCaption,
+                                       xBase + scaleFactor * _conf._colorMapWidth / 2.0,
+                                       yBase
+                                                       + scaleFactor
+                                                       * (VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7 + _conf._colorMapHeight));
+
+       }
+
+       public Point2D.Double panelToLogicPoint(Point2D.Double p) {
+               return new Point2D.Double(
+                               ((p.x - getOffsetPanel().x) / getScaleFactor())
+                                               + getRNAOffset().x,
+                               ((p.y - getOffsetPanel().y) / getScaleFactor())
+                                               + getRNAOffset().y);
+       }
+
+       public Point2D.Double transformCoord(Point2D.Double coordDebut,
+                       double offX, double offY, double rnaBBoxX, double rnaBBoxY,
+                       double scaleFactor) {
+               return new Point2D.Double(offX
+                               + (scaleFactor * (coordDebut.x - rnaBBoxX)), offY
+                               + (scaleFactor * (coordDebut.y - rnaBBoxY)));
+       }
+
+       public void eraseSequence() {
+               _RNA.eraseSequence();
+       }
+
+       public Point2D.Double transformCoord(Point2D.Double coordDebut) {
+               Rectangle2D.Double rnabbox = getExtendedRNABBox();
+               return new Point2D.Double(_offX
+                               + (getScaleFactor() * (coordDebut.x - rnabbox.x)), _offY
+                               + (getScaleFactor() * (coordDebut.y - rnabbox.y)));
+       }
+
+       public void paintComponent(Graphics g) {
+               paintComponent(g, false);
+       }
+
+       public void paintComponent(Graphics g, boolean transparentBackground) {
+               if (_premierAffichage) {
+                       // _border = new Dimension(0, 0);
+                       _translation.x = 0;
+                       _translation.y = (int) (-getTitleHeight() / 2.0);
+                       _popup.buildPopupMenu();
+                       this.add(_popup);
+                       _premierAffichage = false;
+               }
+
+               Graphics2D g2 = (Graphics2D) g;
+               Stroke dflt = g2.getStroke();
+               VueVARNAGraphics g2D = new SwingGraphics(g2);
+               g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                               RenderingHints.VALUE_ANTIALIAS_ON);
+               this.removeAll();
+               super.paintComponent(g2);
+               renderComponent(g2D, transparentBackground, getScaleFactor());
+               if (isFocusOwner()) {
+                       g2.setStroke(new BasicStroke(1.5f));
+                       g2.setColor(Color.decode("#C0C0C0"));
+                       g2.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
+
+               }
+               g2.setStroke(dflt);
+               /*
+                * PSExport e = new PSExport(); SecStrProducerGraphics export = new
+                * SecStrProducerGraphics(e); renderRNA(export, getExtendedRNABBox());
+                * try { export.saveToDisk("./out.ps"); } catch
+                * (ExceptionWritingForbidden e1) { e1.printStackTrace(); }
+                */
+       }
+
+       /**
+        * Draws current RNA structure in a given Graphics "device".
+        * 
+        * @param g2D
+        *            A graphical device
+        * @param transparentBackground
+        *            Whether the background should be transparent, or drawn.
+        */
+       public synchronized void renderComponent(VueVARNAGraphics g2D,
+                       boolean transparentBackground, double scaleFactor) {
+
+               updateTitleHeight();
+
+               if (_debug || _drawBorder) {
+                       g2D.setColor(Color.BLACK);
+                       g2D.setPlainStroke();
+                       g2D.drawRect(getLeftOffset(), getTopOffset(), getInnerWidth(),
+                                       getInnerHeight());
+
+               }
+
+               
+               if (!transparentBackground) {
+                       super.setBackground(_conf._backgroundColor);
+               } else {
+                       super.setBackground(new Color(0, 0, 0, 120));
+               }
+
+               if (getMinimumSize().height <= getSize().height
+                               && getMinimumSize().width <= getSize().width) {
+                       // Draw Title
+                       if (!getTitle().equals("")) {
+                               g2D.setColor(_conf._titleColor);
+                               g2D.setFont(_conf._titleFont);
+                               g2D.drawStringCentered(getTitle(), this.getWidth() / 2,
+                                               this.getHeight() - getTitleHeight() / 2.0);
+                       }
+                       // Draw RNA
+                       renderRNA(g2D, getClip(), true, _conf._autoCenter);
+               }
+               if (_selectionRectangle != null) {
+                       g2D.setColor(Color.BLACK);
+                       g2D.setSelectionStroke();
+                       g2D.drawRect(_selectionRectangle.x, _selectionRectangle.y,
+                                       _selectionRectangle.width, _selectionRectangle.height);
+               }
+               if ((_linkOrigin != null) && (_linkDestination != null)) {
+                       g2D.setColor(_conf._bondColor);
+                       g2D.setPlainStroke();
+                       g2D.setStrokeThickness(3.0 * scaleFactor);
+                       Point2D.Double linkOrigin = (_linkOrigin);
+                       Point2D.Double linkDestination = (_linkDestination);
+                       g2D.drawLine(linkOrigin.x, linkOrigin.y, linkDestination.x,
+                                       linkDestination.y);
+                       for (int i : getSelection().getIndices())
+                               drawBase(g2D, i, _realCoords, _realCenters, scaleFactor
+                                               * _RNA.BASE_RADIUS, scaleFactor, true);
+               }
+
+               if (_debug) {
+                       g2D.setStrokeThickness(3.0 * scaleFactor);
+                       g2D.setColor(Color.black);
+                       Point2D.Double t = this.logicToPanel(_target);
+                       g2D.drawLine(t.x - 3, t.y - 3, t.x + 3, t.y + 3);
+                       g2D.drawLine(t.x - 3, t.y + 3, t.x + 3, t.y - 3);
+                       g2D.setColor(Color.red);
+                       t = this.logicToPanel(_target2);
+                       g2D.drawLine(t.x - 3, t.y - 3, t.x + 3, t.y + 3);
+                       g2D.drawLine(t.x - 3, t.y + 3, t.x + 3, t.y - 3);
+               }
+       }
+
+       public void drawRegionHighlightsAnnotation(VueVARNAGraphics g2D,
+                       Point2D.Double[] realCoords, Point2D.Double[] realCenters,
+                       double scaleFactor) {
+               g2D.setStrokeThickness(2.0 * scaleFactor);
+               g2D.setPlainStroke();
+               for (HighlightRegionAnnotation r : _RNA.getHighlightRegion()) {
+                       GeneralPath s = r.getShape(realCoords, realCenters, scaleFactor);
+                       g2D.setColor(r.getFillColor());
+                       g2D.fill(s);
+                       g2D.setColor(r.getOutlineColor());
+                       g2D.draw(s);
+               }
+       }
+
+       private Rectangle2D.Double getClip() {
+               return new Rectangle2D.Double(getLeftOffset(), getTopOffset(),
+                               this.getInnerWidth(), this.getInnerHeight());
+       }
+
+       public Rectangle2D.Double getViewClip() {
+               return new Rectangle2D.Double(this.getLeftOffset(),
+                               this.getTopOffset(), this.getInnerWidth(),
+                               this.getInnerHeight());
+       }
+
+       /**
+        * Returns the color used to draw backbone bounds.
+        * 
+        * @return The color used to draw backbone bounds
+        */
+       public Color getBackboneColor() {
+               return _conf._backboneColor;
+       }
+
+       /**
+        * Sets the color to be used for drawing backbone interactions.
+        * 
+        * @param backbone_color
+        *            The new color for the backbone bounds
+        */
+       public void setBackboneColor(Color backbone_color) {
+               _conf._backboneColor = backbone_color;
+       }
+
+       /**
+        * Returns the color used to display hydrogen bonds (base pairings)
+        * 
+        * @return The color of hydrogen bonds
+        */
+       public Color getBondColor() {
+               return _conf._bondColor;
+       }
+
+       /**
+        * Returns the title of this panel
+        * 
+        * @return The title
+        */
+       public String getTitle() {
+               return _RNA.getName();
+       }
+
+       /**
+        * Sets the new color to be used for hydrogen bonds (base pairings)
+        * 
+        * @param bond_color
+        *            The new color for hydrogen bonds
+        */
+       public void setDefaultBPColor(Color bond_color) {
+               _conf._bondColor = bond_color;
+       }
+
+       /**
+        * Sets the size of the border, i.e. the empty space between the end of the
+        * drawing area and the actual border.
+        * 
+        * @param b
+        *            The new border size
+        */
+       public void setBorderSize(Dimension b) {
+               _border = b;
+       }
+
+       /**
+        * Returns the size of the border, i.e. the empty space between the end of
+        * the drawing area
+        * 
+        * @return The border size
+        */
+       public Dimension getBorderSize() {
+               return _border;
+       }
+
+       /**
+        * Sets the RNA to be displayed within this Panel. This method does not use
+        * a drawing algorithm to reassigns base coordinates, rather assuming that
+        * the RNA was previously drawn.
+        * 
+        * @param r
+        *            An already drawn RNA to display in this panel
+        */
+       public synchronized void showRNA(RNA r) {
+               fireUINewStructure(r);
+               _RNA = r;
+       }
+
+       /**
+        * Sets the RNA secondary structure to be drawn in this panel, using the
+        * default layout algorithm. In addition to the raw nucleotides sequence,
+        * the secondary structure is given in the so-called "Dot-bracket notation"
+        * (DBN) format. This format is a well-parenthesized word over the alphabet
+        * '(',')','.'.<br/>
+        * Ex:<code>((((((((....))))..(((((...))).))))))</code><br />
+        * Returns <code>true</code> if the sequence/structure couple could be
+        * parsed into a valid secondary structure, and <code>false</code>
+        * otherwise.
+        * 
+        * @param seq
+        *            The raw nucleotides sequence
+        * @param str
+        *            The secondary structure
+        * @throws ExceptionNonEqualLength
+        */
+       public void drawRNA(String seq, String str) throws ExceptionNonEqualLength {
+               drawRNA(seq, str, _RNA.get_drawMode());
+       }
+
+       /**
+        * Sets the RNA secondary structure to be drawn in this panel, using a given
+        * layout algorithm.
+        * 
+        * @param r
+        *            The new secondary structure
+        * @param drawMode
+        *            The drawing algorithm
+        */
+       public void drawRNA(RNA r, int drawMode) {
+               r.setDrawMode(drawMode);
+               drawRNA(r);
+       }
+
+       /**
+        * Redraws the current RNA. This reassigns base coordinates to their default
+        * value using the current drawing algorithm.
+        */
+
+       public void drawRNA() {
+               try {
+                       _RNA.drawRNA(_RNA.get_drawMode(), _conf);
+               } catch (ExceptionNAViewAlgorithm e) {
+                       errorDialog(e);
+                       e.printStackTrace();
+               }
+               repaint();
+       }
+
+       /**
+        * Sets the RNA secondary structure to be drawn in this panel, using the
+        * current drawing algorithm.
+        * 
+        * @param r
+        *            The new secondary structure
+        */
+       public void drawRNA(RNA r) {
+               if (r != null) {
+                       _RNA = r;
+                       drawRNA();
+               }
+       }
+
+       /**
+        * Sets the RNA secondary structure to be drawn in this panel, using a given
+        * layout algorithm. In addition to the raw nucleotides sequence, the
+        * secondary structure is given in the so-called "Dot-bracket notation"
+        * (DBN) format. This format is a well-parenthesized word over the alphabet
+        * '(',')','.'.<br/>
+        * Ex: <code>((((((((....))))..(((((...))).))))))</code><br />
+        * Returns <code>true</code> if the sequence/structure couple could be
+        * parsed into a valid secondary structure, and <code>false</code>
+        * otherwise.
+        * 
+        * @param seq
+        *            The raw nucleotides sequence
+        * @param str
+        *            The secondary structure
+        * @param drawMode
+        *            The drawing algorithm
+        * @throws ExceptionNonEqualLength
+        */
+       public void drawRNA(String seq, String str, int drawMode)
+                       throws ExceptionNonEqualLength {
+               _RNA.setDrawMode(drawMode);
+               try {
+                       _RNA.setRNA(seq, str);
+                       drawRNA();
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       errorDialog(e);
+               } catch (ExceptionFileFormatOrSyntax e1) {
+                       errorDialog(e1);
+               }
+       }
+
+       public void drawRNA(Reader r, int drawMode) throws ExceptionNonEqualLength,
+                       ExceptionFileFormatOrSyntax {
+               _RNA.setDrawMode(drawMode);
+               Collection<RNA> rnas = RNAFactory.loadSecStr(r);
+               if (rnas.isEmpty()) {
+                       throw new ExceptionFileFormatOrSyntax(
+                                       "No RNA could be parsed from that source.");
+               }
+               _RNA = rnas.iterator().next();
+               drawRNA();
+       }
+
+       /**
+        * Draws a secondary structure of RNA using the default drawing algorithm
+        * and displays it, using an interpolated transition between the previous
+        * one and the new one. Extra bases, resulting from a size difference
+        * between the two successive RNAs, are assumed to initiate from the middle
+        * of the sequence. In other words, both prefixes and suffixes of the RNAs
+        * are assumed to match, and what remains is an insertion.
+        * 
+        * @param seq
+        *            Sequence
+        * @param str
+        *            Structure in dot bracket notation
+        * @throws ExceptionNonEqualLength
+        *             If len(seq)!=len(str)
+        */
+       public void drawRNAInterpolated(String seq, String str)
+                       throws ExceptionNonEqualLength {
+               drawRNAInterpolated(seq, str, _RNA.get_drawMode());
+       }
+
+       /**
+        * Draws a secondary structure of RNA using a given algorithm and displays
+        * it, using an interpolated transition between the previous one and the new
+        * one. Extra bases, resulting from a size difference between the two
+        * successive RNAs, are assumed to initiate from the middle of the sequence.
+        * In other words, both prefixes and suffixes of the RNAs are assumed to
+        * match, and what remains is an insertion.
+        * 
+        * @param seq
+        *            Sequence
+        * @param str
+        *            Structure in dot bracket notation
+        * @param drawMode
+        *            The drawing algorithm to be used for the initial placement
+        * @throws ExceptionNonEqualLength
+        *             If len(seq)!=len(str)
+        */
+       public void drawRNAInterpolated(String seq, String str, int drawMode) {
+               drawRNAInterpolated(seq, str, drawMode,
+                               Mapping.DefaultOutermostMapping(_RNA.get_listeBases().size(),
+                                               str.length()));
+       }
+
+       /**
+        * Draws a secondary structure of RNA using the default drawing algorithm
+        * and displays it, using an interpolated transition between the previous
+        * one and the new one. Here, a mapping between those bases of the new
+        * structure and the previous one is explicitly provided.
+        * 
+        * @param seq
+        *            Sequence
+        * @param str
+        *            Structure in dot bracket notation
+        * @param m
+        *            A mapping between the currently rendered structure and its
+        *            successor (seq,str)
+        * @throws ExceptionNonEqualLength
+        *             If len(seq)!=len(str)
+        */
+       public void drawRNAInterpolated(String seq, String str, Mapping m) {
+               drawRNAInterpolated(seq, str, _RNA.get_drawMode(), m);
+       }
+
+       /**
+        * Draws a secondary structure of RNA using a given drawing algorithm and
+        * displays it, using an interpolated transition between the previous one
+        * and the new one. Here, a mapping between those bases of the new structure
+        * and the previous one is provided.
+        * 
+        * @param seq
+        *            Sequence
+        * @param str
+        *            Structure in dot bracket notation
+        * @param drawMode
+        *            The drawing algorithm to be used for the initial placement
+        * @param m
+        *            A mapping between the currently rendered structure and its
+        *            successor (seq,str)
+        */
+       public void drawRNAInterpolated(String seq, String str, int drawMode,
+                       Mapping m) {
+               RNA target = new RNA();
+               try {
+                       target.setRNA(seq, str);
+                       drawRNAInterpolated(target, drawMode, m);
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       errorDialog(e);
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       errorDialog(e);
+               }
+       }
+
+       /**
+        * Draws a secondary structure of RNA using the default drawing algorithm
+        * and displays it, using an interpolated transition between the previous
+        * one and the new one. Here, a mapping between those bases of the new
+        * structure and the previous one is explicitly provided.
+        * 
+        * @param target
+        *            Secondary structure
+        */
+       public void drawRNAInterpolated(RNA target) {
+               drawRNAInterpolated(target, target.get_drawMode(),
+                               Mapping.DefaultOutermostMapping(_RNA.get_listeBases().size(),
+                                               target.getSize()));
+       }
+
+       /**
+        * Draws a secondary structure of RNA using the default drawing algorithm
+        * and displays it, using an interpolated transition between the previous
+        * one and the new one. Here, a mapping between those bases of the new
+        * structure and the previous one is explicitly provided.
+        * 
+        * @param target
+        *            Secondary structure
+        * @param m
+        *            A mapping between the currently rendered structure and its
+        *            successor (seq,str)
+        */
+       public void drawRNAInterpolated(RNA target, Mapping m) {
+               drawRNAInterpolated(target, target.get_drawMode(), m);
+       }
+
+       /**
+        * Draws a secondary structure of RNA using a given drawing algorithm and
+        * displays it, using an interpolated transition between the previous one
+        * and the new one. Here, a mapping between those bases of the new structure
+        * and the previous one is provided.
+        * 
+        * @param target
+        *            Secondary structure of RNA
+        * @param drawMode
+        *            The drawing algorithm to be used for the initial placement
+        * @param m
+        *            A mapping between the currently rendered structure and its
+        *            successor (seq,str)
+        */
+       public void drawRNAInterpolated(RNA target, int drawMode, Mapping m) {
+               try {
+                       target.drawRNA(drawMode, _conf);
+                       _conf._drawColorMap = false;
+                       _interpolator.addTarget(target, m);
+               } catch (ExceptionNAViewAlgorithm e) {
+                       errorDialog(e);
+                       e.printStackTrace();
+               }
+       }
+
+       /**
+        * Returns the current algorithm used for drawing the structure
+        * 
+        * @return The current drawing algorithm
+        */
+       public int getDrawMode() {
+               return this._RNA.getDrawMode();
+       }
+
+       public void showRNA(RNA t, VARNAConfig cfg) {
+               showRNA(t);
+               if (cfg != null) {
+                       this.setConfig(cfg);
+               }
+               repaint();
+       }
+
+       /**
+        * Checks whether an interpolated transition bewteen two RNAs is occurring.
+        * 
+        * @return True if an interpolated transition is occurring, false otherwise
+        */
+
+       public boolean isInterpolationInProgress() {
+               if (_interpolator == null) {
+                       return false;
+               } else
+                       return _interpolator.isInterpolationInProgress();
+       }
+
+       /**
+        * Simply displays (does not redraw) a secondary structure , using an
+        * interpolated transition between the previous one and the new one. A
+        * default mapping between those bases of the new structure and the previous
+        * one is used.
+        * 
+        * @param target
+        *            Secondary structure of RNA
+        */
+       public void showRNAInterpolated(RNA target) {
+               showRNAInterpolated(target, Mapping.DefaultOutermostMapping(_RNA
+                               .get_listeBases().size(), target.getSize()));
+       }
+
+       /**
+        * Simply displays (does not redraw) a secondary structure , using an
+        * interpolated transition between the previous one and the new one. Here, a
+        * mapping between bases of the new structure and the previous one is given.
+        * 
+        * @param target
+        *            Secondary structure of RNA
+        * @param m
+        *            A mapping between the currently rendered structure and its
+        *            successor (seq,str)
+        * @throws ExceptionNonEqualLength
+        *             If len(seq)!=len(str)
+        */
+       public void showRNAInterpolated(RNA target, Mapping m) {
+               showRNAInterpolated(target, null, m);
+       }
+
+       public void showRNAInterpolated(RNA target, VARNAConfig cfg, Mapping m) {
+               _interpolator.addTarget(target, cfg, m);
+       }
+
+       /**
+        * When comparison mode is ON, sets the two RNA secondary structure to be
+        * drawn in this panel, using a given layout algorithm. In addition to the
+        * raw nucleotides sequence, the secondary structure is given in the
+        * so-called "Dot-bracket notation" (DBN) format. This format is a
+        * well-parenthesized word over the alphabet '(',')','.'.<br/>
+        * Ex: <code>((((((((....))))..(((((...))).))))))</code><br />
+        * 
+        * @param firstSeq
+        *            The first RNA raw nucleotides sequence
+        * @param firstStruct
+        *            The first RNA secondary structure
+        * @param secondSeq
+        *            The second RNA raw nucleotides sequence
+        * @param secondStruct
+        *            The second RNA secondary structure
+        * @param drawMode
+        *            The drawing algorithm
+        */
+       public void drawRNA(String firstSeq, String firstStruct, String secondSeq,
+                       String secondStruct, int drawMode) {
+               _RNA.setDrawMode(drawMode);
+               /**
+                * Checking the sequences and structures validities...
+                */
+
+               // This is a comparison, so the two RNA alignment past in parameters
+               // must
+               // have the same sequence and structure length.
+               if (firstSeq.length() == secondSeq.length()
+                               && firstStruct.length() == secondStruct.length()) {
+                       // First RNA
+                       if (firstSeq.length() != firstStruct.length()) {
+                               if (_conf._showWarnings) {
+                                       emitWarning("First sequence length " + firstSeq.length()
+                                                       + " differs from that of it's secondary structure "
+                                                       + firstStruct.length()
+                                                       + ". \nAdapting first sequence length ...");
+                               }
+                               if (firstSeq.length() < firstStruct.length()) {
+                                       while (firstSeq.length() < firstStruct.length()) {
+                                               firstSeq += " ";
+                                       }
+                               } else {
+                                       firstSeq = firstSeq.substring(0, firstStruct.length());
+                               }
+                       }
+
+                       // Second RNA
+                       if (secondSeq.length() != secondStruct.length()) {
+                               if (_conf._showWarnings) {
+                                       emitWarning("Second sequence length " + secondSeq.length()
+                                                       + " differs from that of it's secondary structure "
+                                                       + secondStruct.length()
+                                                       + ". \nAdapting second sequence length ...");
+                               }
+                               if (secondSeq.length() < secondStruct.length()) {
+                                       while (secondSeq.length() < secondStruct.length()) {
+                                               secondSeq += " ";
+                                       }
+                               } else {
+                                       secondSeq = secondSeq.substring(0, secondStruct.length());
+                               }
+                       }
+
+                       int RNALength = firstSeq.length();
+                       String string_superStruct = new String("");
+                       String string_superSeq = new String("");
+                       /**
+                        * In this array, we'll have for each indexes of each characters of
+                        * the final super-structure, the RNA number which is own it.
+                        */
+                       ArrayList<Integer> array_rnaOwn = new ArrayList<Integer>();
+
+                       /**
+                        * Generating super-structure sequences and structures...
+                        */
+
+                       firstStruct = firstStruct.replace('-', '.');
+                       secondStruct = secondStruct.replace('-', '.');
+                       // First of all, we make the structure
+                       for (int i = 0; i < RNALength; i++) {
+                               // If both characters are the same, so it'll be in the super
+                               // structure
+                               if (firstStruct.charAt(i) == secondStruct.charAt(i)) {
+                                       string_superStruct = string_superStruct
+                                                       + firstStruct.charAt(i);
+                                       array_rnaOwn.add(0);
+                               }
+                               // Else if one of the characters is an opening parenthese, so
+                               // it'll be an opening parenthese in the super structure
+                               else if (firstStruct.charAt(i) == '('
+                                               || secondStruct.charAt(i) == '(') {
+                                       string_superStruct = string_superStruct + '(';
+                                       array_rnaOwn.add((firstStruct.charAt(i) == '(') ? 1 : 2);
+                               }
+                               // Else if one of the characters is a closing parenthese, so
+                               // it'll be a closing parenthese in the super structure
+                               else if (firstStruct.charAt(i) == ')'
+                                               || secondStruct.charAt(i) == ')') {
+                                       string_superStruct = string_superStruct + ')';
+                                       array_rnaOwn.add((firstStruct.charAt(i) == ')') ? 1 : 2);
+                               } else {
+                                       string_superStruct = string_superStruct + '.';
+                                       array_rnaOwn.add(-1);
+                               }
+                       }
+
+                       // Next, we make the sequence taking the characters at the same
+                       // index in the first and second sequence
+                       for (int i = 0; i < RNALength; i++) {
+                               string_superSeq = string_superSeq + firstSeq.charAt(i)
+                                               + secondSeq.charAt(i);
+                       }
+
+                       // Now, we need to create the super-structure RNA with the owning
+                       // bases array
+                       // in order to color bases outer depending on the owning statement
+                       // of each bases.
+                       if (!string_superSeq.equals("") && !string_superStruct.equals("")) {
+                               try {
+                                       _RNA.setRNA(string_superSeq, string_superStruct,
+                                                       array_rnaOwn);
+                               } catch (ExceptionUnmatchedClosingParentheses e) {
+                                       errorDialog(e);
+                               } catch (ExceptionFileFormatOrSyntax e) {
+                                       errorDialog(e);
+                               }
+                       } else {
+                               emitWarning("ERROR : The super-structure is NULL.");
+                       }
+
+                       switch (_RNA.get_drawMode()) {
+                       case RNA.DRAW_MODE_RADIATE:
+                               _RNA.drawRNARadiate(_conf);
+                               break;
+                       case RNA.DRAW_MODE_CIRCULAR:
+                               _RNA.drawRNACircle(_conf);
+                               break;
+                       case RNA.DRAW_MODE_LINEAR:
+                               _RNA.drawRNALine(_conf);
+                               break;
+                       case RNA.DRAW_MODE_NAVIEW:
+                               try {
+                                       _RNA.drawRNANAView(_conf);
+                               } catch (ExceptionNAViewAlgorithm e) {
+                                       errorDialog(e);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+
+               }
+       }
+
+       /**
+        * Returns the currently selected base index, obtained through a mouse-left
+        * click
+        * 
+        * @return Selected base
+        * 
+        *         public int getSelectedBaseIndex() { return _selectedBase; }
+        * 
+        *         /** Returns the currently selected base, obtained through a
+        *         mouse-left click
+        * 
+        * @return Selected base
+        * 
+        *         public ModeleBase getSelectedBase() { return
+        *         _RNA.get_listeBases().get(_selectedBase); }
+        * 
+        *         /** Sets the selected base index
+        * 
+        * @param base
+        *            New selected base index
+        * 
+        *            public void setSelectedBase(int base) { _selectedBase = base;
+        *            }
+        */
+
+       /**
+        * Returns the coordinates of the currently displayed RNA
+        * 
+        * @return Coordinates array
+        */
+       public Point2D.Double[] getRealCoords() {
+               return _realCoords;
+       }
+
+       /**
+        * Sets the coordinates of the currently displayed RNA
+        * 
+        * @param coords
+        *            New coordinates
+        */
+       public void setRealCoords(Point2D.Double[] coords) {
+               _realCoords = coords;
+       }
+
+       /**
+        * Returns the popup menu used for user mouse iteractions
+        * 
+        * @return Popup menu
+        */
+       public VueMenu getPopup() {
+               return _popup;
+       }
+
+       /**
+        * Sets the color used to display hydrogen bonds (base pairings)
+        * 
+        * @param bond_color
+        *            The color of hydrogen bonds
+        */
+       public void setBondColor(Color bond_color) {
+               _conf._bondColor = bond_color;
+       }
+
+       /**
+        * Returns the color used to draw the title
+        * 
+        * @return The color used to draw the title
+        */
+       public Color getTitleColor() {
+               return _conf._titleColor;
+       }
+
+       /**
+        * Sets the color used to draw the title
+        * 
+        * @param title_color
+        *            The new color used to draw the title
+        */
+       public void setTitleColor(Color title_color) {
+               _conf._titleColor = title_color;
+       }
+
+       /**
+        * Returns the height taken by the title
+        * 
+        * @return The height taken by the title
+        */
+       private int getTitleHeight() {
+               return _titleHeight;
+       }
+
+       /**
+        * Sets the height taken by the title
+        * 
+        * @param title_height
+        *            The height taken by the title
+        */
+       @SuppressWarnings("unused")
+       private void setTitleHeight(int title_height) {
+               _titleHeight = title_height;
+       }
+
+       /**
+        * Returns the current state of auto centering mode.
+        * 
+        * @return True if autocentered, false otherwise
+        */
+       public boolean isAutoCentered() {
+               return _conf._autoCenter;
+       }
+
+       /**
+        * Sets the current state of auto centering mode.
+        * 
+        * @param center
+        *            New auto-centered state
+        */
+       public void setAutoCenter(boolean center) {
+               _conf._autoCenter = center;
+       }
+
+       /**
+        * Returns the font currently used for rendering the title.
+        * 
+        * @return Current title font
+        */
+       public Font getTitleFont() {
+               return _conf._titleFont;
+       }
+
+       /**
+        * Sets the font used for rendering the title.
+        * 
+        * @param font
+        *            New title font
+        */
+       public void setTitleFont(Font font) {
+               _conf._titleFont = font;
+               updateTitleHeight();
+       }
+
+       /**
+        * For the LINE_MODE drawing algorithm, sets the base pair height increment,
+        * i.e. the vertical distance between two nested arcs.
+        * 
+        * @return The current base pair increment
+        */
+       public double getBPHeightIncrement() {
+               return _RNA._bpHeightIncrement;
+       }
+
+       /**
+        * Sets the base pair height increment, i.e. the vertical distance between
+        * two arcs to be used in LINE_MODE.
+        * 
+        * @param inc
+        *            New height increment
+        */
+       public void setBPHeightIncrement(double inc) {
+               _RNA._bpHeightIncrement = inc;
+       }
+
+       /**
+        * Returns the shifting of the origin of the Panel in zoom mode
+        * 
+        * @return The logical coordinate of the top-left panel point
+        */
+       public Point2D.Double getOffsetPanel() {
+               return _offsetPanel;
+       }
+
+       /**
+        * Returns the vector bringing the logical coordinate of left-top-most point
+        * in the panel to the left-top-most point of the RNA.
+        * 
+        * @return The logical coordinate of the top-left panel point
+        */
+       private Point2D.Double getRNAOffset() {
+               return _offsetRNA;
+       }
+
+       /**
+        * Returns this panel's UI menu
+        * 
+        * @return Applet's UI popupmenu
+        */
+       public VueMenu getPopupMenu() {
+               return _popup;
+       }
+
+       /**
+        * Returns the atomic zoom factor step, or increment.
+        * 
+        * @return Atomic zoom factor increment
+        */
+       public double getZoomIncrement() {
+               return _conf._zoomAmount;
+       }
+
+       /**
+        * Sets the atomic zoom factor step, or increment.
+        * 
+        * @param amount
+        *            Atomic zoom factor increment
+        */
+       public void setZoomIncrement(Object amount) {
+               setZoomIncrement(Float.valueOf(amount.toString()));
+       }
+
+       /**
+        * Sets the atomic zoom factor step, or increment.
+        * 
+        * @param amount
+        *            Atomic zoom factor increment
+        */
+       public void setZoomIncrement(double amount) {
+               _conf._zoomAmount = amount;
+       }
+
+       /**
+        * Returns the current zoom factor
+        * 
+        * @return Current zoom factor
+        */
+       public double getZoom() {
+               return _conf._zoom;
+       }
+
+       /**
+        * Sets the current zoom factor
+        * 
+        * @param _zoom
+        *            New zoom factor
+        */
+       public void setZoom(Object _zoom) {
+               double d = Float.valueOf(_zoom.toString());
+               if (_conf._zoom != d) {
+                       _conf._zoom = d;
+                       fireZoomLevelChanged(d);
+               }
+       }
+
+       /**
+        * Returns the translation used for zooming in and out
+        * 
+        * @return A vector describing the translation
+        */
+       public Point getTranslation() {
+               return _translation;
+       }
+
+       /**
+        * Sets the translation used for zooming in and out
+        * 
+        * @param trans
+        *            A vector describing the new translation
+        */
+       public void setTranslation(Point trans) {
+               _translation = trans;
+               checkTranslation();
+               fireTranslationChanged();
+       }
+
+       /**
+        * Returns the current RNA model
+        * 
+        * @return Current RNA model
+        */
+       public RNA getRNA() {
+               return _RNA;
+       }
+
+       /**
+        * Checks whether the drawn RNA is too large to be displayed, allowing for
+        * shifting mouse interactions.
+        * 
+        * @return true if the RNA is too large to be displayed, false otherwise
+        */
+       public boolean isOutOfFrame() {
+               return _horsCadre;
+       }
+
+       /**
+        * Pops up an error Dialog displaying an exception in an human-readable way.
+        * 
+        * @param error
+        *            The exception to display within the Dialog
+        */
+       public void errorDialog(Exception error) {
+               errorDialog(error, this);
+       }
+
+       /**
+        * Pops up an error Dialog displaying an exception in an human-readable way
+        * if errors are set to be displayed.
+        * 
+        * @see #setErrorsOn(boolean)
+        * @param error
+        *            The exception to display within the Dialog
+        * @param c
+        *            Parent component for the dialog box
+        */
+       public void errorDialog(Exception error, Component c) {
+               if (isErrorsOn()) {
+                       JOptionPane.showMessageDialog(c, error.getMessage(), "VARNA Error",
+                                       JOptionPane.ERROR_MESSAGE);
+               }
+       }
+
+       /**
+        * Pops up an error Dialog displaying an exception in an human-readable way.
+        * 
+        * @param error
+        *            The exception to display within the Dialog
+        * @param c
+        *            Parent component for the dialog box
+        */
+       public static void errorDialogStatic(Exception error, Component c) {
+               if (c != null) {
+                       JOptionPane.showMessageDialog(c, error.getMessage(),
+                                       "VARNA Critical Error", JOptionPane.ERROR_MESSAGE);
+               } else {
+                       System.err.println("Error: " + error.getMessage());
+               }
+       }
+
+       /**
+        * Displays a warning message through a modal dialog if warnings are set to
+        * be displayed.
+        * 
+        * @see #setShowWarnings(boolean)
+        * @param warning
+        *            A message expliciting the warning
+        */
+       public void emitWarning(String warning) {
+               if (_conf._showWarnings)
+                       JOptionPane.showMessageDialog(this, warning, "VARNA Warning",
+                                       JOptionPane.WARNING_MESSAGE);
+       }
+
+       public static void emitWarningStatic(Exception e, Component c) {
+               emitWarningStatic(e.getMessage(), c);
+       }
+
+       public static void emitWarningStatic(String warning, Component c) {
+               if (c != null) {
+                       JOptionPane.showMessageDialog(c, warning, "VARNA Warning",
+                                       JOptionPane.WARNING_MESSAGE);
+               } else {
+                       System.err.println("Error: " + warning);
+               }
+       }
+
+       /**
+        * Toggles modifications on and off
+        * 
+        * @param modifiable
+        *            Modification status
+        */
+       public void setModifiable(boolean modifiable) {
+               _conf._modifiable = modifiable;
+       }
+
+       /**
+        * Returns current modification status
+        * 
+        * @return current modification status
+        */
+       public boolean isModifiable() {
+               return _conf._modifiable;
+       }
+
+       /**
+        * Resets the visual aspects (Zoom factor, shift) for the Panel.
+        */
+       public void reset() {
+               this.setBorderSize(new Dimension(0, 0));
+               this.setTranslation(new Point(0, (int) (-getTitleHeight() / 2.0)));
+               this.setZoom(VARNAConfig.DEFAULT_ZOOM);
+               this.setZoomIncrement(VARNAConfig.DEFAULT_AMOUNT);
+       }
+
+       /**
+        * Returns the color used to draw non-standard bases
+        * 
+        * @return The color used to draw non-standard bases
+        */
+       public Color getNonStandardBasesColor() {
+               return _conf._specialBasesColor;
+       }
+
+       /**
+        * Sets the color used to draw non-standard bases
+        * 
+        * @param basesColor
+        *            The color used to draw non-standard bases
+        */
+       public void setNonStandardBasesColor(Color basesColor) {
+               _conf._specialBasesColor = basesColor;
+       }
+
+       /**
+        * Checks if the current translation doesn't "kick" the whole RNA out of the
+        * panel, and corrects the situation if necessary.
+        */
+       public void checkTranslation() {
+               // verification pour un zoom < 1
+               if (this.getZoom() <= 1) {
+                       // verification sortie gauche
+                       if (this.getTranslation().x < -(int) ((this.getWidth() - this
+                                       .getInnerWidth()) / 2.0)) {
+                               this.setTranslation(new Point(-(int) ((this.getWidth() - this
+                                               .getInnerWidth()) / 2.0), this.getTranslation().y));
+                       }
+                       // verification sortie droite
+                       if (this.getTranslation().x > (int) ((this.getWidth() - this
+                                       .getInnerWidth()) / 2.0)) {
+                               this.setTranslation(new Point((int) ((this.getWidth() - this
+                                               .getInnerWidth()) / 2.0), this.getTranslation().y));
+                       }
+                       // verification sortie bas
+                       if (this.getTranslation().y > (int) ((this.getHeight()
+                                       - getTitleHeight() * 2 - this.getInnerHeight()) / 2.0)) {
+                               this.setTranslation(new Point(this.getTranslation().x,
+                                               (int) ((this.getHeight() - getTitleHeight() * 2 - this
+                                                               .getInnerHeight()) / 2.0)));
+                       }
+                       // verification sortie haut
+                       if (this.getTranslation().y < -(int) ((this.getHeight() - this
+                                       .getInnerHeight()) / 2.0)) {
+                               this.setTranslation(new Point(
+                                               this.getTranslation().x,
+                                               -(int) ((this.getHeight() - this.getInnerHeight()) / 2.0)));
+                       }
+               } else {
+                       // zoom > 1
+                       Rectangle r2 = getZoomedInTranslationBox();
+                       int LBoundX = r2.x;
+                       int UBoundX = r2.x + r2.width;
+                       int LBoundY = r2.y;
+                       int UBoundY = r2.y + r2.height;
+                       if (this.getTranslation().x < LBoundX) {
+                               this.setTranslation(new Point(LBoundX, getTranslation().y));
+                       } else if (this.getTranslation().x > UBoundX) {
+                               this.setTranslation(new Point(UBoundX, getTranslation().y));
+                       }
+                       if (this.getTranslation().y < LBoundY) {
+                               this.setTranslation(new Point(getTranslation().x, LBoundY));
+                       } else if (this.getTranslation().y > UBoundY) {
+                               this.setTranslation(new Point(getTranslation().x, UBoundY));
+                       }
+               }
+       }
+
+       public Rectangle getZoomedInTranslationBox() {
+               int LBoundX = -(int) ((this.getInnerWidth()) / 2.0);
+               int UBoundX = (int) ((this.getInnerWidth()) / 2.0);
+               int LBoundY = -(int) ((this.getInnerHeight()) / 2.0);
+               int UBoundY = (int) ((this.getInnerHeight()) / 2.0);
+               return new Rectangle(LBoundX, LBoundY, UBoundX - LBoundX, UBoundY
+                               - LBoundY);
+
+       }
+
+       /**
+        * Returns the "real pixels" x-coordinate of the RNA.
+        * 
+        * @return X-coordinate of the translation
+        */
+       public int getLeftOffset() {
+               return _border.width
+                               + ((this.getWidth() - 2 * _border.width) - this.getInnerWidth())
+                               / 2 + _translation.x;
+       }
+
+       /**
+        * Returns the "real pixels" width of the drawing surface for our RNA.
+        * 
+        * @return Width of the drawing surface for our RNA
+        */
+       public int getInnerWidth() {
+               // Largeur du dessin
+               return (int) Math.round((this.getWidth() - 2 * _border.width)
+                               * _conf._zoom);
+       }
+
+       /**
+        * Returns the "real pixels" y-coordinate of the RNA.
+        * 
+        * @return Y-coordinate of the translation
+        */
+       public int getTopOffset() {
+               return _border.height
+                               + ((this.getHeight() - 2 * _border.height) - this
+                                               .getInnerHeight()) / 2 + _translation.y;
+       }
+
+       /**
+        * Returns the "real pixels" height of the drawing surface for our RNA.
+        * 
+        * @return Height of the drawing surface for our RNA
+        */
+       public int getInnerHeight() {
+               // Hauteur du dessin
+               return (int) Math.round((this.getHeight()) * _conf._zoom - 2
+                               * _border.height - getTitleHeight());
+       }
+
+       /**
+        * Checks if the current mode is the "comparison" mode
+        * 
+        * @return True if comparison, false otherwise
+        */
+       public boolean isComparisonMode() {
+               return _conf._comparisonMode;
+       }
+
+       /**
+        * Rotates the RNA coordinates by a certain angle
+        * 
+        * @param angleDegres
+        *            Rotation angle, in degrees
+        */
+       public void globalRotation(Double angleDegres) {
+               _RNA.globalRotation(angleDegres);
+               fireLayoutChanged();
+               repaint();
+       }
+
+       /**
+        * Returns the index of the currently selected base, defaulting to the
+        * closest base to the last mouse-click.
+        * 
+        * @return Index of the currently selected base
+        */
+       public Integer getNearestBase() {
+               return _nearestBase;
+       }
+
+       /**
+        * Sets the index of the currently selected base.
+        * 
+        * @param base
+        *            Index of the new selected base
+        */
+       public void setNearestBase(Integer base) {
+               _nearestBase = base;
+       }
+
+       /**
+        * Returns the color used to draw 'Gaps' bases in comparison mode
+        * 
+        * @return Color used for 'Gaps'
+        */
+       public Color getGapsBasesColor() {
+               return _conf._dashBasesColor;
+       }
+
+       /**
+        * Sets the color to use for 'Gaps' bases in comparison mode
+        * 
+        * @param c
+        *            Color used for 'Gaps'
+        */
+       public void setGapsBasesColor(Color c) {
+               _conf._dashBasesColor = c;
+       }
+
+       @SuppressWarnings("unused")
+       private void imprimer() {
+               // PrintPanel canvas;
+               // canvas = new PrintPanel();
+               PrintRequestAttributeSet attributes;
+               attributes = new HashPrintRequestAttributeSet();
+               try {
+                       PrinterJob job = PrinterJob.getPrinterJob();
+                       // job.setPrintable(this);
+                       if (job.printDialog(attributes)) {
+                               job.print(attributes);
+                       }
+               } catch (PrinterException exception) {
+                       errorDialog(exception);
+               }
+       }
+
+       /**
+        * Checks whether errors are to be displayed
+        * 
+        * @return Error display status
+        */
+       public boolean isErrorsOn() {
+               return _conf._errorsOn;
+       }
+
+       /**
+        * Sets whether errors are to be displayed
+        * 
+        * @param on
+        *            New error display status
+        */
+       public void setErrorsOn(boolean on) {
+               _conf._errorsOn = on;
+       }
+
+       /**
+        * Returns the view associated with user interactions
+        * 
+        * @return A view associated with user interactions
+        */
+       public VueUI getVARNAUI() {
+               return _UI;
+       }
+
+       /**
+        * Toggles on/off using base inner color for drawing base-pairs
+        * 
+        * @param on
+        *            True for using base inner color for drawing base-pairs, false
+        *            for classic mode
+        */
+       public void setUseBaseColorsForBPs(boolean on) {
+               _conf._useBaseColorsForBPs = on;
+       }
+
+       /**
+        * Returns true if current base color is used as inner color for drawing
+        * base-pairs
+        * 
+        * @return True for using base inner color for drawing base-pairs, false for
+        *         classic mode
+        */
+       public boolean getUseBaseColorsForBPs() {
+               return _conf._useBaseColorsForBPs;
+       }
+
+       /**
+        * Toggles on/off using a special color used for drawing "non-standard"
+        * bases
+        * 
+        * @param on
+        *            True for using a special color used for drawing "non-standard"
+        *            bases, false for classic mode
+        */
+       public void setColorNonStandardBases(boolean on) {
+               _conf._colorSpecialBases = on;
+       }
+
+       /**
+        * Returns true if a special color is used as inner color for non-standard
+        * base
+        * 
+        * @return True for using a special color used for drawing "non-standard"
+        *         bases, false for classic mode
+        */
+       public boolean getColorSpecialBases() {
+               return _conf._colorSpecialBases;
+       }
+
+       /**
+        * Toggles on/off using a special color used for drawing "Gaps" bases in
+        * comparison mode
+        * 
+        * @param on
+        *            True for using a special color used for drawing "Gaps" bases
+        *            in comparison mode, false for classic mode
+        */
+       public void setColorGapsBases(boolean on) {
+               _conf._colorDashBases = on;
+       }
+
+       /**
+        * Returns true if a special color is used for drawing "Gaps" bases in
+        * comparison mode
+        * 
+        * @return True for using a special color used for drawing "Gaps" bases in
+        *         comparison mode, false for classic mode
+        */
+       public boolean getColorGapsBases() {
+               return _conf._colorDashBases;
+       }
+
+       /**
+        * Toggles on/off displaying warnings
+        * 
+        * @param on
+        *            True to display warnings, false otherwise
+        */
+       public void setShowWarnings(boolean on) {
+               _conf._showWarnings = on;
+       }
+
+       /**
+        * Get current warning display status
+        * 
+        * @return True to display warnings, false otherwise
+        */
+       public boolean getShowWarnings() {
+               return _conf._showWarnings;
+       }
+
+       /**
+        * Toggles on/off displaying non-canonical base-pairs
+        * 
+        * @param on
+        *            True to display NC base-pairs, false otherwise
+        */
+       public void setShowNonCanonicalBP(boolean on) {
+               _conf._drawnNonCanonicalBP = on;
+       }
+
+       /**
+        * Return the current display status for non-canonical base-pairs
+        * 
+        * @return True if NC base-pairs are displayed, false otherwise
+        */
+       public boolean getShowNonCanonicalBP() {
+               return _conf._drawnNonCanonicalBP;
+       }
+
+       /**
+        * Toggles on/off displaying "non-planar" base-pairs
+        * 
+        * @param on
+        *            True to display "non-planar" base-pairs, false otherwise
+        */
+       public void setShowNonPlanarBP(boolean on) {
+               _conf._drawnNonPlanarBP = on;
+       }
+
+       /**
+        * Return the current display status for non-planar base-pairs
+        * 
+        * @return True if non-planars base-pairs are displayed, false otherwise
+        */
+       public boolean getShowNonPlanarBP() {
+               return _conf._drawnNonPlanarBP;
+       }
+
+       /**
+        * Sets the base-pair representation style
+        * 
+        * @param st
+        *            The new base-pair style
+        */
+       public void setBPStyle(VARNAConfig.BP_STYLE st) {
+               _conf._mainBPStyle = st;
+       }
+
+       /**
+        * Returns the base-pair representation style
+        * 
+        * @return The current base-pair style
+        */
+       public VARNAConfig.BP_STYLE getBPStyle() {
+               return _conf._mainBPStyle;
+       }
+
+       /**
+        * Returns the current VARNA Panel configuration. The returned instance
+        * should not be modified directly, but rather through the getters/setters
+        * from the VARNAPanel class.
+        * 
+        * @return Current configuration
+        */
+       public VARNAConfig getConfig() {
+               return _conf;
+       }
+
+       /**
+        * Sets the background color
+        * 
+        * @param c
+        *            New background color
+        */
+       public void setBackground(Color c) {
+               if (_conf != null) {
+                       if (c != null) {
+                               _conf._backgroundColor = c;
+                               _conf._drawBackground = (!c
+                                               .equals(VARNAConfig.DEFAULT_BACKGROUND_COLOR));
+                       } else {
+                               _conf._backgroundColor = VARNAConfig.DEFAULT_BACKGROUND_COLOR;
+                               _conf._drawBackground = false;
+                       }
+               }
+
+       }
+
+       /**
+        * Starts highlighting the selected base.
+        */
+       public void highlightSelectedBase(ModeleBase m) {
+               ArrayList<Integer> v = new ArrayList<Integer>();
+               int sel = m.getIndex();
+               if (sel != -1) {
+                       v.add(sel);
+               }
+               setSelection(v);
+       }
+
+       /**
+        * Starts highlighting the selected base.
+        */
+       public void highlightSelectedStem(ModeleBase m) {
+               ArrayList<Integer> v = new ArrayList<Integer>();
+               int sel = m.getIndex();
+               if (sel != -1) {
+                       ArrayList<Integer> r = _RNA.findStem(sel);
+                       v.addAll(r);
+               }
+               setSelection(v);
+       }
+
+       public BaseList getSelection() {
+               return _selectedBases;
+       }
+
+       public ArrayList<Integer> getSelectionIndices() {
+               return _selectedBases.getIndices();
+       }
+
+       public void setSelection(ArrayList<Integer> indices) {
+               setSelection(_RNA.getBasesAt(indices));
+       }
+
+       public void setSelection(Collection<? extends ModeleBase> mbs) {
+               BaseList bck = new BaseList(_selectedBases);
+               _selectedBases.clear();
+               _selectedBases.addBases(mbs);
+               _blink.setActive(true);
+               fireSelectionChanged(bck, _selectedBases);
+       }
+
+       public ArrayList<Integer> getBasesInRectangleDiff(Rectangle recIn,
+                       Rectangle recOut) {
+               ArrayList<Integer> result = new ArrayList<Integer>();
+               for (int i = 0; i < _realCoords.length; i++) {
+                       if (recIn.contains(_realCoords[i])
+                                       ^ recOut.contains(_realCoords[i]))
+                               result.add(i);
+               }
+               return result;
+       }
+
+       public ArrayList<Integer> getBasesInRectangle(Rectangle rec) {
+               ArrayList<Integer> result = new ArrayList<Integer>();
+               for (int i = 0; i < _realCoords.length; i++) {
+                       if (rec.contains(_realCoords[i]))
+                               result.add(i);
+               }
+               return result;
+       }
+
+       public void setSelectionRectangle(Rectangle rec) {
+               ArrayList<Integer> result = new ArrayList<Integer>();
+               if (_selectionRectangle != null) {
+                       result = getBasesInRectangleDiff(_selectionRectangle, rec);
+               } else {
+                       result = getBasesInRectangle(rec);
+               }
+               _selectionRectangle = new Rectangle(rec);
+               toggleSelection(result);
+               repaint();
+       }
+
+       public void removeSelectionRectangle() {
+               _selectionRectangle = null;
+       }
+
+       public void addToSelection(Collection<? extends Integer> indices) {
+               for (int i : indices) {
+                       addToSelection(i);
+               }
+       }
+
+       public void addToSelection(int i) {
+               BaseList bck = new BaseList(_selectedBases);
+               ModeleBase mb = _RNA.getBaseAt(i);
+               _selectedBases.addBase(mb);
+               _blink.setActive(true);
+               fireSelectionChanged(bck, _selectedBases);
+       }
+
+       public void removeFromSelection(int i) {
+               BaseList bck = new BaseList(_selectedBases);
+               ModeleBase mb = _RNA.getBaseAt(i);
+               _selectedBases.removeBase(mb);
+               if (_selectedBases.size() == 0) {
+                       _blink.setActive(false);
+               } else {
+                       _blink.setActive(true);
+               }
+               fireSelectionChanged(bck, _selectedBases);
+       }
+
+       public boolean isInSelection(int i) {
+               return _selectedBases.contains(_RNA.getBaseAt(i));
+       }
+
+       public void toggleSelection(int i) {
+               if (isInSelection(i))
+                       removeFromSelection(i);
+               else
+                       addToSelection(i);
+       }
+
+       public void toggleSelection(Collection<? extends Integer> indices) {
+               for (int i : indices) {
+                       toggleSelection(i);
+               }
+       }
+
+       /**
+        * Stops highlighting bases
+        */
+       public void clearSelection() {
+               BaseList bck = new BaseList(_selectedBases);
+               _selectedBases.clear();
+               _blink.setActive(false);
+               repaint();
+               fireSelectionChanged(bck, _selectedBases);
+       }
+
+       public void saveSelection() {
+               _backupSelection.clear();
+               _backupSelection.addAll(_selectedBases.getBases());
+       }
+
+       public void restoreSelection() {
+               setSelection(_backupSelection);
+       }
+
+       /**
+        * Stops highlighting bases
+        */
+       public void resetAnnotationHighlight() {
+               _highlightAnnotation = false;
+               repaint();
+       }
+
+       /**
+        * Toggles on/off a rectangular outline of the bounding box.
+        * 
+        * @param on
+        *            True to draw the bounding box, false otherwise
+        */
+       public void drawBBox(boolean on) {
+               _drawBBox = on;
+       }
+
+       /**
+        * Toggles on/off a rectangular outline of the border.
+        * 
+        * @param on
+        *            True to draw the bounding box, false otherwise
+        */
+       public void drawBorder(boolean on) {
+               _drawBorder = on;
+       }
+
+       public void setBaseInnerColor(Color c) {
+               _RNA.setBaseInnerColor(c);
+       }
+
+       public void setBaseNumbersColor(Color c) {
+               _RNA.setBaseNumbersColor(c);
+       }
+
+       public void setBaseNameColor(Color c) {
+               _RNA.setBaseNameColor(c);
+       }
+
+       public void setBaseOutlineColor(Color c) {
+               _RNA.setBaseOutlineColor(c);
+       }
+
+       public ArrayList<TextAnnotation> getListeAnnotations() {
+               return _RNA.getAnnotations();
+       }
+
+       public void resetListeAnnotations() {
+               _RNA.clearAnnotations();
+               repaint();
+       }
+
+       public void addAnnotation(TextAnnotation textAnnotation) {
+               _RNA.addAnnotation(textAnnotation);
+               repaint();
+       }
+
+       public boolean removeAnnotation(TextAnnotation textAnnotation) {
+               boolean done = _RNA.removeAnnotation(textAnnotation);
+               repaint();
+               return done;
+       }
+
+       public TextAnnotation get_selectedAnnotation() {
+               return _selectedAnnotation;
+       }
+
+       public void set_selectedAnnotation(TextAnnotation annotation) {
+               _selectedAnnotation = annotation;
+       }
+
+       public void removeSelectedAnnotation() {
+               _highlightAnnotation = false;
+               _selectedAnnotation = null;
+       }
+
+       public void highlightSelectedAnnotation() {
+               _highlightAnnotation = true;
+       }
+
+       public boolean getFlatExteriorLoop() {
+               return _conf._flatExteriorLoop;
+       }
+
+       public void setFlatExteriorLoop(boolean on) {
+               _conf._flatExteriorLoop = on;
+       }
+
+       public void setLastSelectedPosition(Point2D.Double p) {
+               _lastSelectedCoord.x = p.x;
+               _lastSelectedCoord.y = p.y;
+       }
+
+       public Point2D.Double getLastSelectedPosition() {
+               return _lastSelectedCoord;
+       }
+
+       public void setSequence(String s) {
+               _RNA.setSequence(s);
+               repaint();
+       }
+
+       public void setColorMapVisible(boolean b) {
+               _conf._drawColorMap = b;
+               repaint();
+       }
+
+       public boolean getColorMapVisible() {
+               return _conf._drawColorMap;
+       }
+
+       public void removeColorMap() {
+               _conf._drawColorMap = false;
+               repaint();
+       }
+
+       public void saveSession(String path) {
+               /*
+                * FileOutputStream fos = null; ObjectOutputStream out = null; try { fos
+                * = new FileOutputStream(path); out = new ObjectOutputStream(fos);
+                * out.writeObject(new FullBackup(_conf, _RNA, _conf._title));
+                * out.close(); } catch (Exception ex) { ex.printStackTrace(); }
+                */
+               toXML(path);
+       }
+
+        public FullBackup loadSession(String path) throws ExceptionLoadingFailed { 
+          return loadSession(new File(path));
+        }
+       public FullBackup loadSession(File path) throws ExceptionLoadingFailed {
+
+               FullBackup bck = importSession(path);
+               Mapping map = Mapping.DefaultOutermostMapping(getRNA().getSize(),
+                               bck.rna.getSize());
+               showRNAInterpolated(bck.rna, map);
+               _conf = bck.config;
+               repaint();
+               return bck;
+       }
+
+       public static String VARNA_SESSION_EXTENSION = "varna";
+
+       public static FullBackup importSession(Object path) // BH was String
+                       throws ExceptionLoadingFailed {
+               try {
+                       FileInputStream fis = (path instanceof File ? new FileInputStream((File) path) : new FileInputStream(path.toString()));
+                       // ZipInputStream zis = new
+                       // ZipInputStream(new BufferedInputStream(fis));
+                       // zis.getNextEntry();
+                       FullBackup h = importSession(fis, path.toString());
+                       // zis.close();
+                       return h;
+               } catch (FileNotFoundException e) {
+                       throw (new ExceptionLoadingFailed("File not found.", path.toString()));
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       throw (new ExceptionLoadingFailed(
+                                       "I/O error while loading session.", path.toString()));
+               }
+       }
+
+       public static FullBackup importSession(InputStream fis, String path)
+                       throws ExceptionLoadingFailed {
+               System.setProperty("javax.xml.parsers.SAXParserFactory",
+                               "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
+               SAXParserFactory saxFact = javax.xml.parsers.SAXParserFactory
+                               .newInstance();
+               saxFact.setValidating(false);
+               saxFact.setXIncludeAware(false);
+               saxFact.setNamespaceAware(false);
+               try {
+                       SAXParser sp = saxFact.newSAXParser();
+                       VARNASessionParser sessionData = new VARNASessionParser();
+                       sp.parse(fis, sessionData);
+                       FullBackup res = new FullBackup(sessionData.getVARNAConfig(),
+                                       sessionData.getRNA(), "test"); 
+                       return res;
+               } catch (ParserConfigurationException e) {
+                       throw new ExceptionLoadingFailed("Bad XML parser configuration",
+                                       path);
+               } catch (SAXException e) {
+                       throw new ExceptionLoadingFailed("XML parser Exception", path);
+               } catch (IOException e) {
+                       throw new ExceptionLoadingFailed("I/O error", path);
+               }
+       }
+
+       public void loadFile(File path) {
+               loadFile(path, false);
+       }
+
+       public boolean getDrawBackbone() {
+               return _conf._drawBackbone;
+       }
+
+       public void setDrawBackbone(boolean b) {
+               _conf._drawBackbone = b;
+       }
+
+       public void addHighlightRegion(HighlightRegionAnnotation n) {
+               _RNA.addHighlightRegion(n);
+       }
+
+       public void removeHighlightRegion(HighlightRegionAnnotation n) {
+               _RNA.removeHighlightRegion(n);
+       }
+
+       public void addHighlightRegion(int i, int j) {
+               _RNA.addHighlightRegion(i, j);
+       }
+
+       public void addHighlightRegion(int i, int j, Color fill, Color outline,
+                       double radius) {
+               _RNA.addHighlightRegion(i, j, fill, outline, radius);
+       }
+       
+       public void loadRNA(String path) {
+               loadRNA(path, false);
+       }
+       
+       public void loadRNA(Object path, boolean interpolate) { // BH was String
+               try {
+                       Collection<RNA> rnas = (path instanceof File ? RNAFactory.loadSecStr(new FileReader((File) path)) : RNAFactory.loadSecStr(path.toString()));
+                       if (rnas.isEmpty()) {
+                               throw new ExceptionFileFormatOrSyntax(
+                                               "No RNA could be parsed from that source.");
+                       }
+                       RNA rna = rnas.iterator().next();
+                       try {
+                               rna.drawRNA(_conf);
+                       } catch (ExceptionNAViewAlgorithm e) {
+                               e.printStackTrace();
+                       }
+                       if (!interpolate) {
+                               showRNA(rna);
+                       } else {
+                               this.showRNAInterpolated(rna);
+                       }
+
+               } catch (FileNotFoundException e) {
+                       e.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       e.printStackTrace();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void loadFile(File path, boolean interpolate) { // was String BH StringJS
+               try {
+                       loadSession(path);
+               } catch (Exception e1) {
+                       loadRNA(path, interpolate);
+               }
+       }
+
+       public void setConfig(VARNAConfig cfg) {
+               _conf = cfg;
+       }
+
+       public void toggleDrawOutlineBases() {
+               _conf._drawOutlineBases = !_conf._drawOutlineBases;
+       }
+
+       public void toggleFillBases() {
+               _conf._fillBases = !_conf._fillBases;
+       }
+
+       public void setDrawOutlineBases(boolean drawn) {
+               _conf._drawOutlineBases = drawn;
+       }
+
+       public void setFillBases(boolean drawn) {
+               _conf._fillBases = drawn;
+       }
+
+       public void readValues(Reader r) {
+               this._RNA.readValues(r, _conf._cm);
+       }
+
+       public void addVARNAListener(InterfaceVARNAListener v) {
+               _VARNAListeners.add(v);
+       }
+
+       public void fireLayoutChanged() {
+               for (InterfaceVARNAListener v : _VARNAListeners) {
+                       v.onStructureRedrawn();
+               }
+       }
+
+       public void fireUINewStructure(RNA r) {
+               for (InterfaceVARNAListener v : _VARNAListeners) {
+                       v.onUINewStructure(_conf, r);
+               }
+       }
+
+       public void fireZoomLevelChanged(double d) {
+               for (InterfaceVARNAListener v : _VARNAListeners) {
+                       v.onZoomLevelChanged();
+               }
+       }
+
+       public void fireTranslationChanged() {
+               for (InterfaceVARNAListener v2 : _VARNAListeners) {
+                       v2.onTranslationChanged();
+               }
+       }
+
+       public void addSelectionListener(InterfaceVARNASelectionListener v) {
+               _selectionListeners.add(v);
+       }
+
+       public void fireSelectionChanged(BaseList mold, BaseList mnew) {
+               BaseList addedBases = mnew.removeAll(mold);
+               BaseList removedBases = mold.removeAll(mnew);
+               for (InterfaceVARNASelectionListener v2 : _selectionListeners) {
+                       v2.onSelectionChanged(mnew, addedBases, removedBases);
+               }
+       }
+
+       public void fireHoverChanged(ModeleBase mold, ModeleBase mnew) {
+               for (InterfaceVARNASelectionListener v2 : _selectionListeners) {
+                       v2.onHoverChanged(mold, mnew);
+               }
+       }
+
+       public void addRNAListener(InterfaceVARNARNAListener v) {
+               _RNAListeners.add(v);
+       }
+
+       public void addVARNABasesListener(InterfaceVARNABasesListener l) {
+               _basesListeners.add(l);
+       }
+
+       public void fireSequenceChanged(int index, String oldseq, String newseq) {
+               for (InterfaceVARNARNAListener v2 : _RNAListeners) {
+                       v2.onSequenceModified(index, oldseq, newseq);
+               }
+       }
+
+       public void fireStructureChanged(Set<ModeleBP> current,
+                       Set<ModeleBP> addedBasePairs, Set<ModeleBP> removedBasePairs) {
+               for (InterfaceVARNARNAListener v2 : _RNAListeners) {
+                       v2.onStructureModified(current, addedBasePairs, removedBasePairs);
+               }
+       }
+
+       public void fireLayoutChanged(
+                       Hashtable<Integer, Point2D.Double> movedPositions) {
+               for (InterfaceVARNARNAListener v2 : _RNAListeners) {
+                       v2.onRNALayoutChanged(movedPositions);
+               }
+       }
+
+       public void fireBaseClicked(ModeleBase mb, MouseEvent me) {
+               if (mb != null) {
+                       for (InterfaceVARNABasesListener v2 : _basesListeners) {
+                               v2.onBaseClicked(mb, me);
+                       }
+               }
+       }
+
+       public double getOrientation() {
+               return _RNA.getOrientation();
+       }
+
+       public ModeleBase _hoveredBase = null;
+
+       public void setHoverBase(ModeleBase m) {
+               if (m != _hoveredBase) {
+                       ModeleBase bck = _hoveredBase;
+                       _hoveredBase = m;
+                       repaint();
+                       fireHoverChanged(bck, m);
+               }
+       }
+
+       public void toXML(String path) {
+               FileOutputStream fis;
+               try {
+                       fis = new FileOutputStream(path);
+                       // ZipOutputStream zis = new ZipOutputStream(new
+                       // BufferedOutputStream(fis));
+                       // ZipEntry entry = new ZipEntry("VARNASession");
+                       // zis.putNextEntry(entry);
+                       PrintWriter pw = new PrintWriter(fis);
+                       toXML(pw);
+                       pw.flush();
+                       // zis.closeEntry();
+                       // zis.close();
+                       fis.close();
+               } catch (FileNotFoundException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       public void toXML(PrintWriter out) {
+               try {
+
+                       // out = new PrintWriter(System.out);
+                       StreamResult streamResult = new StreamResult(out);
+                       SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory
+                                       .newInstance();
+                       // SAX2.0 ContentHandler.
+                       TransformerHandler hd = tf.newTransformerHandler();
+                       Transformer serializer = hd.getTransformer();
+                       serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
+                       serializer
+                                       .setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "users.dtd");
+                       serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+                       hd.setResult(streamResult);
+                       hd.startDocument();
+                       toXML(hd);
+                       hd.endDocument();
+               } catch (TransformerConfigurationException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (SAXException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+       }
+
+       public static String XML_ELEMENT_NAME = "VARNASession";
+
+       public void toXML(TransformerHandler hd) throws SAXException {
+               AttributesImpl atts = new AttributesImpl();
+               hd.startElement("", "", XML_ELEMENT_NAME, atts);
+               _RNA.toXML(hd);
+               _conf.toXML(hd);
+               hd.endElement("", "", XML_ELEMENT_NAME);
+       }
+
+       public TextAnnotation getNearestAnnotation(int x, int y) {
+               TextAnnotation t = null;
+               if (getListeAnnotations().size() != 0) {
+                       double dist = Double.MAX_VALUE;
+                       double d2;
+                       Point2D.Double position;
+                       for (TextAnnotation textAnnot : getListeAnnotations()) {
+                               // calcul de la distance
+                               position = textAnnot.getCenterPosition();
+                               position = transformCoord(position);
+                               d2 = Math.sqrt(Math.pow((position.x - x), 2)
+                                               + Math.pow((position.y - y), 2));
+                               // si la valeur est inferieur au minimum actuel
+                               if ((dist > d2)
+                                               && (d2 < getScaleFactor()
+                                                               * ControleurClicMovement.MIN_SELECTION_DISTANCE)) {
+                                       t = textAnnot;
+                                       dist = d2;
+                               }
+                       }
+               }
+               return t;
+       }
+
+       public ModeleBase getNearestBase(int x, int y, boolean always,
+                       boolean onlyPaired) {
+               int i = getNearestBaseIndex(x, y, always, onlyPaired);
+               if (i == -1)
+                       return null;
+               return getRNA().get_listeBases().get(i);
+       }
+
+       public ModeleBase getNearestBase(int x, int y) {
+               return getNearestBase(x, y, false, false);
+       }
+
+       public int getNearestBaseIndex(int x, int y, boolean always,
+                       boolean onlyPaired) {
+               double d2, dist = Double.MAX_VALUE;
+               int mb = -1;
+               for (int i = 0; i < getRealCoords().length; i++) {
+                       if (!onlyPaired
+                                       || (getRNA().get_listeBases().get(i).getElementStructure() != -1)) {
+                               d2 = Math.sqrt(Math.pow((getRealCoords()[i].x - x), 2)
+                                               + Math.pow((getRealCoords()[i].y - y), 2));
+                               if ((dist > d2)
+                                               && ((d2 < getScaleFactor()
+                                                               * ControleurClicMovement.MIN_SELECTION_DISTANCE) || always)) {
+                                       dist = d2;
+                                       mb = i;
+                               }
+                       }
+               }
+               return mb;
+       }
+
+       public void globalRescale(double factor) {
+               _RNA.rescale(factor);
+               fireLayoutChanged();
+               repaint();
+       }
+
+       public void setSpaceBetweenBases(double sp) {
+               _conf._spaceBetweenBases = sp;
+       }
+
+       public double getSpaceBetweenBases() {
+               return _conf._spaceBetweenBases;
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/AlignmentDemo.java b/srcjar/fr/orsay/lri/varna/applications/AlignmentDemo.java
new file mode 100644 (file)
index 0000000..18c328c
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+public class AlignmentDemo extends JFrame{
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE1 =  "CGCGCACGCGA----UAUU----UCGCGUCGCGCAUUUGCGCGUAGCGCG";
+       private static final String DEFAULT_STRUCTURE1 = "(((((.(((((----....----))))).(((((....)))))..)))))";
+
+       private static final String DEFAULT_SEQUENCE2 =  "CGCGCACGCGSGCGCGUUUGCGCUCGCGU---------------AGCGCG";
+       private static final String DEFAULT_STRUCTURE2 = "(((((.(((((((((....))))))))).--------------..)))))";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vpMaster;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seq1Panel = new JPanel();
+       private JPanel _seq2Panel = new JPanel();
+       private JPanel _struct1Panel = new JPanel();
+       private JPanel _struct2Panel = new JPanel();
+       private JLabel _info = new JLabel();
+       private JTextField _struct1 = new JTextField(DEFAULT_STRUCTURE1);
+       private JTextField _struct2 = new JTextField(DEFAULT_STRUCTURE2);
+       private JTextField _seq1 = new JTextField(DEFAULT_SEQUENCE1);
+       private JTextField _seq2 = new JTextField(DEFAULT_SEQUENCE2);
+       private JLabel _struct1Label = new JLabel(" Str1:");
+       private JLabel _struct2Label = new JLabel(" Str2:");
+       private JLabel _seq1Label = new JLabel(" Seq1:");
+       private JLabel _seq2Label = new JLabel(" Seq2:");
+       private JButton _goButton = new JButton("Go");
+
+       private String _str1Backup = "";
+       private String _str2Backup = "";
+       private String _seq1Backup = "";
+       private String _seq2Backup = "";
+       private RNA _RNA = new RNA();
+
+       private static String errorOpt = "error";
+       @SuppressWarnings("unused")
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+
+       @SuppressWarnings("unused")
+       private int _algoCode;
+
+
+       public AlignmentDemo() {
+               super();
+                       _vpMaster = new VARNAPanel(getSeq1(), getStruct1(), getSeq2(), getStruct2(), RNA.DRAW_MODE_RADIATE,"");
+               _vpMaster.setPreferredSize(new Dimension(600, 400));
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() {
+               int marginTools = 40;
+
+               setBackground(_backgroundColor);
+               _vpMaster.setBackground(_backgroundColor);
+
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+
+               _goButton.addActionListener(new ActionListener() {
+
+                       public void actionPerformed(ActionEvent e) {
+                                       _vpMaster.drawRNA(getSeq1(), getStruct1(), getSeq2(), getStruct2(), _vpMaster.getDrawMode());
+                               _vpMaster.repaint();
+                       }
+               });
+
+               
+               _seq1Label.setHorizontalTextPosition(JLabel.LEFT);
+               _seq1Label.setPreferredSize(new Dimension(marginTools, 15));
+               _seq1.setFont(textFieldsFont);
+
+               _seq1Panel.setLayout(new BorderLayout());
+               _seq1Panel.add(_seq1Label, BorderLayout.WEST);
+               _seq1Panel.add(_seq1, BorderLayout.CENTER);
+
+               _seq2Label.setHorizontalTextPosition(JLabel.LEFT);
+               _seq2Label.setPreferredSize(new Dimension(marginTools, 15));
+               _seq2.setFont(textFieldsFont);
+               
+               _seq2Panel.setLayout(new BorderLayout());
+               _seq2Panel.add(_seq2Label, BorderLayout.WEST);
+               _seq2Panel.add(_seq2, BorderLayout.CENTER);
+
+               _struct1Label.setPreferredSize(new Dimension(marginTools, 15));
+               _struct1Label.setHorizontalTextPosition(JLabel.LEFT);
+               _struct1.setFont(textFieldsFont);
+               _struct1Panel.setLayout(new BorderLayout());
+               _struct1Panel.add(_struct1Label, BorderLayout.WEST);
+               _struct1Panel.add(_struct1, BorderLayout.CENTER);
+
+               _struct2Label.setPreferredSize(new Dimension(marginTools, 15));
+               _struct2Label.setHorizontalTextPosition(JLabel.LEFT);
+               _struct2.setFont(textFieldsFont);
+               _struct2Panel.setLayout(new BorderLayout());
+               _struct2Panel.add(_struct2Label, BorderLayout.WEST);
+               _struct2Panel.add(_struct2, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(4, 0));
+               _input.add(_seq1Panel);
+               _input.add(_struct1Panel);
+               _input.add(_seq2Panel);
+               _input.add(_struct2Panel);
+
+               JPanel goPanel = new JPanel();
+               goPanel.setLayout(new BorderLayout());
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+               _tools.add(goPanel, BorderLayout.EAST);
+
+               goPanel.add(_goButton, BorderLayout.CENTER);
+               
+               getContentPane().setLayout(new BorderLayout());
+               JPanel VARNAs = new JPanel();
+               VARNAs.setLayout(new GridLayout(1,1));
+               VARNAs.add(_vpMaster);
+               getContentPane().add(VARNAs, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.SOUTH);
+
+               setVisible(true);
+               _vpMaster.getVARNAUI().UIRadiate();
+       }
+
+       public RNA getRNA() {
+
+               if (!( _str1Backup.equals(getStruct1())
+                       && _str2Backup.equals(getStruct2())
+                       && _seq1Backup.equals(getSeq1())
+                       && _seq2Backup.equals(getSeq2())
+               )) {
+                       _vpMaster.drawRNA(getSeq1(), getStruct1(), getSeq2(), getStruct2(), _vpMaster.getDrawMode());
+                       _RNA = _vpMaster.getRNA();
+                       _str1Backup = getStruct1();
+                       _str2Backup = getStruct2();
+                       _seq1Backup = getSeq1();
+                       _seq2Backup = getSeq2();
+               }
+               return _RNA;
+       }
+
+
+       public String getStruct1() {
+               return cleanStruct(_struct1.getText());
+       }
+
+       public String getStruct2() {
+               return cleanStruct(_struct2.getText());
+       }
+
+       public String getSeq1() {
+               return cleanStruct(_seq1.getText());
+       }
+
+       public String getSeq2() {
+               return cleanStruct(_seq2.getText());
+       }
+       
+       
+       private String cleanStruct(String struct) {
+               struct = struct.replaceAll("[:-]", "-");
+               return struct;
+       }
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               _vpMaster.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       @SuppressWarnings("unused")
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vpMaster;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vpMaster = surface;
+       }
+
+       public JTextField get_struct() {
+               return _struct1;
+       }
+
+       public void set_struct(JTextField _struct) {
+               this._struct1 = _struct;
+       }
+
+       public JTextField get_seq() {
+               return _seq1;
+       }
+
+       public void set_seq(JTextField _seq) {
+               this._seq1 = _seq;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+
+       public static void main(String[] args) {
+               AlignmentDemo d = new AlignmentDemo();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/BasicINI.java b/srcjar/fr/orsay/lri/varna/applications/BasicINI.java
new file mode 100644 (file)
index 0000000..89a590e
--- /dev/null
@@ -0,0 +1,178 @@
+package fr.orsay.lri.varna.applications;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Set;
+
+public class BasicINI {
+
+       private Hashtable<String,Hashtable<String,String>> _data = new Hashtable<String,Hashtable<String,String>>();
+
+
+       public void addItem(String category, String key, String val)
+       {
+               if (!_data.containsKey(category))
+               {
+                       _data.put(category, new Hashtable<String,String>());
+               }
+               System.out.println("[E]"+key+"->"+val);
+               _data.get(category).put(key,val);
+       }
+
+
+       public String getItem(String category, String key)
+       {
+               String result = "";
+               if (_data.containsKey(category))
+               {
+                       if (_data.get(category).containsKey(key))
+                       {
+                               result = _data.get(category).get(key);
+                       }
+               }
+               return result;
+       }
+
+       public ArrayList<String> getItemList(String category)
+       {
+               ArrayList<String> result = new ArrayList<String>();
+               if (_data.containsKey(category))
+               {
+                       for (String key: _data.get(category).keySet())
+                       {
+                               result.add(_data.get(category).get(key));
+                       }
+               }
+               return result;
+       }
+
+       public BasicINI(){
+
+       }
+
+       public static void saveINI(BasicINI data, String filename)  
+       {
+               try
+               {
+                       FileWriter out = new FileWriter(filename);
+                       Set<String> cats = data._data.keySet();
+                       String[] sortedCats = new String[cats.size()];
+                       sortedCats = cats.toArray(sortedCats); 
+                       Arrays.sort(sortedCats);
+                       for (int i=0;i<sortedCats.length;i++)
+                       {
+                               String cat = sortedCats[i];             
+                               out.write("["+cat+"]\n"); 
+                               Hashtable<String,String> vals = data._data.get(cat);
+                               Set<String> keys = vals.keySet();
+                               String[] sortedKeys = new String[keys.size()];
+                               sortedKeys = keys.toArray(sortedKeys); 
+                               for(int j=0;j<sortedKeys.length;j++)
+                               {
+                                       String key = sortedKeys[j];
+                                       String val = vals.get(key);
+                                       out.write(key+"="+val+"\n");                            
+                               }
+                       }
+                       out.close();
+               }
+               catch(Exception e3)
+               {e3.printStackTrace();}
+       }
+
+       public static BasicINI loadINI(String filename)  
+       {
+               BasicINI result = new BasicINI();    
+
+               // Etats du parsing simplifie ...
+               final int CATEGORY = 0;
+               final int KEY = 1;
+               final int VAL = 2;
+               int state = KEY;
+               String category = "";
+               String key = "";
+               String val = "";
+
+               try
+               {
+                       System.out.println("Loading "+new File(filename).getAbsolutePath());
+
+                       Reader r = new FileReader(filename);
+                       StreamTokenizer s = new StreamTokenizer(r);
+                       s.resetSyntax();
+                       s.eolIsSignificant(true);
+                       s.wordChars('\u0000','\u00FF');
+                       s.whitespaceChars('\u0000','\u000F');
+                       s.ordinaryChar('[');
+                       s.ordinaryChar(']');
+                       s.ordinaryChar('=');
+                       int token = s.nextToken();
+                       while(token != StreamTokenizer.TT_EOF)
+                       {
+                               switch(token)
+                               {
+                               case('[') :
+                               {
+                                       state = CATEGORY;
+                               }
+                               break;
+                               case(']') :
+                               {
+                                       state = KEY;
+                               }
+                               break;
+                               case('=') :
+                               {
+                                       state = VAL;
+                               }
+                               break;
+                               case(StreamTokenizer.TT_EOL) :
+                               {
+                                       if (state==VAL)
+                                       {
+                                         state = KEY;
+                                         result.addItem(category, key, val);
+                                         key="";
+                                         val="";
+                                       }
+                               }
+                               case(StreamTokenizer.TT_WORD) :
+                               {
+                                       String word = s.sval;
+                                       switch(state)
+                                       {
+                                       case(CATEGORY) :
+                                       {
+                                               category = word;
+                                       }
+                                       break;
+                                       case(KEY) :
+                                       {
+                                               key = word;
+                                       }
+                                       break;
+                                       case(VAL) :
+                                       {
+                                               val = word;
+                                       }
+                                       break;
+                                       }
+                               }
+                               break;
+                               }
+                               token = s.nextToken();
+                       }
+               }
+               catch(Exception exc1)
+               {exc1.printStackTrace();}
+               return result;
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/FileNameExtensionFilter.java b/srcjar/fr/orsay/lri/varna/applications/FileNameExtensionFilter.java
new file mode 100644 (file)
index 0000000..377a4f1
--- /dev/null
@@ -0,0 +1,71 @@
+package fr.orsay.lri.varna.applications;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.swing.filechooser.FileFilter;
+
+public class FileNameExtensionFilter extends FileFilter {
+
+    Hashtable<String,Integer> _exts = new Hashtable<String,Integer>();
+    String _desc = "";
+       
+       public FileNameExtensionFilter(String desc,String ext1)
+       {
+               _desc = desc;
+               _exts.put(ext1,0);
+       }
+
+       public FileNameExtensionFilter(String desc,String ext1,String ext2)
+       {
+               this(desc,ext1);
+               _exts.put(ext2,1);
+       }
+       
+       public FileNameExtensionFilter(String desc,String ext1,String ext2,String ext3)
+       {
+               this(desc,ext1,ext2);
+               _exts.put(ext3,2);
+       }
+
+       public FileNameExtensionFilter(String desc,String ext1,String ext2,String ext3,String ext4)
+       {
+               this(desc,ext1,ext2,ext3);
+               _exts.put(ext4,3);
+       }
+       
+       
+       public boolean accept(File path) {
+               String name = path.getName();
+               if (path.isDirectory())
+                       return true;
+               int index = name.lastIndexOf(".");
+               if (index != -1)
+               {
+                 String suffix = name.substring(index+1);
+                 if (_exts.containsKey(suffix))
+                 {return true;}
+               }
+               return false;
+       }
+
+       @Override
+       public String getDescription() {
+               return _desc;
+       }
+       
+       public String[] getExtensions()
+       {
+               String[] exts = new String[_exts.size()];
+               Enumeration<String> k = _exts.keys();
+               int n = 0;
+               while(k.hasMoreElements())
+               {
+                       exts[n] = k.nextElement();
+                       n++;
+               }
+               return exts;
+       }       
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/NussinovDemo.java b/srcjar/fr/orsay/lri/varna/applications/NussinovDemo.java
new file mode 100644 (file)
index 0000000..94ac79d
--- /dev/null
@@ -0,0 +1,819 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JTextPane;
+import javax.swing.border.BevelBorder;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurInterpolator;
+import fr.orsay.lri.varna.exceptions.ExceptionDrawingAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.VARNAConfig.BP_STYLE;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNABasesListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;;
+
+public class NussinovDemo extends JFrame implements InterfaceVARNAListener,InterfaceVARNABasesListener {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String SEQUENCE_DUMMY =        "AAAAAAAAAA";
+       private static final String SEQUENCE_A =        "AGGCACGUCU";
+       private static final String SEQUENCE_B =  "GAGUAGCCUC";
+       private static final String SEQUENCE_C = "GCAUAGCUGC";
+       private static final String SEQUENCE_INRIA = "GAGAAGUACUUGAAAUUGGCCUCCUC";
+       
+
+       private static final String SEQUENCE_BIG = "AAAACAAAAACACCAUGGUGUUUUCACCCAAUUGGGUGAAAACAGAGAUCUCGAGAUCUCUGUUUUUGUUUU"; 
+
+       private static final String DEFAULT_STRUCTURE = "..........";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vpMaster;
+
+       private InfoPanel _infos = new InfoPanel();
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _structPanel = new JPanel();
+       private JLabel _actions = new JLabel();
+       private JLabel _struct = new JLabel(DEFAULT_STRUCTURE);
+       private JComboBox _seq1 = new JComboBox();
+       private JLabel _structLabel = new JLabel("Structure Secondaire");
+       private JLabel _seqLabel = new JLabel("Sequence d'ARN");
+       private JButton _goButton = new JButton("Repliement");
+       private JButton _switchButton = new JButton("Effacer");
+
+
+       private Color _backgroundColor = Color.white;
+       
+       public static Font textFieldsFont = Font.decode("MonoSpaced-BOLD-16");
+       public static Font labelsFont = Font.decode("SansSerif-BOLD-20");
+       public static final int marginTools = 250;
+       public static String APP_TITLE = "Fête de la science 2015 - Inria AMIB - Repliement d'ARN";
+       
+
+       public static ModelBaseStyle createStyle(String txt) 
+       {
+               ModelBaseStyle result = new ModelBaseStyle();
+               try {
+                       result.assignParameters(txt);
+               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionParameterError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               return result;
+       }
+       
+       public void applyTo(VARNAPanel vp, ModelBaseStyle mb, int[] indices)
+       {
+               for(int i=0;i<indices.length;i++)
+               { 
+                       ModeleBase m = vp.getRNA().getBaseAt(indices[i]);
+                       m.setStyleBase(mb);
+                       if (m.getElementStructure()!=-1)
+                       {
+                               vp.getRNA().getBaseAt(m.getElementStructure()).setStyleBase(mb);
+                       }
+               }
+               vp.repaint();
+       }
+       
+       
+       public NussinovDemo() {
+               super();
+               try {
+                       _vpMaster = new VARNAPanel(getSeq(), "");
+               } catch (ExceptionNonEqualLength e) {
+                       _vpMaster.errorDialog(e);
+               }
+               _vpMaster.setPreferredSize(new Dimension(600, 600));
+               RNAPanelDemoInit();
+       }
+
+       public static void formatLabel(JLabel j)
+       {
+               j.setHorizontalTextPosition(JLabel.LEFT);
+               j.setPreferredSize(new Dimension(marginTools, 15));
+               j.setFont(labelsFont);
+       }
+       
+       private void RNAPanelDemoInit() {
+               
+               
+
+               _seq1.setFont(textFieldsFont);
+               String[] seqs = {SEQUENCE_DUMMY,SEQUENCE_INRIA,SEQUENCE_A,SEQUENCE_B,SEQUENCE_C,SEQUENCE_BIG};
+               _seq1.setModel(new DefaultComboBoxModel(seqs));
+               _seq1.setEditable(true);
+               
+
+               setBackground(_backgroundColor);
+               _vpMaster.setBackground(_backgroundColor);
+               _vpMaster.addVARNAListener(this);
+               _vpMaster.setFlatExteriorLoop(true);
+               
+
+
+               formatLabel(_seqLabel);
+               formatLabel(_structLabel);
+
+               _goButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               showSolution();
+                               onStructureRedrawn();
+                       }
+               });
+
+               _switchButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               try {
+                                                       RNA r = new RNA();
+                                                       r.setRNA("", "");
+                                                       _struct.setText("");
+                                                       _vpMaster.setTitle("");
+                                                       _vpMaster.showRNA(r);
+                                                       onStructureRedrawn();
+                                       } 
+                               catch (ExceptionFileFormatOrSyntax e2) {
+                                       e2.printStackTrace();
+                               } catch (ExceptionUnmatchedClosingParentheses e2) {
+                                       // TODO Auto-generated catch block
+                                       e2.printStackTrace();
+                               }
+                               _vpMaster.repaint();
+                       }
+               });
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq1, BorderLayout.CENTER);
+
+               _structLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _structLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _struct.setFont(textFieldsFont);
+               _structPanel.setLayout(new BorderLayout());
+               _structPanel.add(_structLabel, BorderLayout.WEST);
+               _structPanel.add(_struct, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(0, 1));
+               _input.add(_seqPanel);
+               _input.add(_structPanel);
+
+               JPanel goPanel = new JPanel();
+               goPanel.setLayout(new BorderLayout());
+
+               _infos.setFont(labelsFont);
+
+               
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_infos, BorderLayout.SOUTH);
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_actions, BorderLayout.NORTH);
+               _tools.add(goPanel, BorderLayout.EAST);
+               _tools.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+               goPanel.add(_goButton, BorderLayout.CENTER);
+               goPanel.add(_switchButton, BorderLayout.SOUTH);
+
+               getContentPane().setLayout(new BorderLayout());
+               JPanel VARNAs = new JPanel();
+               VARNAs.setLayout(new GridLayout(1,2));
+               VARNAs.add(_vpMaster);
+               VARNAs.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
+               getContentPane().add(VARNAs, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.SOUTH);
+
+               setVisible(true);
+
+               _vpMaster.getVARNAUI().UIRadiate();
+               _vpMaster.setTitleFontSize(26f);
+               _vpMaster.setTitleFontStyle(Font.PLAIN);
+               _vpMaster.addVARNABasesListener(this);
+               _vpMaster.setBackground(Color.decode("#308099"));
+               _vpMaster.setModifiable(false);
+               _vpMaster.setTitle("Repliement cible");
+               _vpMaster.setBPStyle(BP_STYLE.SIMPLE);
+               _vpMaster.setBackboneColor(Color.white);
+               _vpMaster.setDefaultBPColor(Color.white);
+               _vpMaster.setBaseNumbersColor(Color.white);
+               _vpMaster.setBaseOutlineColor(Color.white);
+               _vpMaster.setTitleColor(Color.white);           
+               _vpMaster.setTitleFontSize(26f);
+
+               
+               
+               this.setTitle(APP_TITLE);
+               
+               showSolution();
+               onStructureRedrawn();
+       }
+
+       private synchronized void showSolution()
+       {
+               ArrayList<String> sols = getStructs();
+               _infos.setInfo(sols, count(getSeq()));
+       }
+       
+
+
+
+       public String getSeq()
+       {
+               return (""+_seq1.getSelectedItem()).toUpperCase();
+       }
+
+       
+       private boolean canBasePairAll(char a, char b)
+       {
+               return true;
+       }
+
+       private boolean canBasePairBasic(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               if ((a=='G')&&(b=='U'))
+                       return true;
+               if ((a=='U')&&(b=='G'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreBasic(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return 1.0;
+               if ((a=='C')&&(b=='G'))
+                       return 1.0;
+               if ((a=='U')&&(b=='A'))
+                       return 1.0;
+               if ((a=='A')&&(b=='U'))
+                       return 1.0;
+               if ((a=='G')&&(b=='U'))
+                       return 1.0;
+               if ((a=='U')&&(b=='G'))
+                       return 1.0;
+               return Double.NEGATIVE_INFINITY;
+       }
+
+       
+       private boolean canBasePairNussinov(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               if ((a=='U')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='U'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreNussinov(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return 3.0;
+               if ((a=='C')&&(b=='G'))
+                       return 3.0;
+               if ((a=='U')&&(b=='A'))
+                       return 2.0;
+               if ((a=='A')&&(b=='U'))
+                       return 2.0;
+               if ((a=='U')&&(b=='G'))
+                       return 1.0;
+               if ((a=='G')&&(b=='U'))
+                       return 1.0;
+               return Double.NEGATIVE_INFINITY;
+       }
+
+       private boolean canBasePairINRIA(char a, char b)
+       {
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+
+               if ((a=='A')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='A'))
+                       return true;
+               if ((a=='U')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='U'))
+                       return true;
+               if ((a=='A')&&(b=='A'))
+                       return true;
+               if ((a=='U')&&(b=='U'))
+                       return true;
+
+               if ((a=='U')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='U'))
+                       return true;
+               if ((a=='A')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='A'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreINRIA(char a, char b)
+       {
+               if ((a=='U')&&(b=='A'))
+                       return 3;
+               if ((a=='A')&&(b=='U'))
+                       return 3;
+               if ((a=='G')&&(b=='C'))
+                       return 3;
+               if ((a=='C')&&(b=='G'))
+                       return 3;
+
+               if ((a=='A')&&(b=='G'))
+                       return 2;
+               if ((a=='G')&&(b=='A'))
+                       return 2;
+               if ((a=='U')&&(b=='C'))
+                       return 2;
+               if ((a=='C')&&(b=='U'))
+                       return 2;
+               if ((a=='A')&&(b=='A'))
+                       return 2;
+               if ((a=='U')&&(b=='U'))
+                       return 2;
+
+               if ((a=='U')&&(b=='G'))
+                       return 1;
+               if ((a=='G')&&(b=='U'))
+                       return 1;
+               if ((a=='A')&&(b=='C'))
+                       return 1;
+               if ((a=='C')&&(b=='A'))
+                       return 1;
+               return Double.NEGATIVE_INFINITY;
+       }
+       
+       private boolean canBasePair(char a, char b)
+       {
+               return canBasePairBasic(a,b);
+               //return canBasePairNussinov(a,b);
+               //return canBasePairINRIA(a,b);
+       }
+       
+       private double basePairScore(char a, char b)
+       {
+               return basePairScoreBasic(a,b);
+               //return basePairScoreNussinov(a,b);
+               //return basePairScoreINRIA(a,b);
+       }
+       
+       public double[][] fillMatrix(String seq)
+       {
+               int n = seq.length();
+               double[][] tab = new double[n][n];
+               for(int m=1;m<=n;m++)
+               {
+                       for(int i=0;i<n-m+1;i++)
+                       {
+                               int j = i+m-1;
+                               tab[i][j] = 0;
+                               if (i<j)
+                               { 
+                                       tab[i][j] = Math.max(tab[i][j], tab[i+1][j]); 
+                                       for (int k=i+1;k<=j;k++)
+                                       {
+                                               if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                               {
+                                                       double fact1 = 0;
+                                                       if (k>i+1)
+                                                       {
+                                                               fact1 = tab[i+1][k-1];
+                                                       }
+                                                       double fact2 = 0;
+                                                       if (k<j)
+                                                       {
+                                                               fact2 = tab[k+1][j];
+                                                       }
+                                                       tab[i][j] = Math.max(tab[i][j],basePairScore(seq.charAt(i),seq.charAt(k))+fact1+fact2);
+                                               } 
+                                       }
+                               }
+                       }                       
+               }
+               return tab;
+       }
+
+       public static ArrayList<Double> combine(double bonus, ArrayList<Double> part1, ArrayList<Double> part2)
+       {
+               ArrayList<Double> base = new ArrayList<Double>();
+               for(double d1: part1)
+               {
+                       for(double d2: part2)
+                       {
+                               base.add(bonus+d1+d2);
+                       }
+               }
+               return base;
+       }
+
+       public static ArrayList<Double> selectBests(ArrayList<Double> base)
+       {
+               ArrayList<Double> result = new ArrayList<Double>();
+               double best = Double.NEGATIVE_INFINITY;
+               for(double val: base)
+               {
+                       best = Math.max(val, best);
+               }
+               for(double val: base)
+               {
+                       if (val == best)
+                               result.add(val);
+               }
+               return result;
+       }
+
+       
+       private ArrayList<String> backtrack(double[][] tab, String seq)
+       {
+               return backtrack(tab,seq, 0, seq.length()-1);
+       }
+
+       private ArrayList<String> backtrack(double[][] tab, String seq, int i, int j)
+       {
+               ArrayList<String> result = new ArrayList<String>();
+               if (i<j)
+               { 
+                       ArrayList<Integer> indices = new ArrayList<Integer>();
+                       indices.add(-1);
+                       for (int k=i+1;k<=j;k++)
+                       {
+                               indices.add(k);
+                       }
+                       for (int k : indices)
+                       {
+                               if (k==-1)
+                               {
+                                       if (tab[i][j] == tab[i+1][j])
+                                       {
+                                               for (String s:backtrack(tab, seq, i+1,j))
+                                               {
+                                                       result.add("."+s);
+                                               }
+                                       }
+                               }
+                               else
+                               {
+                                       if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                       {
+                                               double fact1 = 0;
+                                               if (k>i+1)
+                                               {
+                                                       fact1 = tab[i+1][k-1];
+                                               }
+                                               double fact2 = 0;
+                                               if (k<j)
+                                               {
+                                                       fact2 = tab[k+1][j];
+                                               }
+                                               if (tab[i][j]==basePairScore(seq.charAt(i),seq.charAt(k))+fact1+fact2)
+                                               { 
+                                                       for (String s1:backtrack(tab, seq, i+1,k-1))
+                                                       {
+                                                               for (String s2:backtrack(tab, seq, k+1,j))
+                                                               {
+                                                                       result.add("("+s1+")"+s2);
+                                                               }
+                                                       }
+                                               }
+                                       }                                       
+                               }                               
+                       }
+               }
+               else if  (i==j)
+               {
+                       result.add(".");
+               }
+               else 
+               {
+                       result.add("");
+               }
+               return result;
+       }
+       
+       public BigInteger count(String seq)
+       {
+               int n = seq.length();
+               
+               BigInteger[][] tab = new BigInteger[n][n];
+               for(int m=1;m<=n;m++)
+               {
+                       for(int i=0;i<n-m+1;i++)
+                       {
+                               int j = i+m-1;
+                               tab[i][j] = BigInteger.ZERO;
+                               if (i<j)
+                               { 
+                                       tab[i][j] = tab[i][j].add(tab[i+1][j]); 
+                                       for (int k=i+1;k<=j;k++)
+                                       {
+                                               if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                               {
+                                                       BigInteger fact1 = BigInteger.ONE;
+                                                       if (k>i+1)
+                                                       {
+                                                               fact1 = tab[i+1][k-1];
+                                                       }
+                                                       BigInteger fact2 = BigInteger.ONE;
+                                                       if (k<j)
+                                                       {
+                                                               fact2 = tab[k+1][j];
+                                                       }
+                                                       tab[i][j] = tab[i][j].add(fact1.multiply(fact2));
+                                               } 
+                                       }
+                               }
+                               else
+                               {
+                                       tab[i][j] = BigInteger.ONE;
+                               }
+                       }                       
+               }
+               return tab[0][n-1];
+       }
+       
+       private String _cache = "";
+       ArrayList<String> _cacheStructs = new ArrayList<String>(); 
+       
+       public ArrayList<String> getStructs() {
+               String seq = getSeq();
+               seq = seq.toUpperCase();
+               if (!_cache.equals(seq))
+               {
+                       double[][] mfe = fillMatrix(seq);
+                       _cacheStructs = backtrack(mfe,seq);
+                       _cache = seq;
+               }
+               return _cacheStructs;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vpMaster;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vpMaster = surface;
+       }
+
+
+       public JLabel get_info() {
+               return _actions;
+       }
+
+       public void set_info(JLabel _info) {
+               this._actions = _info;
+       }
+
+       public static void main(String[] args) {
+               NussinovDemo d = new NussinovDemo();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+       public void onStructureRedrawn() {
+               _vpMaster.repaint();
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoad(String path) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoaded() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       static final String[] _bases =     {"A","C","G","U"};
+       static final String[] _basesComp = {"U","G","C","A"};
+       
+       public void onBaseClicked(ModeleBase mb, MouseEvent e) {
+       }
+       
+       public void onZoomLevelChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onTranslationChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+       private class InfoPanel extends JPanel
+       {
+               ArrayList<String> _sols = new ArrayList<String>();
+               BigInteger _nbFolds = BigInteger.ZERO;
+               JTextArea _text = new JTextArea("");
+               JTextArea _subopts = new JTextArea("");
+               JPanel _suboptBrowser = new JPanel();
+               JPanel _suboptCount = new JPanel();
+               int _selectedIndex = 0;
+               JButton next = new JButton(">");
+               JButton previous = new JButton("<");
+               
+               InfoPanel()
+               {
+                       setLayout(new BorderLayout());
+                       add(_suboptBrowser,BorderLayout.SOUTH);
+                       add(_suboptCount,BorderLayout.NORTH);
+                       
+                       next.addActionListener(new ActionListener(){
+                               public void actionPerformed(ActionEvent arg0) {
+                                       if (_sols.size()>0)
+                                       {
+                                               setSelectedIndex((_selectedIndex+1)%_sols.size());
+                                       }
+                               }                               
+                       });
+
+                       previous.addActionListener(new ActionListener(){
+                               public void actionPerformed(ActionEvent arg0) {
+                                       if (_sols.size()>0)
+                                       {
+                                               setSelectedIndex((_selectedIndex+_sols.size()-1)%_sols.size());
+                                       }
+                               }                               
+                       });
+                       next.setEnabled(false);
+                       previous.setEnabled(false);
+                       
+
+                       JLabel nbLab = new JLabel("#Repliements");
+                       NussinovDemo.formatLabel(nbLab);
+
+                       _suboptCount.setLayout(new BorderLayout());
+                       _suboptCount.add(nbLab,BorderLayout.WEST);
+                       _suboptCount.add(_text,BorderLayout.CENTER);
+
+                       JLabel cooptlab = new JLabel("#Co-optimaux");
+                       NussinovDemo.formatLabel(cooptlab);
+                       
+                       JPanel commands = new JPanel();
+                       commands.add(previous);
+                       commands.add(next);
+                       
+                       JPanel jp = new JPanel();
+                       jp.setLayout(new BorderLayout());
+                       jp.add(_subopts,BorderLayout.WEST);
+                       jp.add(commands,BorderLayout.CENTER);
+
+                       _suboptBrowser.setLayout(new BorderLayout());
+                       _suboptBrowser.add(cooptlab,BorderLayout.WEST);
+                       _suboptBrowser.add(jp,BorderLayout.CENTER);
+                       
+                       
+               }
+                       
+                               
+               public void setSelectedIndex(int i)
+               {
+                       _selectedIndex = i;
+                       RNA rfolded = new RNA();
+                       try {
+                               rfolded.setRNA(getSeq(), _sols.get(i));
+                               rfolded.drawRNARadiate(_vpMaster.getConfig());
+                               rfolded.setBaseNameColor(Color.white);
+                               rfolded.setBaseOutlineColor(Color.white);
+                               rfolded.setBaseNumbersColor(Color.white);
+                               _vpMaster.setBaseNumbersColor(Color.white);
+                               _vpMaster.setBaseOutlineColor(Color.white);
+                               _vpMaster.setFillBases(false);
+                               _vpMaster.setBaseNameColor(Color.white);
+                               _vpMaster.showRNAInterpolated(rfolded);                 
+                               
+                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ExceptionFileFormatOrSyntax e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       _struct.setText(_sols.get(i));
+                       formatDescription();
+               }
+               
+               public void setFont(Font f)
+               {
+                       super.setFont(f);
+                       if(_text!=null)
+                       {
+                               _text.setFont(f);
+                               _text.setOpaque(false);
+                       }
+                       if(_subopts!=null)
+                       {
+                       _subopts.setFont(f);
+                       _subopts.setOpaque(false);
+                       }
+               }
+               public void setInfo(ArrayList<String> sols, BigInteger nbFolds)
+               {
+                       _sols = sols;
+                       _nbFolds = nbFolds;
+                       formatDescription();
+                       setSelectedIndex(0);
+               }
+               
+               private void formatDescription()
+               {
+                       _text.setText(""+_nbFolds);
+                       _subopts.setText(""+_sols.size());
+                       next.setEnabled(_sols.size()>1);
+                       previous.setEnabled(_sols.size()>1);
+                       
+               }
+               
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/NussinovDesignDemo.java b/srcjar/fr/orsay/lri/varna/applications/NussinovDesignDemo.java
new file mode 100644 (file)
index 0000000..9c82a07
--- /dev/null
@@ -0,0 +1,997 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JTextPane;
+import javax.swing.border.BevelBorder;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurInterpolator;
+import fr.orsay.lri.varna.exceptions.ExceptionDrawingAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.VARNAConfig.BP_STYLE;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNABasesListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;;
+
+public class NussinovDesignDemo extends JFrame implements InterfaceVARNAListener,InterfaceVARNABasesListener, ItemListener {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String SEQUENCE_A =        "AGGCACGUCU";
+       private static final String SEQUENCE_B =  "GAGUAGCCUC";
+       private static final String SEQUENCE_C = "GCAUAGCUGC";
+       private static final String SEQUENCE_INRIA = "CGAUUGCAUCGCAAGU";
+       
+       private static final String TARGET_STRUCTURE_1 = "(((((((..)))))))";
+       private static final String TARGET_STRUCTURE_2 = "(((())))(((())))";
+       private static final String TARGET_STRUCTURE_3 = "(.((.((..).)).))";
+       private static final String TARGET_STRUCTURE_4 = "((((((())))(((())(()))))))";
+       private static final String TARGET_STRUCTURE_5 = "(((())))(((())))(((())))(((())))(((())))(((())))";
+
+       private static final String SEQUENCE_BIG = "AAAACAAAAACACCAUGGUGUUUUCACCCAAUUGGGUGAAAACAGAGAUCUCGAGAUCUCUGUUUUUGUUUU"; 
+
+       private static final String DEFAULT_STRUCTURE = "..........";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vpMaster;
+       private VARNAPanel _vpTarget;
+
+       private InfoPanel _infos = new InfoPanel();
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _structPanel = new JPanel();
+       private JLabel _actions = new JLabel();
+       private JComboBox _struct = new JComboBox();
+       private JLabel _seq1 = new JLabel();
+       private JLabel _structLabel = new JLabel("Structure Cible");
+       private JLabel _seqLabel = new JLabel("Sequence d'ARN");
+       private JButton _switchButton = new JButton("Reset");
+
+
+       private Color _backgroundColor = Color.white;
+
+       private Color _okColor = Color.decode("#E33729");
+       private Color _koColor = new Color(250,200,200);
+
+
+       public static ModelBaseStyle createStyle(String txt) 
+       {
+               ModelBaseStyle result = new ModelBaseStyle();
+               try {
+                       result.assignParameters(txt);
+               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionParameterError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               return result;
+       }
+       
+       public void applyTo(VARNAPanel vp, ModelBaseStyle mb, int[] indices)
+       {
+               for(int i=0;i<indices.length;i++)
+               { 
+                       ModeleBase m = vp.getRNA().getBaseAt(indices[i]);
+                       m.setStyleBase(mb);
+                       if (m.getElementStructure()!=-1)
+                       {
+                               vp.getRNA().getBaseAt(m.getElementStructure()).setStyleBase(mb);
+                       }
+               }
+               vp.repaint();
+       }
+       
+       
+       public NussinovDesignDemo() {
+               super();
+               try {
+                       _vpMaster = new VARNAPanel(getSeq(), "");
+                       _vpTarget = new VARNAPanel();
+               } catch (ExceptionNonEqualLength e) {
+                       _vpMaster.errorDialog(e);
+               }
+               _vpMaster.setPreferredSize(new Dimension(600, 600));
+               _vpTarget.setPreferredSize(new Dimension(600, 600));
+               RNAPanelDemoInit();
+       }
+       
+       static Font labelsFont = Font.decode("Dialog-BOLD-25");
+
+       private void RNAPanelDemoInit() {
+               int marginTools = 250;
+               Font textFieldsFont = Font.decode("MonoSpaced-BOLD-16");
+               
+               
+
+               _seq1.setFont(textFieldsFont.deriveFont(25f));
+               
+
+               setBackground(_backgroundColor);
+
+               
+               _structLabel.setFont(labelsFont.deriveFont(Font.PLAIN));
+
+               
+               String[] secstr = {TARGET_STRUCTURE_1,TARGET_STRUCTURE_2,TARGET_STRUCTURE_3,TARGET_STRUCTURE_4,TARGET_STRUCTURE_5};
+               DefaultComboBoxModel cm = new DefaultComboBoxModel(secstr); 
+               _struct.setModel(cm);
+               _struct.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               System.out.println(e.getActionCommand());
+                               setTarget(((JComboBox)e.getSource()).getSelectedItem().toString());
+                       }
+               });
+               _struct.setFont(textFieldsFont);
+               _struct.setEnabled(true);
+               _struct.setEditable(true);
+
+
+               _switchButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               setTarget(_struct.getSelectedItem().toString());                
+                       }
+               });
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq1, BorderLayout.CENTER);
+
+               _seqPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+               _structLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _structLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _structPanel.setLayout(new BorderLayout());
+               _structPanel.add(_structLabel, BorderLayout.WEST);
+               _structPanel.add(_struct, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(0, 1));
+               _input.add(_structPanel);
+               
+               JPanel goPanel = new JPanel();
+               goPanel.setLayout(new BorderLayout());
+
+               _infos.setFont(labelsFont);
+
+
+               formatLabel(_seqLabel);
+               formatLabel(_seq1);
+
+               
+               _tools.setLayout(new BorderLayout());
+               //_tools.add(_infos, BorderLayout.NORTH);
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_actions, BorderLayout.SOUTH);
+               _tools.add(goPanel, BorderLayout.EAST);
+               
+               _tools.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+               //goPanel.add(_goButton, BorderLayout.CENTER);
+               goPanel.add(_switchButton, BorderLayout.CENTER);
+
+               getContentPane().setLayout(new BorderLayout());
+               JSplitPane VARNAs = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+               VARNAs.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
+               //VARNAs.setLayout(new GridLayout(1,2));
+               JPanel mast2 = new JPanel();
+               mast2.setLayout(new BorderLayout());
+               mast2.add(_seqPanel, BorderLayout.NORTH);
+               mast2.add(_infos, BorderLayout.SOUTH);
+
+               JPanel mast = new JPanel();
+               mast.setLayout(new BorderLayout());
+               mast.add(_vpMaster, BorderLayout.CENTER);
+               mast.add(mast2,BorderLayout.SOUTH);
+               
+               VARNAs.add(mast);
+               VARNAs.add(_vpTarget);
+               VARNAs.doLayout();
+               VARNAs.setDividerSize(5);
+               
+               getContentPane().add(VARNAs, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.NORTH);
+
+               setVisible(true);
+
+
+               _vpMaster.setBackground(_backgroundColor);
+               _vpMaster.addVARNAListener(this);
+               _vpMaster.setTitle("Meilleur repliement - Séquence courante");
+               _vpMaster.setBPStyle(BP_STYLE.SIMPLE);
+               _vpMaster.getVARNAUI().UIRadiate();
+               _vpMaster.setTitleFontSize(26f);
+               _vpMaster.setTitleFontStyle(Font.PLAIN);
+               
+               _vpTarget.setBackground(Color.decode("#308099"));
+               _vpTarget.setModifiable(false);
+               _vpTarget.setTitle("Repliement cible");
+               _vpTarget.setBPStyle(BP_STYLE.SIMPLE);
+               _vpTarget.setBackboneColor(Color.white);
+               _vpTarget.setDefaultBPColor(Color.white);
+               _vpTarget.setBaseNumbersColor(Color.white);
+               _vpTarget.setBaseOutlineColor(Color.white);
+               _vpTarget.setTitleColor(Color.white);           
+               _vpTarget.setTitleFontSize(26f);
+
+               
+               _okColor = Color.decode("#F39126");
+               _koColor = new Color(250,200,200);
+               
+               _seqPanel.setBackground(Color.decode("#E33729"));
+               _infos.setBackground(Color.decode("#E33729"));
+
+
+               
+               _vpMaster.addVARNABasesListener(this);
+               setTitle("Fête de la science 2015 - Inria AMIB - Design d'ARN");
+               
+               setTarget(secstr[0]);
+               
+       }
+
+       private synchronized void showSolution()
+       {
+               ArrayList<String> sols = getStructs();
+               _infos.setInfo(sols, count(getSeq()));
+               if ((sols.size()==1)&&(sols.get(0).equals(_struct.getSelectedItem().toString())))
+               {
+                       /*JOptionPane.showMessageDialog(null, 
+                                       "Vous avez trouvé une séquence pour cette structure !!!\n Saurez vous faire le design de molécules plus complexes ?",
+                                       "Félicitations !", 
+                                       JOptionPane.INFORMATION_MESSAGE);*/
+                       
+               }
+               else
+               {
+                       this._vpMaster.setTitle("Meilleur repliement - Séquence courante");
+               }
+       }
+       
+
+       public void setTarget(String target)
+       {
+               try {
+                       _vpTarget.drawRNA(String.format("%"+target.length()+"s", ""),target);
+                       _vpTarget.setBaseNumbersColor(Color.white);
+                       _vpTarget.setBaseOutlineColor(Color.white);
+                       //_vpTarget.toggleDrawOutlineBases();
+                       createDummySeq();
+                       showSolution();
+                       onStructureRedrawn();
+               } catch (ExceptionNonEqualLength e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+       
+       private void createDummySeq()
+       {
+               RNA r = _vpTarget.getRNA();
+               String seq = new String();
+               for (int i=0;i<r.getSize();i++)
+               {
+                       seq += 'A';
+               }
+               try {
+               RNA rn = new RNA();
+                       rn.setRNA(seq,r.getStructDBN());
+               for(ModeleBP mbp: r.getAllBPs())
+               {
+                       rn.getBaseAt(mbp.getIndex5()).setContent("A"); 
+                       rn.getBaseAt(mbp.getIndex3()).setContent("U"); 
+               }
+               _vpMaster.drawRNA(rn);
+               _vpMaster.repaint();
+               _seq1.setText(_vpMaster.getRNA().getSeq());
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+
+       public String getSeq()
+       {
+               return (""+_seq1.getText()).toUpperCase();
+       }
+
+       
+       private boolean canBasePairAll(char a, char b)
+       {
+               return true;
+       }
+
+       private boolean canBasePairBasic(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreBasic(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return 1.0;
+               if ((a=='C')&&(b=='G'))
+                       return 1.0;
+               if ((a=='U')&&(b=='A'))
+                       return 1.0;
+               if ((a=='A')&&(b=='U'))
+                       return 1.0;
+               return Double.NEGATIVE_INFINITY;
+       }
+
+       
+       private boolean canBasePairNussinov(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               if ((a=='U')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='U'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreNussinov(char a, char b)
+       {
+               if ((a=='G')&&(b=='C'))
+                       return 3.0;
+               if ((a=='C')&&(b=='G'))
+                       return 3.0;
+               if ((a=='U')&&(b=='A'))
+                       return 2.0;
+               if ((a=='A')&&(b=='U'))
+                       return 2.0;
+               if ((a=='U')&&(b=='G'))
+                       return 1.0;
+               if ((a=='G')&&(b=='U'))
+                       return 1.0;
+               return Double.NEGATIVE_INFINITY;
+       }
+
+       private boolean canBasePairINRIA(char a, char b)
+       {
+               if ((a=='U')&&(b=='A'))
+                       return true;
+               if ((a=='A')&&(b=='U'))
+                       return true;
+               if ((a=='G')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='G'))
+                       return true;
+
+               if ((a=='A')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='A'))
+                       return true;
+               if ((a=='U')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='U'))
+                       return true;
+               if ((a=='A')&&(b=='A'))
+                       return true;
+               if ((a=='U')&&(b=='U'))
+                       return true;
+
+               if ((a=='U')&&(b=='G'))
+                       return true;
+               if ((a=='G')&&(b=='U'))
+                       return true;
+               if ((a=='A')&&(b=='C'))
+                       return true;
+               if ((a=='C')&&(b=='A'))
+                       return true;
+               return false;
+       }
+
+       private double basePairScoreINRIA(char a, char b)
+       {
+               if ((a=='U')&&(b=='A'))
+                       return 3;
+               if ((a=='A')&&(b=='U'))
+                       return 3;
+               if ((a=='G')&&(b=='C'))
+                       return 3;
+               if ((a=='C')&&(b=='G'))
+                       return 3;
+
+               if ((a=='A')&&(b=='G'))
+                       return 2;
+               if ((a=='G')&&(b=='A'))
+                       return 2;
+               if ((a=='U')&&(b=='C'))
+                       return 2;
+               if ((a=='C')&&(b=='U'))
+                       return 2;
+               if ((a=='A')&&(b=='A'))
+                       return 2;
+               if ((a=='U')&&(b=='U'))
+                       return 2;
+
+               if ((a=='U')&&(b=='G'))
+                       return 1;
+               if ((a=='G')&&(b=='U'))
+                       return 1;
+               if ((a=='A')&&(b=='C'))
+                       return 1;
+               if ((a=='C')&&(b=='A'))
+                       return 1;
+               return Double.NEGATIVE_INFINITY;
+       }
+       
+       private boolean canBasePair(char a, char b)
+       {
+               return canBasePairBasic(a,b);
+               //return canBasePairNussinov(a,b);
+               //return canBasePairINRIA(a,b);
+       }
+       
+       private double basePairScore(char a, char b)
+       {
+               return basePairScoreBasic(a,b);
+               //return basePairScoreNussinov(a,b);
+               //return basePairScoreINRIA(a,b);
+       }
+       
+       public double[][] fillMatrix(String seq)
+       {
+               int n = seq.length();
+               double[][] tab = new double[n][n];
+               for(int m=1;m<=n;m++)
+               {
+                       for(int i=0;i<n-m+1;i++)
+                       {
+                               int j = i+m-1;
+                               tab[i][j] = 0;
+                               if (i<j)
+                               { 
+                                       tab[i][j] = Math.max(tab[i][j], tab[i+1][j]); 
+                                       for (int k=i+1;k<=j;k++)
+                                       {
+                                               if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                               {
+                                                       double fact1 = 0;
+                                                       if (k>i+1)
+                                                       {
+                                                               fact1 = tab[i+1][k-1];
+                                                       }
+                                                       double fact2 = 0;
+                                                       if (k<j)
+                                                       {
+                                                               fact2 = tab[k+1][j];
+                                                       }
+                                                       tab[i][j] = Math.max(tab[i][j],basePairScore(seq.charAt(i),seq.charAt(k))+fact1+fact2);
+                                               } 
+                                       }
+                               }
+                       }                       
+               }
+               return tab;
+       }
+
+       public static ArrayList<Double> combine(double bonus, ArrayList<Double> part1, ArrayList<Double> part2)
+       {
+               ArrayList<Double> base = new ArrayList<Double>();
+               for(double d1: part1)
+               {
+                       for(double d2: part2)
+                       {
+                               base.add(bonus+d1+d2);
+                       }
+               }
+               return base;
+       }
+
+       public static ArrayList<Double> selectBests(ArrayList<Double> base)
+       {
+               ArrayList<Double> result = new ArrayList<Double>();
+               double best = Double.NEGATIVE_INFINITY;
+               for(double val: base)
+               {
+                       best = Math.max(val, best);
+               }
+               for(double val: base)
+               {
+                       if (val == best)
+                               result.add(val);
+               }
+               return result;
+       }
+
+       
+       private ArrayList<String> backtrack(double[][] tab, String seq)
+       {
+               return backtrack(tab,seq, 0, seq.length()-1);
+       }
+
+       private ArrayList<String> backtrack(double[][] tab, String seq, int i, int j)
+       {
+               ArrayList<String> result = new ArrayList<String>();
+               if (i<j)
+               { 
+                       ArrayList<Integer> indices = new ArrayList<Integer>();
+                       indices.add(-1);
+                       for (int k=i+1;k<=j;k++)
+                       {
+                               indices.add(k);
+                       }
+                       for (int k : indices)
+                       {
+                               if (k==-1)
+                               {
+                                       if (tab[i][j] == tab[i+1][j])
+                                       {
+                                               for (String s:backtrack(tab, seq, i+1,j))
+                                               {
+                                                       result.add("."+s);
+                                               }
+                                       }
+                               }
+                               else
+                               {
+                                       if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                       {
+                                               double fact1 = 0;
+                                               if (k>i+1)
+                                               {
+                                                       fact1 = tab[i+1][k-1];
+                                               }
+                                               double fact2 = 0;
+                                               if (k<j)
+                                               {
+                                                       fact2 = tab[k+1][j];
+                                               }
+                                               if (tab[i][j]==basePairScore(seq.charAt(i),seq.charAt(k))+fact1+fact2)
+                                               { 
+                                                       for (String s1:backtrack(tab, seq, i+1,k-1))
+                                                       {
+                                                               for (String s2:backtrack(tab, seq, k+1,j))
+                                                               {
+                                                                       result.add("("+s1+")"+s2);
+                                                               }
+                                                       }
+                                               }
+                                       }                                       
+                               }                               
+                       }
+               }
+               else if  (i==j)
+               {
+                       result.add(".");
+               }
+               else 
+               {
+                       result.add("");
+               }
+               return result;
+       }
+       
+       public BigInteger count(String seq)
+       {
+               int n = seq.length();
+               
+               BigInteger[][] tab = new BigInteger[n][n];
+               for(int m=1;m<=n;m++)
+               {
+                       for(int i=0;i<n-m+1;i++)
+                       {
+                               int j = i+m-1;
+                               tab[i][j] = BigInteger.ZERO;
+                               if (i<j)
+                               { 
+                                       tab[i][j] = tab[i][j].add(tab[i+1][j]); 
+                                       for (int k=i+1;k<=j;k++)
+                                       {
+                                               if (canBasePair(seq.charAt(i),seq.charAt(k)))
+                                               {
+                                                       BigInteger fact1 = BigInteger.ONE;
+                                                       if (k>i+1)
+                                                       {
+                                                               fact1 = tab[i+1][k-1];
+                                                       }
+                                                       BigInteger fact2 = BigInteger.ONE;
+                                                       if (k<j)
+                                                       {
+                                                               fact2 = tab[k+1][j];
+                                                       }
+                                                       tab[i][j] = tab[i][j].add(fact1.multiply(fact2));
+                                               } 
+                                       }
+                               }
+                               else
+                               {
+                                       tab[i][j] = BigInteger.ONE;
+                               }
+                       }                       
+               }
+               return tab[0][n-1];
+       }
+       
+       private String _cache = "";
+       ArrayList<String> _cacheStructs = new ArrayList<String>(); 
+       
+       public ArrayList<String> getStructs() {
+               String seq = getSeq();
+               seq = seq.toUpperCase();
+               if (!_cache.equals(seq))
+               {
+                       double[][] mfe = fillMatrix(seq);
+                       _cacheStructs = backtrack(mfe,seq);
+                       _cache = seq;
+               }
+               return _cacheStructs;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vpMaster;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vpMaster = surface;
+       }
+
+
+       public JLabel get_info() {
+               return _actions;
+       }
+
+       public void set_info(JLabel _info) {
+               this._actions = _info;
+       }
+
+       public static void main(String[] args) {
+               NussinovDesignDemo d = new NussinovDesignDemo();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+       public void onStructureRedrawn() {
+               _vpMaster.repaint();
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoad(String path) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoaded() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       static final String[] _bases =     {"A","C","G","U"};
+       static final String[] _basesComp = {"U","G","C","A"};
+       
+       public void onBaseClicked(ModeleBase mb, MouseEvent e) {
+               int index = -1;
+               for(int i =0;i<_bases.length;i++)
+               {
+                       if (mb.getContent().equalsIgnoreCase(_bases[i]))
+                       {
+                               index = i;
+                       }
+               }
+               index = (index+1)%_bases.length;
+               mb.setContent(_bases[index].toUpperCase());
+               ArrayList<ModeleBase> partners =_vpTarget.getRNA().getAllPartners(mb.getIndex());
+               if (partners.size()!=0)
+               {
+                       ModeleBase mbPartner = _vpMaster.getRNA().getBaseAt(partners.get(0).getIndex());
+                       mbPartner.setContent(_basesComp[index].toUpperCase());
+               }
+               _vpMaster.repaint();
+               _seq1.setText(_vpMaster.getRNA().getSeq());
+               new Temporizer(_vpMaster.getRNA().getSeq()).start();
+       }
+       
+       private class Temporizer extends Thread{
+               String _seq;
+               public Temporizer(String seq)
+               {
+                       _seq = seq;
+               }
+               public void run() {
+            try {
+                               this.sleep(1000);
+                               if (_vpMaster.getRNA().getSeq().equalsIgnoreCase(_seq))
+                               {
+                                       showSolution();
+                               }
+                       } catch (InterruptedException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+        }
+       }
+       public void onZoomLevelChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onTranslationChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void itemStateChanged(ItemEvent arg0) {
+               // TODO Auto-generated method stub
+               System.out.println();
+       }
+
+       
+       public static void formatLabel(JLabel j)
+       {
+               j.setHorizontalTextPosition(JLabel.LEFT);
+               j.setPreferredSize(new Dimension(NussinovDemo.marginTools, 25));
+               j.setFont(labelsFont);
+               j.setForeground(Color.white);
+       }
+       
+       public static void formatLabel(JTextArea j)
+       {
+               j.setPreferredSize(new Dimension(NussinovDemo.marginTools, 25));
+               j.setFont(labelsFont);
+               j.setForeground(Color.white);
+       }
+
+       public class InfoPanel extends JPanel
+       {
+               ArrayList<String> _sols = new ArrayList<String>();
+               BigInteger _nbFolds = BigInteger.ZERO;
+               JLabel _text = new JLabel("");
+               JLabel _subopts = new JLabel("");
+               JPanel _suboptBrowser = new JPanel();
+               JPanel _suboptCount = new JPanel();
+               int _selectedIndex = 0;
+               JButton next = new JButton(">");
+               JButton previous = new JButton("<");
+               
+               InfoPanel()
+               {
+                       this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+                       setLayout(new BorderLayout());
+                       add(_suboptBrowser,BorderLayout.SOUTH);
+                       add(_suboptCount,BorderLayout.NORTH);
+                       
+                       next.addActionListener(new ActionListener(){
+                               public void actionPerformed(ActionEvent arg0) {
+                                       if (_sols.size()>0)
+                                       {
+                                               setSelectedIndex((_selectedIndex+1)%_sols.size());
+                                       }
+                               }                               
+                       });
+
+                       previous.addActionListener(new ActionListener(){
+                               public void actionPerformed(ActionEvent arg0) {
+                                       if (_sols.size()>0)
+                                       {
+                                               setSelectedIndex((_selectedIndex+_sols.size()-1)%_sols.size());
+                                       }
+                               }                               
+                       });
+                       next.setEnabled(false);
+                       previous.setEnabled(false);
+
+                       JLabel nbLab = new JLabel("#Repliements");
+                       NussinovDesignDemo.formatLabel(nbLab);
+                       
+
+                       JLabel cooptlab = new JLabel("#Co-optimaux");
+                       NussinovDesignDemo.formatLabel(cooptlab);
+                       
+                       NussinovDesignDemo.formatLabel(_text);
+                       NussinovDesignDemo.formatLabel(_subopts);
+
+                       
+                       _suboptCount.setLayout(new BorderLayout());
+                       _suboptCount.add(nbLab,BorderLayout.WEST);
+                       _suboptCount.add(_text,BorderLayout.CENTER);
+                       _suboptCount.setBackground(Color.decode("#E33729"));
+
+                       
+                       JPanel commands = new JPanel();
+                       commands.add(previous);
+                       commands.add(next);
+                       commands.setBackground(Color.decode("#E33729"));
+                       
+                       JPanel jp = new JPanel();
+                       jp.setLayout(new BorderLayout());
+                       jp.add(_subopts,BorderLayout.WEST);
+                       jp.add(commands,BorderLayout.CENTER);
+                       jp.setBackground(Color.decode("#E33729"));
+
+                       _suboptBrowser.setLayout(new BorderLayout());
+                       _suboptBrowser.add(cooptlab,BorderLayout.WEST);
+                       _suboptBrowser.add(jp,BorderLayout.CENTER);
+                       _suboptBrowser.setBackground(Color.decode("#E33729"));
+                       
+               }
+                       
+                               
+               /*public void setSelectedIndex(int i)
+               {
+                       _selectedIndex = i;
+                       RNA rfolded = new RNA();
+                       try {
+                               rfolded.setRNA(getSeq(), _sols.get(i));
+                               rfolded.drawRNARadiate(_vpMaster.getConfig());
+                       _vpMaster.showRNAInterpolated(rfolded);
+                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ExceptionFileFormatOrSyntax e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       //_struct.setText(_sols.get(i));
+                       formatDescription();
+               }*/
+               
+               public void setSelectedIndex(int i)
+               {
+                       _selectedIndex = i;
+                       RNA rfolded = new RNA();
+                       try {
+                               rfolded.setRNA(getSeq(), _sols.get(i));
+                               RNA target = _vpTarget.getRNA();
+                               for(ModeleBase mb: rfolded.get_listeBases())
+                               {
+                                       ModeleBase mbref = target.getBaseAt(mb.getIndex());
+                                       if (mb.getElementStructure()==mbref.getElementStructure())
+                                       {
+                                               mb.getStyleBase().setBaseInnerColor(_okColor);
+                                               mb.getStyleBase().setBaseNameColor(Color.white);
+                                       }
+                               }                               
+                               for(ModeleBase mb: target.get_listeBases())
+                               {
+                                       ModeleBase mbref = rfolded.getBaseAt(mb.getIndex());
+                                       if (mb.getElementStructure()==mbref.getElementStructure())
+                                       {
+                                               mb.getStyleBase().setBaseInnerColor(_okColor);
+                                       }
+                                       else
+                                       {
+                                               mb.getStyleBase().setBaseInnerColor(Color.white);
+                                       }
+                               }                               
+                               rfolded.drawRNARadiate(_vpMaster.getConfig());
+                               if ((_sols.size()==1)&& (target.getStructDBN().equals(_sols.get(0))))                                   
+                                       rfolded.setName("Félicitations !");
+                               else
+                                       rfolded.setName("Repliement stable - "+(i+1)+"/"+_sols.size());
+                       _vpMaster.showRNAInterpolated(rfolded);
+                       _vpTarget.repaint();
+                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ExceptionFileFormatOrSyntax e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       //_struct.setSelectedItem(_sols.get(i));
+                       formatDescription();
+               }
+               
+//             public void setFont(Font f)
+//             {
+//                     super.setFont(f);
+//                     if(_text!=null)
+//                     {
+//                             _text.setFont(f);
+//                             _text.setOpaque(false);
+//                     }
+//                     if(_subopts!=null)
+//                     {
+//                     _subopts.setFont(f);
+//                     _subopts.setOpaque(false);
+//                     }
+//             }
+               public void setInfo(ArrayList<String> sols, BigInteger nbFolds)
+               {
+                       _sols = sols;
+                       _nbFolds = nbFolds;
+                       formatDescription();
+                       setSelectedIndex(0);
+               }
+               
+               private void formatDescription()
+               {
+                       _text.setText(""+_nbFolds);
+                       _subopts.setText(""+_sols.size());
+                       next.setEnabled(_sols.size()>1);
+                       previous.setEnabled(_sols.size()>1);
+                       
+               }
+               
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/SecStrConsensus.java b/srcjar/fr/orsay/lri/varna/applications/SecStrConsensus.java
new file mode 100644 (file)
index 0000000..5fce12b
--- /dev/null
@@ -0,0 +1,189 @@
+package fr.orsay.lri.varna.applications;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public class SecStrConsensus {
+       
+       
+       /**
+        * Internal class to represent a simple base-pair.
+        * @author Yawn
+        *
+        */
+       static class SimpleBP{
+               int bp5;
+               int bp3;
+               
+               public SimpleBP(int i5, int i3)
+               {
+                       bp5=i5;
+                       bp3=i3;
+               }
+       }
+       
+       public static int[] extractConsensus(ArrayList<ArrayList<SimpleBP>> bps)
+       {
+               // We do not currently know the length of the alignment
+               // => Estimate it as the biggest index of a base-pair plus one. 
+               int maxlength = 0;
+               for (ArrayList<SimpleBP> strs : bps)
+               {
+                       for (SimpleBP bp : strs)
+                       {
+                               maxlength = Math.max(1+Math.max(bp.bp5, bp.bp3), maxlength);
+                       }
+               }
+               // Now we have a good estimate for length, allocate and initialize data
+               // to be fed to the dynamic programming procedure.
+               ArrayList<Hashtable<Integer,Double>> seq = new ArrayList<Hashtable<Integer,Double>>();
+               for (int i=0;i<seq.size();i++)
+               { seq.add(new Hashtable<Integer,Double>()); }
+               for (ArrayList<SimpleBP> strs : bps)
+               {
+                       for (SimpleBP bp : strs)
+                       {
+                               int i = bp.bp5;
+                               int j = bp.bp3;
+                               Hashtable<Integer,Double> h = seq.get(i);
+                               if (!h.containsKey(j))
+                               {
+                                       h.put(j, 0.0);
+                               }
+                               h.put(j, h.get(i)+1.);
+                       }
+               }
+               // At this point, seq contains, at each position i, a hashtable which associates,
+               // to each possible end j, the number of time a base-pair (i,j) occurs in the alignment
+               
+               // We can now run the dynamic programming procedure on this data
+               double[][] mat = fillMatrix(seq); 
+               ArrayList<SimpleBP> res = backtrack(mat,seq);
+               
+               // Convert it to an array, ie finalres[i] = j >= 0 iff a base-pair (i,j) is present 
+               // in the consensus, or -1 otherwise
+               int[] finalres = new int[seq.size()];
+               for (int i=0;i<seq.size();i++)
+               { finalres[i] = -1; }
+               for (SimpleBP bp : res)
+               {
+                       finalres[bp.bp5] = bp.bp3;
+                       finalres[bp.bp3] = bp.bp5;
+               }
+               
+               return finalres;
+       }
+       
+       private static boolean canBasePair(ArrayList<Hashtable<Integer,Double>> seq, int i, int k)
+       {
+               return seq.get(i).containsKey(k);
+       }
+       
+       // Returns the score of a potential base-pair, ie the number of structures in which it is found.
+       private static double basePairScore(ArrayList<Hashtable<Integer,Double>> seq, int i, int k)
+       {
+               return seq.get(i).get(k);       
+       }
+       
+       
+       private static double[][] fillMatrix(ArrayList<Hashtable<Integer,Double>> seq)
+       {
+               int n = seq.size();
+               double[][] tab = new double[n][n];
+               for(int m=1;m<=n;m++)
+               {
+                       for(int i=0;i<n-m+1;i++)
+                       {
+                               int j = i+m-1;
+                               tab[i][j] = 0;
+                               if (i<j)
+                               { 
+                                       tab[i][j] = Math.max(tab[i][j], tab[i+1][j]); 
+                                       for (int k=i+1;k<=j;k++)
+                                       {
+                                               if (canBasePair(seq,i,k))
+                                               {
+                                                       double fact1 = 0;
+                                                       if (k>i+1)
+                                                       {
+                                                               fact1 = tab[i+1][k-1];
+                                                       }
+                                                       double fact2 = 0;
+                                                       if (k<j)
+                                                       {
+                                                               fact2 = tab[k+1][j];
+                                                       }
+                                                       tab[i][j] = Math.max(tab[i][j],basePairScore(seq,i,k)+fact1+fact2);
+                                               } 
+                                       }
+                               }
+                       }                       
+               }
+               return tab;
+       }       
+       
+       private static  ArrayList<SimpleBP> backtrack(double[][] tab,ArrayList<Hashtable<Integer,Double>> seq)
+       {
+               return backtrack(tab,seq,0,seq.size()-1);
+       }
+       
+       private static ArrayList<SimpleBP> backtrack(double[][] tab,ArrayList<Hashtable<Integer,Double>> seq, int i, int j)
+       {
+               ArrayList<SimpleBP> result = new ArrayList<SimpleBP>();
+               if (i<j)
+               { 
+                       ArrayList<Integer> indices = new ArrayList<Integer>();
+                       indices.add(-1);
+                       for (int k=i+1;k<=j;k++)
+                       {
+                               indices.add(k);
+                       }
+                       for (int k : indices)
+                       {
+                               if (k==-1)
+                               {
+                                       if (tab[i][j] == tab[i+1][j])
+                                       {
+                                               result = backtrack(tab, seq, i+1,j);
+                                       }
+                               }
+                               else
+                               {
+                                       if (canBasePair(seq,i,k))
+                                       {
+                                               double fact1 = 0;
+                                               if (k>i+1)
+                                               {
+                                                       fact1 = tab[i+1][k-1];
+                                               }
+                                               double fact2 = 0;
+                                               if (k<j)
+                                               {
+                                                       fact2 = tab[k+1][j];
+                                               }
+                                               if (tab[i][j]==basePairScore(seq,i,k)+fact1+fact2)
+                                               { 
+                                                       result = backtrack(tab, seq, i+1,k-1);
+                                                       result.addAll(backtrack(tab, seq, k+1,j));
+                                                       result.add(new SimpleBP(i,k));
+                                               }
+                                       }                                       
+                               }                               
+                       }
+               }
+               else if  (i==j)
+               {
+                       
+               }
+               else 
+               {
+                       
+               }
+               return result;
+       }
+       
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/SuperpositionDemo.java b/srcjar/fr/orsay/lri/varna/applications/SuperpositionDemo.java
new file mode 100644 (file)
index 0000000..edef438
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurInterpolator;
+import fr.orsay.lri.varna.exceptions.ExceptionDrawingAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;;
+
+public class SuperpositionDemo extends JFrame implements InterfaceVARNAListener {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE1 = "CGCGCACGCGAUAUUUCGCGUCGCGCAUUUGCGCGUAGCGCG";
+       private static final String DEFAULT_SEQUENCE2 = "CGCGCACGCGAUAUUUCGCGUCGCGCAUUUGCGCGUAGCGCG";
+
+       private static final String DEFAULT_STRUCTURE1 = "(((((.(((((----....----))))).(((((....)))))..)))))";
+       private static final String DEFAULT_STRUCTURE2 = "(((((.(((((((((....))))))))).--------------..)))))";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vpMaster;
+       private VARNAPanel _vpSlave;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _struct1Panel = new JPanel();
+       private JPanel _struct2Panel = new JPanel();
+       private JLabel _info = new JLabel();
+       private JTextField _struct1 = new JTextField(DEFAULT_STRUCTURE1);
+       private JTextField _struct2 = new JTextField(DEFAULT_STRUCTURE2);
+       private JTextField _seq1 = new JTextField(DEFAULT_SEQUENCE1);
+       private JTextField _seq2 = new JTextField(DEFAULT_SEQUENCE2);
+       private JLabel _struct1Label = new JLabel(" Str1:");
+       private JLabel _struct2Label = new JLabel(" Str2:");
+       private JLabel _seqLabel = new JLabel(" Seq:");
+       private JButton _goButton = new JButton("Go");
+       private JButton _switchButton = new JButton("Switch");
+
+       private String _str1Backup = "";
+       private String _str2Backup = "";
+       private RNA _RNA1 = new RNA();
+       private RNA _RNA2 = new RNA();
+
+       private static String errorOpt = "error";
+       @SuppressWarnings("unused")
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+
+       @SuppressWarnings("unused")
+       private int _algoCode;
+
+       private int _currentDisplay = 1;
+
+       public static ModelBaseStyle createStyle(String txt) 
+       {
+               ModelBaseStyle result = new ModelBaseStyle();
+               try {
+                       result.assignParameters(txt);
+               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionParameterError e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               return result;
+       }
+       
+       public void applyTo(VARNAPanel vp, ModelBaseStyle mb, int[] indices)
+       {
+               for(int i=0;i<indices.length;i++)
+               { 
+                       ModeleBase m = vp.getRNA().getBaseAt(indices[i]);
+                       m.setStyleBase(mb);
+                       if (m.getElementStructure()!=-1)
+                       {
+                               vp.getRNA().getBaseAt(m.getElementStructure()).setStyleBase(mb);
+                       }
+               }
+               vp.repaint();
+       }
+       
+       
+       public SuperpositionDemo() {
+               super();
+               try {
+                       _vpMaster = new VARNAPanel(getText1(), getStruct1());
+                       _vpSlave = new VARNAPanel(getText2(), getStruct2());
+               } catch (ExceptionNonEqualLength e) {
+                       _vpMaster.errorDialog(e);
+               }
+               _vpMaster.setPreferredSize(new Dimension(400, 400));
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() {
+               int marginTools = 40;
+
+               setBackground(_backgroundColor);
+               _vpMaster.setBackground(_backgroundColor);
+               _vpMaster.addVARNAListener(this);
+               //_vpSlave.setModifiable(false);
+               _vpSlave.setBackground(Color.decode("#F0F0F0"));
+
+               _vpMaster.drawRNA(getRNA((_currentDisplay)%2));
+               _vpSlave.drawRNA(getRNA((_currentDisplay+1)%2));
+
+               /*ModeleStyleBase red = createStyle("label=#ff4d4d,fill=#ffdddd,outline=#ff4d4d");
+               int[] ired = {6,7,8,9,10};
+               applyTo(_vpMaster, red, ired);
+               applyTo(_vpSlave, red, ired);
+               ModeleStyleBase blue = createStyle("label=#0000ff,fill=#ddddff,outline=#0000ff");
+               int[] iblue = {0,1,2,3,4};
+               applyTo(_vpMaster, blue, iblue);
+               applyTo(_vpSlave, blue, iblue);
+               ModeleStyleBase purple = createStyle("label=#cc00cc,fill=#ffddff,outline=#cc00cc");
+               int[] ipurple = {21,22,23,24,25};
+               applyTo(_vpMaster, purple, ipurple);
+               ModeleStyleBase green = createStyle("label=#009900,fill=#aaeeaa,outline=#009900");
+               int[] igreen = {11,12,13,14};
+               applyTo(_vpSlave, green, igreen);*/
+               
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               _seqLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _seqLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _seq1.setFont(textFieldsFont);
+               _seq1.setText(getRNA1().getSeq());
+               _seq2.setFont(textFieldsFont);
+               _seq2.setText(getRNA2().getSeq());
+
+               _goButton.addActionListener(new ActionListener() {
+
+                       public void actionPerformed(ActionEvent e) {
+                               _currentDisplay = (_currentDisplay + 1) % 2;
+                               _vpMaster.drawRNA(getRNA(_currentDisplay));
+                               _vpSlave.drawRNA(getRNA((_currentDisplay + 1) % 2));
+                               onStructureRedrawn();
+                       }
+               });
+
+               _switchButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               try {
+                                       _currentDisplay = (_currentDisplay + 1) % 2;
+
+                                                       Mapping m = Mapping.readMappingFromAlignment(
+                                                                       getStruct(_currentDisplay), getStruct((_currentDisplay+1)%2));
+                                                       Mapping m2 = Mapping.readMappingFromAlignment(
+                                                                       getStruct((_currentDisplay+1)%2), getStruct(_currentDisplay));
+                                                       _vpMaster.showRNAInterpolated(getRNA(_currentDisplay), m);
+                                                       _vpSlave.showRNAInterpolated(getRNA((_currentDisplay+1)%2), m2);
+                                                       onStructureRedrawn();
+                                       } 
+                               catch (MappingException e3) 
+                               {
+                                                       try {
+                                                               _vpMaster.drawRNAInterpolated(getText(_currentDisplay),getStruct(_currentDisplay));
+                                                       } catch (ExceptionNonEqualLength e1) {
+                                                               // TODO Auto-generated catch block
+                                                               e1.printStackTrace();
+                                                       }
+                                               }
+                               _vpMaster.repaint();
+                               _vpSlave.repaint();
+                       }
+               });
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq1, BorderLayout.CENTER);
+
+               _struct1Label.setPreferredSize(new Dimension(marginTools, 15));
+               _struct1Label.setHorizontalTextPosition(JLabel.LEFT);
+               _struct1.setFont(textFieldsFont);
+               _struct1Panel.setLayout(new BorderLayout());
+               _struct1Panel.add(_struct1Label, BorderLayout.WEST);
+               _struct1Panel.add(_struct1, BorderLayout.CENTER);
+
+               _struct2Label.setPreferredSize(new Dimension(marginTools, 15));
+               _struct2Label.setHorizontalTextPosition(JLabel.LEFT);
+               _struct2.setFont(textFieldsFont);
+               _struct2Panel.setLayout(new BorderLayout());
+               _struct2Panel.add(_struct2Label, BorderLayout.WEST);
+               _struct2Panel.add(_struct2, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(3, 0));
+               _input.add(_seqPanel);
+               _input.add(_struct1Panel);
+               _input.add(_struct2Panel);
+
+               JPanel goPanel = new JPanel();
+               goPanel.setLayout(new BorderLayout());
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+               _tools.add(goPanel, BorderLayout.EAST);
+
+               goPanel.add(_goButton, BorderLayout.CENTER);
+               goPanel.add(_switchButton, BorderLayout.SOUTH);
+
+               getContentPane().setLayout(new BorderLayout());
+               JPanel VARNAs = new JPanel();
+               VARNAs.setLayout(new GridLayout(1,2));
+               VARNAs.add(_vpMaster);
+               VARNAs.add(_vpSlave);
+               getContentPane().add(VARNAs, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.SOUTH);
+
+               setVisible(true);
+
+               _vpMaster.getVARNAUI().UIRadiate();
+               _vpSlave.getVARNAUI().UIRadiate();              
+               
+               onStructureRedrawn();
+       }
+
+       public RNA getMasterRNA()
+       {
+               return getRNA(_currentDisplay);
+       }
+
+       public RNA getSlaveRNA1()
+       {
+               return getRNA((_currentDisplay+1)%2);
+       }
+
+       public RNA getSlaveRNA2()
+       {
+               return getRNA((_currentDisplay+2)%2);
+       }
+
+       
+       public RNA getRNA(int i) {
+         if (i==0)
+         {  return getRNA1();  }
+         else 
+         {  return getRNA2();  }
+       }
+
+       
+       public RNA getRNA1() {
+               if (!_str1Backup.equals(getStruct1())) {
+                       try {
+                               _RNA1.setRNA(getText1(), getStruct1());
+                               _RNA1.drawRNA(_vpMaster.getDrawMode(),_vpMaster.getConfig());
+                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                               e.printStackTrace();
+                       } catch (ExceptionFileFormatOrSyntax e1) {
+                               _vpMaster.errorDialog(e1);
+                       } catch (ExceptionDrawingAlgorithm e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       _str1Backup = getStruct1();
+               }
+               return _RNA1;
+       }
+
+       public RNA getRNA2() {
+               if (!_str2Backup.equals(getStruct2())) {
+                       try {
+                               _RNA2.setRNA(getText2(), getStruct2());
+                               _RNA2.drawRNA(_vpMaster.getDrawMode(),_vpMaster.getConfig());
+                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                               e.printStackTrace();
+                       } catch (ExceptionFileFormatOrSyntax e1) {
+                               _vpMaster.errorDialog(e1);
+                       } catch (ExceptionDrawingAlgorithm e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       _str2Backup = getStruct2();
+               }
+               return _RNA2;
+       }
+
+    public String getText(int i)
+    {
+       return "";
+    }
+
+    public String getStruct(int i)
+    {
+       if (i==0) 
+               return _struct1.getText();
+       else
+               return _struct2.getText();
+    }
+
+    
+       public String getText1()
+       {
+               return _seq1.getText();
+       }
+
+       public String getText2()
+       {
+               return _seq2.getText();
+       }
+
+       public String getStruct1() {
+               return cleanStruct(_struct1.getText());
+       }
+
+       public String getStruct2() {
+               return cleanStruct(_struct2.getText());
+       }
+
+       private String cleanStruct(String struct) {
+               struct = struct.replaceAll("[:-]", "");
+               return struct;
+       }
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               _vpMaster.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       @SuppressWarnings("unused")
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vpMaster;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vpMaster = surface;
+       }
+
+       public JTextField get_struct() {
+               return _struct1;
+       }
+
+       public void set_struct(JTextField _struct) {
+               this._struct1 = _struct;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+
+       public static void main(String[] args) {
+               SuperpositionDemo d = new SuperpositionDemo();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+       public void onStructureRedrawn() {
+               try{
+                                       Mapping m = Mapping.readMappingFromAlignment(
+                                                       this.getStruct((_currentDisplay+1)%2), getStruct((_currentDisplay)%2));
+                                       ControleurInterpolator.moveNearOtherRNA(getRNA((_currentDisplay)%2), getRNA((_currentDisplay+1)%2), m);
+                                       _vpSlave.repaint();
+                                       _vpMaster.repaint();
+               }
+               catch (MappingException e3) {System.out.println(e3.toString());}
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoad(String path) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoaded() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onZoomLevelChanged(){
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onTranslationChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAConsoleDemo.java b/srcjar/fr/orsay/lri/varna/applications/VARNAConsoleDemo.java
new file mode 100644 (file)
index 0000000..487fa96
--- /dev/null
@@ -0,0 +1,266 @@
+package fr.orsay.lri.varna.applications;
+
+/*
+VARNA is a Java library for quick automated drawings RNA secondary structure 
+Copyright (C) 2007  Yann Ponty
+
+This program is free software:you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+*/
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.VARNAConsole;
+import fr.orsay.lri.varna.controlers.ControleurDemoTextField;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+* An RNA 2d Panel demo applet
+* 
+* @author Yann Ponty & Darty Kévin
+* 
+*/
+
+public class VARNAConsoleDemo extends JApplet implements ActionListener  {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE = "CAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIA";
+
+       private static final String DEFAULT_STRUCTURE = "..(((((...(((((...(((((...(((((.....)))))...))))).....(((((...(((((.....)))))...))))).....)))))...)))))..";
+
+       private VARNAPanel _vp;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _structPanel = new JPanel();
+       private JLabel _info = new JLabel();
+       private JButton _go = new JButton("Go");
+       private JTextField _struct = new JTextField();
+       private JTextField _seq = new JTextField();
+       private JLabel _structLabel = new JLabel(" Str:");
+       private JLabel _seqLabel = new JLabel(" Seq:");
+
+       private VARNAConsole _console;
+
+       private static String errorOpt = "error";
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+
+       private int _algoCode;
+
+       public VARNAConsoleDemo() {
+               super();
+               try {
+                       _vp = new VARNAPanel(_seq.getText(), _struct.getText());
+                       _vp.setErrorsOn(false);
+               } catch (ExceptionNonEqualLength e) {
+                       _vp.errorDialog(e);
+               }
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() {
+               
+               
+               int marginTools = 40;
+
+               setBackground(_backgroundColor);
+               _vp.setBackground(_backgroundColor);
+
+               try {
+                       _vp.getRNA().setRNA(_seq.getText(), _struct.getText());
+                       _vp.setErrorsOn(false);
+               } catch (Exception e1) {
+                       _vp.errorDialog(e1);
+               }
+
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               _console = new VARNAConsole(_vp); 
+
+               
+               _go.addActionListener(this);
+               
+               _seqLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _seqLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _seq.setFont(textFieldsFont);
+               _seq.setText(_vp.getRNA().getSeq());
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq, BorderLayout.CENTER);
+
+               _structLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _structLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _struct.setFont(textFieldsFont);
+               _struct.setText(_vp.getRNA().getStructDBN());
+               _structPanel.setLayout(new BorderLayout());
+               _structPanel.add(_structLabel, BorderLayout.WEST);
+               _structPanel.add(_struct, BorderLayout.CENTER);
+
+
+               _input.setLayout(new GridLayout(3, 0));
+               _input.add(_seqPanel);
+               _input.add(_structPanel);
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+               _tools.add(_go, BorderLayout.EAST);
+
+               getContentPane().setLayout(new BorderLayout());
+               getContentPane().add(_vp, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.SOUTH);
+
+               _vp.getVARNAUI().UIRadiate();
+               setPreferredSize(new Dimension(400,400));
+               
+               setVisible(true);
+               
+               _console.setVisible(true);
+               
+       }
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               retrieveParametersValues();
+               _vp.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       private String getParameterValue(String key, String def) {
+               String tmp;
+               tmp = getParameter(key);
+               if (tmp == null) {
+                       return def;
+               } else {
+                       return tmp;
+               }
+       }
+
+       private void retrieveParametersValues() {
+               _error = Boolean.parseBoolean(getParameterValue(errorOpt, "false"));
+               _vp.setErrorsOn(_error);
+               _backgroundColor = getSafeColor(getParameterValue("background",
+                               _backgroundColor.toString()), _backgroundColor);
+               _vp.setBackground(_backgroundColor);
+               _seq.setText(getParameterValue("sequenceDBN", ""));
+               _struct.setText(getParameterValue("structureDBN", ""));
+               String _algo = getParameterValue("algorithm", "radiate");
+               if (_algo.equals("circular"))
+                       _algoCode = RNA.DRAW_MODE_CIRCULAR;
+               else if (_algo.equals("naview"))
+                       _algoCode = RNA.DRAW_MODE_NAVIEW;
+               else if (_algo.equals("line"))
+                       _algoCode = RNA.DRAW_MODE_LINEAR;
+               else
+                       _algoCode = RNA.DRAW_MODE_RADIATE;
+               if (_seq.getText().equals("") && _struct.getText().equals("")) {
+                       _seq.setText(DEFAULT_SEQUENCE);
+                       _struct.setText(DEFAULT_STRUCTURE);
+               }
+               try {
+                       _vp.drawRNA(_seq.getText(), _struct.getText(), _algoCode);
+               } catch (ExceptionNonEqualLength e) {
+                       e.printStackTrace();
+               }
+
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vp;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vp = surface;
+       }
+
+       public JTextField get_struct() {
+               return _struct;
+       }
+
+       public void set_struct(JTextField _struct) {
+               this._struct = _struct;
+       }
+
+       public JTextField get_seq() {
+               return _seq;
+       }
+
+       public void set_seq(JTextField _seq) {
+               this._seq = _seq;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+
+       public void actionPerformed(ActionEvent arg0) {
+                 RNA r = new RNA();
+                 try {
+                       _vp.drawRNAInterpolated(_seq.getText(), _struct.getText());
+               } catch (ExceptionNonEqualLength e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+                 _vp.repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAEditor.java b/srcjar/fr/orsay/lri/varna/applications/VARNAEditor.java
new file mode 100644 (file)
index 0000000..665e9fc
--- /dev/null
@@ -0,0 +1,656 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Point2D.Double;
+import java.io.File;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultHighlighter;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.ReorderableJList;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.FullBackup;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VARNAEditor extends JFrame implements DropTargetListener, InterfaceVARNAListener, MouseListener {
+
+       /**
+        * 
+        */
+//     private static final_long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE = "CAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIA";
+
+       private static final String DEFAULT_STRUCTURE1 = "..(((((...(((((...(((((...(((((.....)))))...))))).....(((((...(((((.....)))))...))))).....)))))...)))))..";
+       private static final String DEFAULT_STRUCTURE2 = "..(((((...(((((...(((((........(((((...(((((.....)))))...)))))..................))))).....)))))...)))))..";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vp;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _strPanel = new JPanel();
+       private JLabel _info = new JLabel();
+       
+       private JTextField _str = new JTextField(DEFAULT_STRUCTURE1);
+       Object _hoverHighlightStr = null;
+       ArrayList<Object> _selectionHighlightStr = new ArrayList<Object>();
+       
+       private JTextField _seq = new JTextField(DEFAULT_SEQUENCE);
+       Object _hoverHighlightSeq = null;
+       ArrayList<Object> _selectionHighlightSeq = new ArrayList<Object>();
+       
+       
+       private JLabel _strLabel = new JLabel(" Str:");
+       private JLabel _seqLabel = new JLabel(" Seq:");
+       private JButton _deleteButton = new JButton("Delete");
+       private JButton _duplicateButton = new JButton("Duplicate");
+       
+       private JPanel _listPanel = new JPanel();
+       private ReorderableJList _sideList = null;
+
+
+
+       private static String errorOpt = "error";
+       @SuppressWarnings("unused")
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+
+       private static int _nextID = 1;
+       @SuppressWarnings("unused")
+       private int _algoCode;
+       
+       private BackupHolder _rnaList;
+
+
+       public VARNAEditor() {
+               super("VARNA Editor");
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() 
+       {
+           DefaultListModel dlm = new DefaultListModel(); 
+           
+
+               int marginTools = 40;
+
+           DefaultListSelectionModel m = new DefaultListSelectionModel();
+           m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+           m.setLeadAnchorNotificationEnabled(false);
+           
+           
+               _sideList = new ReorderableJList();
+               _sideList.setModel(dlm);
+               _sideList.addMouseListener(this);
+           _sideList.setSelectionModel(m);
+           _sideList.setPreferredSize(new Dimension(100, 0));
+           _sideList.addListSelectionListener( new ListSelectionListener(){
+                       public void valueChanged(ListSelectionEvent arg0) {
+                               //System.out.println(arg0);
+                               if (!_sideList.isSelectionEmpty() && !arg0.getValueIsAdjusting())
+                               {
+                                       FullBackup  sel = (FullBackup) _sideList.getSelectedValue();
+                                       Mapping map = Mapping.DefaultOutermostMapping(_vp.getRNA().getSize(), sel.rna.getSize());
+                                       _vp.showRNAInterpolated(sel.rna,sel.config,map);
+                                       _seq.setText(sel.rna.getSeq());
+                                       _str.setText(sel.rna.getStructDBN(true));
+                               }
+                       }
+           });
+
+           _rnaList = new BackupHolder(dlm,_sideList);
+               RNA _RNA1 = new RNA("User defined 1");
+               RNA _RNA2 = new RNA("User defined 2");
+               try {
+                       _vp = new VARNAPanel("0",".");
+                       _RNA1.setRNA(DEFAULT_SEQUENCE, DEFAULT_STRUCTURE1);
+                       _RNA1.drawRNARadiate(_vp.getConfig());
+                       _RNA2.setRNA(DEFAULT_SEQUENCE, DEFAULT_STRUCTURE2);
+                       _RNA2.drawRNARadiate(_vp.getConfig());
+               } catch (ExceptionNonEqualLength e) {
+                       _vp.errorDialog(e);
+               } catch (ExceptionUnmatchedClosingParentheses e2) {
+               e2.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e3) {
+               e3.printStackTrace();
+               }
+               _vp.setPreferredSize(new Dimension(400, 400));
+           _rnaList.add(_vp.getConfig().clone(),_RNA2,generateDefaultName());
+           _rnaList.add(_vp.getConfig().clone(),_RNA1,generateDefaultName(),true);
+
+           JScrollPane listScroller = new JScrollPane(_sideList);
+           listScroller.setPreferredSize(new Dimension(150, 0));
+
+               setBackground(_backgroundColor);
+               _vp.setBackground(_backgroundColor);
+
+
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               _seqLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _seqLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _seq.setFont(textFieldsFont);
+               _seq.setText(DEFAULT_SEQUENCE);
+               _seq.setEditable(false);
+               
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq, BorderLayout.CENTER);
+
+               _strLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _strLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _str.setFont(textFieldsFont);
+               _str.setEditable(false);
+               _strPanel.setLayout(new BorderLayout());
+               _strPanel.add(_strLabel, BorderLayout.WEST);
+               _strPanel.add(_str, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(2, 0));
+               _input.add(_seqPanel);
+               _input.add(_strPanel);
+
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+
+               _deleteButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _rnaList.removeSelected();
+                       }
+               });
+//             _duplicateButton.addActionListener(new ActionListener() {
+//                     public void actionPerformed(ActionEvent e) {
+//                                     _rnaList.add((VARNAConfig)_vp.getConfig().clone(),_vp.getRNA().clone(),_vp.getRNA().getName()+"-"+DateFormat.getTimeInstance(DateFormat.LONG).format(new Date()),true); 
+//                     }});
+               
+               JPanel ops = new JPanel();
+               ops.setLayout(new GridLayout(1,2));
+               ops.add(_deleteButton);
+               ops.add(_duplicateButton);
+
+               JLabel j = new JLabel("Structures",JLabel.CENTER);
+               _listPanel.setLayout(new BorderLayout());
+               
+               _listPanel.add(ops,BorderLayout.SOUTH);
+               _listPanel.add(j,BorderLayout.NORTH);
+               _listPanel.add(listScroller,BorderLayout.CENTER);
+
+
+               JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,_listPanel,_vp);
+               getContentPane().setLayout(new BorderLayout());
+               getContentPane().add(split, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.NORTH);
+
+               setVisible(true);
+               DropTarget dt = new DropTarget(_vp, this);
+               
+               _vp.addRNAListener(new InterfaceVARNARNAListener(){
+                       public void onSequenceModified(int index, String oldseq, String newseq) {
+                               _seq.setText(_vp.getRNA().getSeq());
+                       }
+
+                       public void onStructureModified(Set<ModeleBP> current,
+                                       Set<ModeleBP> addedBasePairs, Set<ModeleBP> removedBasePairs) {
+                               _str.setText(_vp.getRNA().getStructDBN(true));
+                       }
+
+                       public void onRNALayoutChanged(Hashtable<Integer, Double> previousPositions) {
+                       }
+                       
+               });
+               
+               _vp.addSelectionListener(new InterfaceVARNASelectionListener(){
+
+                       public void onHoverChanged(ModeleBase oldbase, ModeleBase newBase) {
+                               if (_hoverHighlightSeq!=null)
+                               {
+                                       _seq.getHighlighter().removeHighlight(_hoverHighlightSeq);
+                                       _hoverHighlightSeq = null;
+                               }
+                               if (_hoverHighlightStr!=null)
+                               {
+                                       _str.getHighlighter().removeHighlight(_hoverHighlightStr);
+                                       _hoverHighlightStr = null;
+                               }
+                               if (newBase!=null)
+                               {
+                                       try {
+                                               _hoverHighlightSeq = _seq.getHighlighter().addHighlight(newBase.getIndex(), newBase.getIndex()+1, new DefaultHighlighter.DefaultHighlightPainter(Color.green) );
+                                               _hoverHighlightStr = _str.getHighlighter().addHighlight(newBase.getIndex(), newBase.getIndex()+1, new DefaultHighlighter.DefaultHighlightPainter(Color.green) );
+                                       } catch (BadLocationException e) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+
+                       public void onSelectionChanged(BaseList selection,
+                                       BaseList addedBases, BaseList removedBases) {
+                               for(Object tag: _selectionHighlightSeq)
+                               {
+                                       _seq.getHighlighter().removeHighlight(tag);
+                               }
+                               _selectionHighlightSeq.clear();
+                               for(Object tag: _selectionHighlightStr)
+                               {
+                                       _str.getHighlighter().removeHighlight(tag);
+                               }
+                               _selectionHighlightStr.clear();
+                               for (ModeleBase m: selection.getBases())
+                               {
+                                       try {
+                                               _selectionHighlightSeq.add(_seq.getHighlighter().addHighlight(m.getIndex(), m.getIndex()+1, new DefaultHighlighter.DefaultHighlightPainter(Color.orange) ));
+                                               _selectionHighlightStr.add(_str.getHighlighter().addHighlight(m.getIndex(), m.getIndex()+1, new DefaultHighlighter.DefaultHighlightPainter(Color.orange) ));
+                                       } catch (BadLocationException e) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               
+               _vp.addVARNAListener(this);
+       }
+       
+       public static String generateDefaultName()
+       {
+               return "User file #"+_nextID++;
+       }
+
+       public RNA getRNA() {
+               return (RNA)_sideList.getSelectedValue();
+       }
+
+
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               _vp.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       @SuppressWarnings("unused")
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vp;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vp = surface;
+       }
+
+
+       public JTextField get_seq() {
+               return _seq;
+       }
+
+       public void set_seq(JTextField _seq) {
+               this._seq = _seq;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+
+       public static void main(String[] args) {
+               VARNAEditor d = new VARNAEditor();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+       
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void drop(DropTargetDropEvent dtde) {
+           try {
+               Transferable tr = dtde.getTransferable();
+               DataFlavor[] flavors = tr.getTransferDataFlavors();
+               for (int i = 0; i < flavors.length; i++) {
+           if (flavors[i].isFlavorJavaFileListType()) {
+             dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+             Object ob = tr.getTransferData(flavors[i]);
+             if (ob instanceof List)
+             {
+                 List list = (List) ob;
+                 for (int j = 0; j < list.size(); j++) {
+                 Object o = list.get(j);
+                 
+                 if (dtde.getSource() instanceof DropTarget)
+                 {
+                         DropTarget dt = (DropTarget) dtde.getSource();
+                         Component c = dt.getComponent();
+                         if (c instanceof VARNAPanel)
+                         {
+                                         String path = o.toString();
+                                 VARNAPanel vp = (VARNAPanel) c;
+                                         try{
+                                 FullBackup bck =  VARNAPanel.importSession((File) o); // BH SwingJS
+                                 _rnaList.add(bck.config, bck.rna,bck.name,true);
+                                         }
+                                         catch (ExceptionLoadingFailed e3)
+                                         {
+                                                 Collection<RNA> rnas = RNAFactory.loadSecStr((File) o); // BH SwingJS 
+                                                 if (rnas.isEmpty())
+                                                 {
+                                                         throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from that source.");
+                                                 }
+                                                 
+                                                 int id = 1;
+                                                 for(RNA r: rnas)
+                                                 {
+                                                         r.drawRNA(vp.getConfig());
+                                                         String name = r.getName();
+                                                         if (name.equals(""))
+                                                         { 
+                                                                 name = path.substring(path.lastIndexOf(File.separatorChar)+1);
+                                                         }
+                                                         if (rnas.size()>1)
+                                                         {
+                                                                 name += " - Molecule# "+id++;
+                                                         }
+                                                         _rnaList.add(vp.getConfig().clone(),r,name,true);
+                                                 }
+                                         }                                       
+                         }
+                 }
+                 }
+             }
+             // If we made it this far, everything worked.
+             dtde.dropComplete(true);
+             return;
+           }
+               }
+               // Hmm, the user must not have dropped a file list
+               dtde.rejectDrop();
+             } catch (Exception e) {
+               e.printStackTrace();
+               dtde.rejectDrop();
+             }
+               
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+       }
+
+       private class BackupHolder{
+               private DefaultListModel _rnaList;
+               private ArrayList<RNA> _rnas = new ArrayList<RNA>();
+               JList _l;
+               
+               public BackupHolder(DefaultListModel rnaList, JList l)
+               {
+                       _rnaList = rnaList;
+                       _l = l;
+               }
+               
+               public void add(VARNAConfig c, RNA r)
+               {
+                       add(c, r, r.getName(),false);
+               }
+
+               public void add(VARNAConfig c, RNA r,boolean select)
+               {
+                       add(c, r, r.getName(),select);
+               }
+
+               public void add(VARNAConfig c, RNA r, String name)
+               {
+                       add(c, r, name,false);                  
+               }
+               public void add(VARNAConfig c, RNA r, String name, boolean select)
+               {
+                       if (select){
+                               _l.removeSelectionInterval(0, _rnaList.size());
+                       }
+                       if (name.equals(""))
+                       {
+                               name = generateDefaultName();
+                       }
+                       FullBackup bck = new FullBackup(c,r,name);
+                       _rnas.add(0, r);
+                       _rnaList.add(0,bck);
+                       if (select){
+                         _l.setSelectedIndex(0);
+                       }
+               }
+
+               public void remove(int i)
+               {
+                       _rnas.remove(i);
+                       _rnaList.remove(i);
+                       
+               }
+               public DefaultListModel getModel()
+               {
+                       return _rnaList;
+               }
+               public boolean contains(RNA r)
+               {
+                       return _rnas.contains(r);
+               }
+               /*public int getSize()
+               {
+                       return _rnaList.getSize();
+               }*/
+               public FullBackup getElementAt(int i)
+               {
+                       return (FullBackup) _rnaList.getElementAt(i);
+               }
+               
+               public void removeSelected()
+               {
+                       int i = _l.getSelectedIndex();
+                       if (i!=-1)
+                       {
+                         if (_rnaList.getSize()==1)
+                         {
+                                 RNA r = new RNA();
+                                 try {
+                                       r.setRNA(" ", ".");
+                                 } catch (ExceptionUnmatchedClosingParentheses e1) {
+                                 } catch (ExceptionFileFormatOrSyntax e1) {
+                                 }
+                                 _vp.showRNA(r);
+                                 _vp.repaint();
+                         }
+                         else
+                         {  
+                                int newi = i+1;
+                                if (newi==_rnaList.getSize())
+                                {
+                                        newi = _rnaList.getSize()-2;
+                                }
+                                FullBackup bck = (FullBackup) _rnaList.getElementAt(newi);
+                                _l.setSelectedValue(bck,true);
+                         }
+                         _rnaList.remove(i);
+                       }
+
+               }
+       }
+
+       public void onStructureRedrawn() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               _rnaList.add(v, r,"",true);
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseClicked(MouseEvent e) {
+                          if(e.getClickCount() == 2){
+                            int index = _sideList.locationToIndex(e.getPoint());
+                            ListModel dlm = _sideList.getModel();
+                            FullBackup item = (FullBackup) dlm.getElementAt(index);;
+                            _sideList.ensureIndexIsVisible(index);
+                            Object newName = JOptionPane.showInputDialog(
+                                           this,
+                                           "Specify a new name for this RNA",
+                                           "Rename RNA", 
+                                           JOptionPane.QUESTION_MESSAGE,
+                                           (Icon)null,
+                                           null,
+                                           item.toString());
+                            if (newName!=null)
+                            {
+                                item.name = newName.toString();
+                                this._sideList.repaint();
+                            }
+                            }
+       }
+
+       public void mouseEntered(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mousePressed(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseReleased(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onZoomLevelChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onTranslationChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAGUI.java b/srcjar/fr/orsay/lri/varna/applications/VARNAGUI.java
new file mode 100644 (file)
index 0000000..fe468c4
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Point2D.Double;
+import java.io.File;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultHighlighter;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.ReorderableJList;
+import fr.orsay.lri.varna.components.ZoomWindow;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.FullBackup;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VARNAGUI extends JFrame implements DropTargetListener, InterfaceVARNAListener, MouseListener, AdjustmentListener {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE = "CAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIA";
+
+       private static final String DEFAULT_STRUCTURE1 = "..(((((...(((((...(((((...(((((.....)))))...))))).....(((((...(((((.....)))))...))))).....)))))...)))))..";
+       private static final String DEFAULT_STRUCTURE2 = "..(((((...(((((...(((((........(((((...(((((.....)))))...)))))..................))))).....)))))...)))))..";
+       // private static final String DEFAULT_STRUCTURE1 = "((((....))))";
+       // private static final String DEFAULT_STRUCTURE2 =
+       // "((((..(((....)))..))))";
+
+       private VARNAPanel _vp;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _strPanel = new JPanel();
+       private JLabel _info = new JLabel();
+       
+       private JTextField _str = new JTextField(DEFAULT_STRUCTURE1);
+       Object _hoverHighlightStr = null;
+       ArrayList<Object> _selectionHighlightStr = new ArrayList<Object>();
+       
+       private JTextField _seq = new JTextField(DEFAULT_SEQUENCE);
+       Object _hoverHighlightSeq = null;
+       ArrayList<Object> _selectionHighlightSeq = new ArrayList<Object>();
+       
+       
+       private ZoomWindow _zoomWindow;
+       private JLabel _strLabel = new JLabel(" Str:");
+       private JLabel _seqLabel = new JLabel(" Seq:");
+       private JButton _createButton = new JButton("Create");
+       private JButton _deleteButton = new JButton("Delete");
+       private JButton _duplicateButton = new JButton("Snapshot");
+       
+       private JPanel _listPanel = new JPanel();
+       private ReorderableJList _sideList = null;
+
+
+       private static String errorOpt = "error";
+       @SuppressWarnings("unused")
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+       
+       private JScrollBar _vert = new JScrollBar(JScrollBar.VERTICAL);
+       private JScrollBar _horiz = new JScrollBar(JScrollBar.HORIZONTAL);
+
+       private static int _nextID = 1;
+       @SuppressWarnings("unused")
+       private int _algoCode;
+       
+       private BackupHolder _rnaList;
+
+
+       public VARNAGUI() {
+               super("VARNA GUI");
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() 
+       {
+           DefaultListModel dlm = new DefaultListModel(); 
+           
+
+               int marginTools = 40;
+
+           DefaultListSelectionModel m = new DefaultListSelectionModel();
+           m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+           m.setLeadAnchorNotificationEnabled(false);
+           
+           
+               _sideList = new ReorderableJList();
+               _sideList.setModel(dlm);
+               _sideList.addMouseListener(this);
+           _sideList.setSelectionModel(m);
+           _sideList.setPreferredSize(null);
+           
+           _sideList.addListSelectionListener( new ListSelectionListener(){
+                       public void valueChanged(ListSelectionEvent arg0) {
+                               //System.out.println(arg0);
+                               if (!_sideList.isSelectionEmpty() && !arg0.getValueIsAdjusting())
+                               {
+                                       FullBackup  sel = (FullBackup) _sideList.getSelectedValue();
+                                       _vp.setConfig(sel.config);
+                                       showRNA(sel.rna);
+                                       _seq.setText(sel.rna.getSeq());
+                                       _str.setText(sel.rna.getStructDBN(true));
+                               }
+                       }
+           });
+
+           _rnaList = new BackupHolder(dlm,_sideList);
+               RNA _RNA1 = new RNA("User defined 1");
+               RNA _RNA2 = new RNA("User defined 2");
+               try {
+                       _vp = new VARNAPanel("0",".");
+                       _zoomWindow = new ZoomWindow(_vp);
+                       _RNA1.setRNA(DEFAULT_SEQUENCE, DEFAULT_STRUCTURE1);
+                       _RNA1.drawRNARadiate(_vp.getConfig());
+                       _RNA2.setRNA(DEFAULT_SEQUENCE, DEFAULT_STRUCTURE2);
+                       _RNA2.drawRNARadiate(_vp.getConfig());
+               } catch (ExceptionNonEqualLength e) {
+                       _vp.errorDialog(e);
+               } catch (ExceptionUnmatchedClosingParentheses e2) {
+               e2.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e3) {
+               e3.printStackTrace();
+               }
+               _vp.setPreferredSize(new Dimension(400, 400));
+           _rnaList.add(_vp.getConfig().clone(),_RNA2,generateDefaultName());
+           _rnaList.add(_vp.getConfig().clone(),_RNA1,generateDefaultName(),true);
+           
+
+           JScrollPane listScroller = new JScrollPane(_sideList);//,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+           listScroller.setPreferredSize(new Dimension(150, 0));
+
+               setBackground(_backgroundColor);
+               _vp.setBackground(_backgroundColor);
+
+
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               _seqLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _seqLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _seq.setFont(textFieldsFont);
+               _seq.setText(DEFAULT_SEQUENCE);
+
+               _createButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               try {
+                               RNA nRNA = new RNA(generateDefaultName());
+                               nRNA.setRNA(_seq.getText(), _str.getText());
+                               nRNA.drawRNARadiate(_vp.getConfig());
+                               _rnaList.add(new VARNAConfig(),nRNA,true);
+                               } catch (ExceptionUnmatchedClosingParentheses e1) {
+                                       JOptionPane.showMessageDialog(_vp, e1.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);
+                               } catch (ExceptionFileFormatOrSyntax e1) {
+                                       JOptionPane.showMessageDialog(_vp, e1.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);
+                               }
+                       }
+               });
+
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq, BorderLayout.CENTER);
+
+               _strLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _strLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _str.setFont(textFieldsFont);
+               _strPanel.setLayout(new BorderLayout());
+               _strPanel.add(_strLabel, BorderLayout.WEST);
+               _strPanel.add(_str, BorderLayout.CENTER);
+
+               _input.setLayout(new GridLayout(2, 0));
+               _input.add(_seqPanel);
+               _input.add(_strPanel);
+
+               JPanel goPanel = new JPanel();
+               goPanel.setLayout(new BorderLayout());
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+               _tools.add(goPanel, BorderLayout.EAST);
+
+               _deleteButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _rnaList.removeSelected();
+                       }
+               });
+               // BH 2018 SwingJS can't clone, as it does not implement serialization 
+               if (/** @j2sNative false && */ true)
+               _duplicateButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                                       _rnaList.add((VARNAConfig)_vp.getConfig().clone(),_vp.getRNA().clone(),_vp.getRNA().getName()+"-"+DateFormat.getTimeInstance(DateFormat.LONG).format(new Date()),true); 
+                       }});
+               
+               JPanel ops = new JPanel();
+               ops.setLayout(new GridLayout(1,2));
+               ops.add(_deleteButton);
+               ops.add(_duplicateButton);
+               
+               JPanel opspanel = new JPanel(new BorderLayout());
+               opspanel.add(ops,BorderLayout.NORTH);
+               opspanel.add(_zoomWindow,BorderLayout.SOUTH);
+               
+               _zoomWindow.setPreferredSize(new Dimension(-1,200));
+               
+
+               JLabel j = new JLabel("Structure Manager",JLabel.CENTER);
+               _listPanel.setLayout(new BorderLayout());
+               
+               _listPanel.add(opspanel,BorderLayout.SOUTH);
+               _listPanel.add(j,BorderLayout.NORTH);
+               _listPanel.add(listScroller,BorderLayout.CENTER);
+
+               goPanel.add(_createButton, BorderLayout.CENTER);
+
+               JPanel vpScroll = new JPanel();
+               vpScroll.setLayout(new BorderLayout());
+               _horiz.setVisible(false);
+               _horiz.addAdjustmentListener(this);
+               _vert.setVisible(false);
+               _vert.addAdjustmentListener(this);
+               vpScroll.add(_horiz,BorderLayout.SOUTH);
+               vpScroll.add(_vert,BorderLayout.EAST);
+               vpScroll.add(_vp,BorderLayout.CENTER);
+               JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,_listPanel,vpScroll);
+               getContentPane().setLayout(new BorderLayout());
+               getContentPane().add(split, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.NORTH);
+               
+
+               setVisible(true);
+               DropTarget dt = new DropTarget(_vp, this);
+               
+               _vp.addRNAListener(new InterfaceVARNARNAListener(){
+                       public void onSequenceModified(int index, String oldseq, String newseq) {
+                               //System.out.println("Sequence changed: Index:"+index+" ["+oldseq+"]=>["+newseq+"]");
+                       }
+
+                       public void onStructureModified(Set<ModeleBP> current,
+                                       Set<ModeleBP> addedBasePairs, Set<ModeleBP> removedBasePairs) {
+                               String result = "";
+                               //System.out.println("Structure changed: ");
+                               for (ModeleBP s:addedBasePairs)
+                               {  result +=s;  }
+                               //System.out.println("     Added: "+result);
+                               result = "";
+                               for (ModeleBP s:removedBasePairs)
+                               {  result +=s;  }
+                               //System.out.println("   Removed: "+result);
+                       }
+
+                       public void onRNALayoutChanged(Hashtable<Integer, Double> previousPositions) {
+                               //System.out.print("Layout changed, bases#: ");
+                               String result = "";
+                               for (Integer s:previousPositions.keySet())
+                               {  result +=s+" ";  }                           
+                               //System.out.println(result);
+                       }
+                       
+               });
+               
+               _vp.addSelectionListener(new InterfaceVARNASelectionListener(){
+
+                       public void onHoverChanged(ModeleBase oldbase, ModeleBase newBase) {
+                               if (_hoverHighlightSeq!=null)
+                               {
+                                       _seq.getHighlighter().removeHighlight(_hoverHighlightSeq);
+                                       _hoverHighlightSeq = null;
+                               }
+                               if (_hoverHighlightStr!=null)
+                               {
+                                       _str.getHighlighter().removeHighlight(_hoverHighlightStr);
+                                       _hoverHighlightStr = null;
+                               }
+                               if (newBase!=null)
+                               {
+                                       try {
+                                               int i = newBase.getIndex();
+                                               int[] shifts = _vp.getRNA().getStrandShifts();
+                                               _hoverHighlightSeq = _seq.getHighlighter().addHighlight(i+shifts[i], i+shifts[i]+1, new DefaultHighlighter.DefaultHighlightPainter(Color.green) );
+                                               _hoverHighlightStr = _str.getHighlighter().addHighlight(i+shifts[i], i+shifts[i]+1, new DefaultHighlighter.DefaultHighlightPainter(Color.green) );
+                                       } catch (BadLocationException e) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+
+                       public void onSelectionChanged(BaseList selection,
+                                       BaseList addedBases, BaseList removedBases) {
+                               for(Object tag: _selectionHighlightSeq)
+                               {
+                                       _seq.getHighlighter().removeHighlight(tag);
+                               }
+                               _selectionHighlightSeq.clear();
+                               for(Object tag: _selectionHighlightStr)
+                               {
+                                       _str.getHighlighter().removeHighlight(tag);
+                               }
+                               _selectionHighlightStr.clear();
+                               int[] shifts = _vp.getRNA().getStrandShifts();
+                               for (ModeleBase m: selection.getBases())
+                               {
+                                       try {
+                                               int i = m.getIndex();
+                                               _selectionHighlightSeq.add(_seq.getHighlighter().addHighlight(i+shifts[i], i+shifts[i]+1, new DefaultHighlighter.DefaultHighlightPainter(Color.orange) ));
+                                               _selectionHighlightStr.add(_str.getHighlighter().addHighlight(i+shifts[i], i+shifts[i]+1, new DefaultHighlighter.DefaultHighlightPainter(Color.orange) ));
+                                       } catch (BadLocationException e) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               
+               _vp.addVARNAListener(this);
+               
+               new Thread(_zoomWindow).start();   
+       }
+       
+       protected void showRNA(RNA rna) {
+               _vp.showRNAInterpolated(rna);
+               _zoomWindow.repaint();
+       }
+
+
+       public void addRNA(RNA r, VARNAConfig cfg)
+       {
+               _rnaList.add(cfg,r); 
+       }
+       
+       public static String generateDefaultName()
+       {
+               return "User file #"+_nextID++;
+       }
+
+       public RNA getRNA() {
+               return (RNA)_sideList.getSelectedValue();
+       }
+
+
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               _vp.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       @SuppressWarnings("unused")
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vp;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vp = surface;
+       }
+
+
+       public JTextField get_seq() {
+               return _seq;
+       }
+
+       public void set_seq(JTextField _seq) {
+               this._seq = _seq;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+
+       public static void main(String[] args) {
+               List<Image> icons = new ArrayList<Image>();
+               //JOptionPane.showMessageDialog(null, ""+Toolkit.getDefaultToolkit().getImage("./VARNA16x16.png"), "Check", JOptionPane.INFORMATION_MESSAGE);
+               icons.add(Toolkit.getDefaultToolkit().getImage("./VARNA16x16.png"));
+               icons.add(Toolkit.getDefaultToolkit().getImage("./VARNA32x32.png"));
+               icons.add(Toolkit.getDefaultToolkit().getImage("./VARNA64x64.png"));
+               VARNAGUI d = new VARNAGUI();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setIconImages(icons);
+               d.setVisible(true);
+       }
+       
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void drop(DropTargetDropEvent dtde) {
+           try {
+               Transferable tr = dtde.getTransferable();
+               DataFlavor[] flavors = tr.getTransferDataFlavors();
+               for (int i = 0; i < flavors.length; i++) {
+           if (flavors[i].isFlavorJavaFileListType()) {
+             dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+             Object ob = tr.getTransferData(flavors[i]);
+             if (ob instanceof List)
+             {
+                 List list = (List) ob;
+                 for (int j = 0; j < list.size(); j++) {
+                 Object o = list.get(j);
+                 
+                 if (dtde.getSource() instanceof DropTarget)
+                 {
+                         DropTarget dt = (DropTarget) dtde.getSource();
+                         Component c = dt.getComponent();
+                         if (c instanceof VARNAPanel)
+                         {
+                                         String path = o.toString();
+                                 VARNAPanel vp = (VARNAPanel) c;
+                                         try{
+                                 FullBackup bck =  VARNAPanel.importSession((File) o);  // BH SwingJS
+                                 _rnaList.add(bck.config, bck.rna,bck.name,true);
+                                         }
+                                         catch (ExceptionLoadingFailed e3)
+                                         {
+                                                 ArrayList<RNA> rnas = RNAFactory.loadSecStr((File) o); // BH SwingJS
+                                                 if (rnas.isEmpty())
+                                                 {
+                                                         throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from that source.");
+                                                 }
+                                                 
+                                             dtde.dropComplete(true);
+                                                 _vp.getVARNAUI().UIChooseRNAs(rnas);
+                                             return;
+                                                 /*
+                                                 for(RNA r: rnas)
+                                                 {
+                                                         r.drawRNA(vp.getConfig());
+                                                         String name = r.getName();
+                                                         if (name.equals(""))
+                                                         { 
+                                                                 name = path.substring(path.lastIndexOf(File.separatorChar)+1);
+                                                         }
+                                                         if (rnas.size()>1)
+                                                         {
+                                                                 name += " - Molecule# "+id++;
+                                                         }
+                                                         _rnaList.add(vp.getConfig().clone(),r,name,true);
+                                                 }*/
+                                         }                                       
+                         }
+                 }
+                 }
+             }
+             // If we made it this far, everything worked.
+             dtde.dropComplete(true);
+             return;
+           }
+               }
+               // Hmm, the user must not have dropped a file list
+               dtde.rejectDrop();
+             } catch (Exception e) {
+               e.printStackTrace();
+               dtde.rejectDrop();
+             }
+               
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+       }
+
+       private class BackupHolder{
+               private DefaultListModel _rnaList;
+               private ArrayList<RNA> _rnas = new ArrayList<RNA>();
+               JList _l;
+               
+               public BackupHolder(DefaultListModel rnaList, JList l)
+               {
+                       _rnaList = rnaList;
+                       _l = l;
+               }
+               
+               public void add(VARNAConfig c, RNA r)
+               {
+                       add(c, r, r.getName(),false);
+               }
+
+               public void add(VARNAConfig c, RNA r,boolean select)
+               {
+                       add(c, r, r.getName(),select);
+               }
+
+               public void add(VARNAConfig c, RNA r, String name)
+               {
+                       add(c, r, name,false);                  
+               }
+               public void add(VARNAConfig c, RNA r, String name, boolean select)
+               {
+                       if (!_rnas.contains(r))
+                       {
+                       if (select){
+                               _l.removeSelectionInterval(0, _rnaList.size());
+                       }
+                       if (name.equals(""))
+                       {
+                               name = generateDefaultName();
+                       }
+                       FullBackup bck = new FullBackup(c,r,name);
+                       _rnas.add(0, r);
+                       _rnaList.add(0,bck);
+                       _l.doLayout();
+                       if (select){
+                         _l.setSelectedIndex(0);
+                       }
+                       }
+               }
+
+               public void remove(int i)
+               {
+                       _rnas.remove(i);
+                       _rnaList.remove(i);
+                       
+               }
+               public DefaultListModel getModel()
+               {
+                       return _rnaList;
+               }
+               public boolean contains(RNA r)
+               {
+                       return _rnas.contains(r);
+               }
+               /*public int getSize()
+               {
+                       return _rnaList.getSize();
+               }*/
+               public FullBackup getElementAt(int i)
+               {
+                       return (FullBackup) _rnaList.getElementAt(i);
+               }
+               
+               public void removeSelected()
+               {
+                       int i = _l.getSelectedIndex();
+                       if (i!=-1)
+                       {
+                         if (_rnaList.getSize()==1)
+                         {
+                                 RNA r = new RNA();
+                                 try {
+                                       r.setRNA(" ", ".");
+                                 } catch (ExceptionUnmatchedClosingParentheses e1) {
+                                 } catch (ExceptionFileFormatOrSyntax e1) {
+                                 }
+                                 showRNA(r);
+                         }
+                         else
+                         {  
+                                int newi = i+1;
+                                if (newi==_rnaList.getSize())
+                                {
+                                        newi = _rnaList.getSize()-2;
+                                }
+                                FullBackup bck = (FullBackup) _rnaList.getElementAt(newi);
+                                _l.setSelectedValue(bck,true);
+                         }
+                         _rnaList.remove(i);
+                       }
+
+               }
+       }
+
+       public void onStructureRedrawn() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               _rnaList.add(v, r,r.getName(),true);
+               onZoomLevelChanged();
+       }
+
+       public void onWarningEmitted(String s) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseClicked(MouseEvent e) {
+                          if(e.getClickCount() == 2){
+                            int index = _sideList.locationToIndex(e.getPoint());
+                            ListModel dlm = _sideList.getModel();
+                            FullBackup item = (FullBackup) dlm.getElementAt(index);;
+                            _sideList.ensureIndexIsVisible(index);
+                            Object newName = JOptionPane.showInputDialog(
+                                           this,
+                                           "Specify a new name for this RNA",
+                                           "Rename RNA", 
+                                           JOptionPane.QUESTION_MESSAGE,
+                                           (Icon)null,
+                                           null,
+                                           item.toString());
+                            if (newName!=null)
+                            {
+                                item.name = newName.toString();
+                                this._sideList.repaint();
+                            }
+                            }
+       }
+
+       public void mouseEntered(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mousePressed(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseReleased(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onZoomLevelChanged() {
+               if (_vp.getZoom()>1.02)
+               {
+                 Rectangle r = _vp.getZoomedInTranslationBox();
+                 _horiz.setMinimum(r.x);
+                 _horiz.setMaximum(r.x+r.width+_vp.getWidth());
+                 _horiz.getModel().setExtent(_vp.getWidth());
+                 _horiz.getModel().setValue(_vp.getTranslation().x);             
+                 _horiz.doLayout();
+                 _horiz.setVisible(true);
+                 
+                 _vert.setMinimum(r.y);
+                 _vert.setMaximum(r.y+r.height+_vp.getHeight());
+                 _vert.getModel().setExtent(_vp.getHeight());
+                 _vert.getModel().setValue(_vp.getTranslation().y);              
+                 _vert.doLayout();
+                 _vert.setVisible(true);
+               }
+               else
+               {
+                 _horiz.setVisible(false);
+                 _vert.setVisible(false);
+               }
+       }
+
+       public void onTranslationChanged() {
+               if (_vp.getZoom()>1.02)
+               {
+                       int nx = _horiz.getMaximum()-(_vp.getTranslation().x-_horiz.getMinimum())-_vp.getWidth();
+                       int ny = _vert.getMaximum()-(_vp.getTranslation().y-_vert.getMinimum())-_vp.getHeight();
+                       _horiz.getModel().setValue(nx);
+                       _horiz.doLayout();
+                       _vert.getModel().setValue(ny);
+                       _vert.doLayout();
+               }
+       }
+
+       public void adjustmentValueChanged(AdjustmentEvent arg0) {
+               if (arg0.getSource()==_horiz)
+               {
+                       _vp.setTranslation(new Point(_horiz.getMaximum()-(arg0.getValue()-_horiz.getMinimum())-_vp.getWidth(),_vp.getTranslation().y));
+                       _vp.repaint();
+               }
+               else if (arg0.getSource()==_vert)
+               {
+                       _vp.setTranslation(new Point(_vp.getTranslation().x,_vert.getMaximum()-(arg0.getValue()-_vert.getMinimum())-_vp.getHeight()));
+                       _vp.repaint();
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAOnlineDemo.java b/srcjar/fr/orsay/lri/varna/applications/VARNAOnlineDemo.java
new file mode 100644 (file)
index 0000000..2d02ce7
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+/*
+ VARNA is a Java library for quick automated drawings RNA secondary structure 
+ Copyright (C) 2007  Yann Ponty
+
+ This program is free software:you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurDemoTextField;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+ * An RNA 2d Panel demo applet
+ * 
+ * @author Yann Ponty & Darty Kévin
+ * 
+ */
+
+public class VARNAOnlineDemo extends JApplet {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+       private static final String DEFAULT_SEQUENCE = "CAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIA";
+
+       private static final String DEFAULT_STRUCTURE = "..(((((...(((((...(((((...(((((.....)))))...))))).....(((((...(((((.....)))))...))))).....)))))...)))))..";
+
+       private VARNAPanel _vp;
+
+       private JPanel _tools = new JPanel();
+       private JPanel _input = new JPanel();
+
+       private JPanel _seqPanel = new JPanel();
+       private JPanel _structPanel = new JPanel();
+       private JLabel _info = new JLabel();
+       private JTextField _struct = new JTextField();
+       private JTextField _seq = new JTextField();
+       private JLabel _structLabel = new JLabel(" Str:");
+       private JLabel _seqLabel = new JLabel(" Seq:");
+
+       private static String errorOpt = "error";
+       private boolean _error;
+
+       private Color _backgroundColor = Color.white;
+
+       private int _algoCode;
+
+       public VARNAOnlineDemo() {
+               super();
+               try {
+                       _vp = new VARNAPanel(_seq.getText(), _struct.getText());
+                       _vp.setErrorsOn(false);
+               } catch (ExceptionNonEqualLength e) {
+                       _vp.errorDialog(e);
+               }
+               RNAPanelDemoInit();
+       }
+
+       private void RNAPanelDemoInit() {
+               int marginTools = 40;
+
+               setBackground(_backgroundColor);
+               _vp.setBackground(_backgroundColor);
+
+               try {
+                       _vp.getRNA().setRNA(_seq.getText(), _struct.getText());
+                       _vp.setErrorsOn(false);
+               } catch (Exception e1) {
+                       _vp.errorDialog(e1);
+               }
+
+               Font textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               _seqLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _seqLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _seq.setFont(textFieldsFont);
+               _seq.setText(_vp.getRNA().getSeq());
+
+               _seqPanel.setLayout(new BorderLayout());
+               _seqPanel.add(_seqLabel, BorderLayout.WEST);
+               _seqPanel.add(_seq, BorderLayout.CENTER);
+
+               _structLabel.setPreferredSize(new Dimension(marginTools, 15));
+               _structLabel.setHorizontalTextPosition(JLabel.LEFT);
+               _struct.setFont(textFieldsFont);
+               _struct.setText(_vp.getRNA().getStructDBN());
+               _structPanel.setLayout(new BorderLayout());
+               _structPanel.add(_structLabel, BorderLayout.WEST);
+               _structPanel.add(_struct, BorderLayout.CENTER);
+
+               ControleurDemoTextField controleurTextField = new ControleurDemoTextField(this);
+               _seq.addCaretListener(controleurTextField);
+               _struct.addCaretListener(controleurTextField);
+
+               _input.setLayout(new GridLayout(3, 0));
+               _input.add(_seqPanel);
+               _input.add(_structPanel);
+
+               _tools.setLayout(new BorderLayout());
+               _tools.add(_input, BorderLayout.CENTER);
+               _tools.add(_info, BorderLayout.SOUTH);
+
+               getContentPane().setLayout(new BorderLayout());
+               getContentPane().add(_vp, BorderLayout.CENTER);
+               getContentPane().add(_tools, BorderLayout.SOUTH);
+
+               setVisible(true);
+               _vp.getVARNAUI().UIRadiate();
+       }
+
+       public String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description,
+                               { "sequenceDBN", "String", "A raw RNA sequence" },
+                               { "structureDBN", "String",
+                                               "An RNA structure in dot bracket notation (DBN)" },
+                               { errorOpt, "boolean", "To show errors" }, };
+               return info;
+       }
+
+       public void init() {
+               retrieveParametersValues();
+               _vp.setBackground(_backgroundColor);
+               _error = true;
+       }
+
+       private Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+               } catch (Exception e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       private String getParameterValue(String key, String def) {
+               String tmp;
+               tmp = getParameter(key);
+               if (tmp == null) {
+                       return def;
+               } else {
+                       return tmp;
+               }
+       }
+
+       private void retrieveParametersValues() {
+               _error = Boolean.parseBoolean(getParameterValue(errorOpt, "false"));
+               _vp.setErrorsOn(_error);
+               _backgroundColor = getSafeColor(getParameterValue("background",
+                               _backgroundColor.toString()), _backgroundColor);
+               _vp.setBackground(_backgroundColor);
+               _seq.setText(getParameterValue("sequenceDBN", ""));
+               _struct.setText(getParameterValue("structureDBN", ""));
+               String _algo = getParameterValue("algorithm", "radiate");
+               if (_algo.equals("circular"))
+                       _algoCode = RNA.DRAW_MODE_CIRCULAR;
+               else if (_algo.equals("naview"))
+                       _algoCode = RNA.DRAW_MODE_NAVIEW;
+               else if (_algo.equals("line"))
+                       _algoCode = RNA.DRAW_MODE_LINEAR;
+               else
+                       _algoCode = RNA.DRAW_MODE_RADIATE;
+               if (_seq.getText().equals("") && _struct.getText().equals("")) {
+                       _seq.setText(DEFAULT_SEQUENCE);
+                       _struct.setText(DEFAULT_STRUCTURE);
+               }
+               try {
+                       _vp.drawRNA(_seq.getText(), _struct.getText(), _algoCode);
+               } catch (ExceptionNonEqualLength e) {
+                       e.printStackTrace();
+               }
+
+       }
+
+       public VARNAPanel get_varnaPanel() {
+               return _vp;
+       }
+
+       public void set_varnaPanel(VARNAPanel surface) {
+               _vp = surface;
+       }
+
+       public JTextField get_struct() {
+               return _struct;
+       }
+
+       public void set_struct(JTextField _struct) {
+               this._struct = _struct;
+       }
+
+       public JTextField get_seq() {
+               return _seq;
+       }
+
+       public void set_seq(JTextField _seq) {
+               this._seq = _seq;
+       }
+
+       public JLabel get_info() {
+               return _info;
+       }
+
+       public void set_info(JLabel _info) {
+               this._info = _info;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAPrinter.java b/srcjar/fr/orsay/lri/varna/applications/VARNAPrinter.java
new file mode 100644 (file)
index 0000000..6dbc332
--- /dev/null
@@ -0,0 +1,51 @@
+package fr.orsay.lri.varna.applications;
+
+import java.awt.*;
+import javax.swing.*;
+import java.awt.print.*;
+
+public class VARNAPrinter implements Printable {
+  private Component componentToBePrinted;
+
+  public static void printComponent(Component c) {
+    new VARNAPrinter(c).print();
+  }
+  
+  public VARNAPrinter(Component componentToBePrinted) {
+    this.componentToBePrinted = componentToBePrinted;
+  }
+  
+  public void print() {
+    PrinterJob printJob = PrinterJob.getPrinterJob();
+    printJob.setPrintable(this);
+    if (printJob.printDialog())
+      try {
+        printJob.print();
+      } catch(PrinterException pe) {
+        //System.out.println("Error printing: " + pe);
+      }
+  }
+
+  public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
+    if (pageIndex > 0) {
+      return(NO_SUCH_PAGE);
+    } else {
+      Graphics2D g2d = (Graphics2D)g;
+      g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+      disableDoubleBuffering(componentToBePrinted);
+      componentToBePrinted.paint(g2d);
+      enableDoubleBuffering(componentToBePrinted);
+      return(PAGE_EXISTS);
+    }
+  }
+
+  public static void disableDoubleBuffering(Component c) {
+    RepaintManager currentManager = RepaintManager.currentManager(c);
+    currentManager.setDoubleBufferingEnabled(false);
+  }
+
+  public static void enableDoubleBuffering(Component c) {
+    RepaintManager currentManager = RepaintManager.currentManager(c);
+    currentManager.setDoubleBufferingEnabled(true);
+  }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/VARNAcmd.java b/srcjar/fr/orsay/lri/varna/applications/VARNAcmd.java
new file mode 100644 (file)
index 0000000..7b16c61
--- /dev/null
@@ -0,0 +1,529 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications;
+
+
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.FileImageOutputStream;
+import javax.swing.JFrame;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionJPEGEncoding;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceParameterLoader;
+import fr.orsay.lri.varna.models.FullBackup;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.VARNAConfigLoader;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VARNAcmd implements InterfaceParameterLoader {
+       
+       public class ExitCode extends Exception{
+               /**
+                * 
+                */
+               private static final long serialVersionUID = -3011196062868355584L;
+               private int _c;
+               private String _msg;
+               public ExitCode(int c,String msg){
+                       _c = c;
+                       _msg = msg;
+               }
+               public int getExitCode(){
+                       return _c;
+               }
+               public String getExitMessage(){
+                       return _msg;
+               }
+       }
+       
+       
+       private Hashtable<String, String> _optsValues = new Hashtable<String, String>();
+       private Hashtable<String, String> _basicOptsInv = new Hashtable<String, String>();
+       private String _inFile = "";
+       private String _outFile = "";
+       int _baseWidth = 400;
+       double _scale = 1.0;
+       float _quality = 0.9f;
+
+       private String[] _basicOptions = { VARNAConfigLoader.algoOpt,
+                       VARNAConfigLoader.bpStyleOpt, VARNAConfigLoader.bondColorOpt,
+                       VARNAConfigLoader.backboneColorOpt, VARNAConfigLoader.periodNumOpt,
+                       VARNAConfigLoader.baseInnerColorOpt,
+                       VARNAConfigLoader.baseOutlineColorOpt,
+
+       };
+
+       public VARNAcmd(Vector<String> args) throws ExitCode {
+               for (int j = 0; j < _basicOptions.length; j++) {
+                       _basicOptsInv.put(_basicOptions[j], _basicOptions[j]);
+               }
+               int i = 0;
+               while (i < args.size()) {
+                       String opt = args.elementAt(i);
+                       if (opt.charAt(0) != '-') {
+                               errorExit("Missing or unknown option \"" + opt + "\"");
+                       }
+                       if (opt.equals("-h")) {
+                               displayLightHelpExit();
+                       }
+                       if (opt.equals("-x")) {
+                               displayDetailledHelpExit();
+                       } else {
+                               if (i + 1 >= args.size()) {
+                                       errorExit("Missing argument for option \"" + opt + "\"");
+                               }
+                               String val = args.get(i + 1);
+                               if (opt.equals("-i")) {
+                                       _inFile = val;
+                               } else if (opt.equals("-o")) {
+                                       _outFile = val;
+                               } else if (opt.equals("-quality")) {
+                                       _quality = Float.parseFloat(val);
+                               } else if (opt.equals("-resolution")) {
+                                       _scale = Float.parseFloat(val);
+                               } else {
+                                       addOption(opt, val);
+                               }
+                       }
+                       i += 2;
+               }
+       }
+
+       public void addOption(String key, String value) {
+               if (key.equals("-i")) {
+                       _inFile = value;
+               } else if (key.equals("-o")) {
+                       _outFile = value;
+               } else {
+                       _optsValues.put(key.substring(1), value);
+               }
+       }
+
+       private String getDescription() {
+               return "VARNA v"
+                               + VARNAConfig.MAJOR_VERSION
+                               + "."
+                               + VARNAConfig.MINOR_VERSION
+                               + " Assisted drawing of RNA secondary structure (Command Line version)";
+       }
+
+       private String indent(int k) {
+               String result = "";
+               for (int i = 0; i < k; i++) {
+                       result += "  ";
+               }
+               return result;
+       }
+
+       private String complete(String s, int k) {
+               String result = s;
+               while (result.length() < k) {
+                       result += " ";
+               }
+               return result;
+       }
+
+       Vector<String[]> matrix = new Vector<String[]>();
+
+       private void addLine(String opt, String val) {
+               String[] line = { opt, val };
+               matrix.add(line);
+       }
+
+       private static int MAX_WIDTH = 100;
+
+       @SuppressWarnings("unchecked")
+       private void printMatrix(int ind) {
+               String[][] values = new String[matrix.size()][];
+               matrix.toArray(values);
+               Arrays.sort(values, new Comparator() {
+                       public int compare(Object o1, Object o2) {
+                               String[] tab1 = (String[]) o1;
+                               String[] tab2 = (String[]) o2;
+                               return tab1[0].compareTo(tab2[0]);
+                       }
+               });
+
+               int maxSize = 0;
+               for (int i = 0; i < values.length; i++) {
+                       String[] elem = values[i];
+                       maxSize = Math.max(maxSize, elem[0].length());
+               }
+               maxSize += ind + 2;
+               for (int i = 0; i < values.length; i++) {
+                       String[] elem = values[i];
+                       String opt = elem[0];
+                       String msg = elem[1];
+                       opt = complete("", ind) + "-" + complete(opt, maxSize - ind);
+                       System.out.println(opt  + msg.substring(0, Math.min(MAX_WIDTH - opt.length(), msg.length())));
+                       if (opt.length() + msg.length() >= MAX_WIDTH) {
+                               int off = MAX_WIDTH - opt.length();
+                               while (off < msg.length()) {
+                                       String nmsg = msg.substring(off, Math.min(off + MAX_WIDTH
+                                                       - opt.length(), msg.length()));
+                                       System.out.println(complete("", opt.length())+nmsg);
+                                       off += MAX_WIDTH - opt.length();
+                               }
+                       }
+               } 
+               matrix = new Vector<String[]>();
+       }
+
+       private void printUsage() {
+               System.out
+                               .println("Usage: java -cp . [-i InFile|-sequenceDBN XXX -structureDBN YYY] -o OutFile [Options]");
+               System.out.println("Where:");
+               System.out.println(indent(1)
+                               + "OutFile\tSupported formats: {JPEG,PNG,EPS,XFIG,SVG}");
+               System.out
+                               .println(indent(1)
+                                               + "InFile\tSecondary structure file: Supported formats: {BPSEQ,CT,RNAML,DBN}");
+
+       }
+
+       private void printHelpOptions() {
+               System.out.println("\nMain options:");
+               addLine("h", "Displays a short description of main options and exits");
+               addLine("x", "Displays a detailled description of all options");
+               printMatrix(2);
+       }
+
+       private void printMainOptions(String[][] info) {
+               System.out.println("\nMain options:");
+               addLine("h", "Displays a short description of main options and exits");
+               addLine("x", "Displays a detailled description of all options");
+               for (int i = 0; i < info.length; i++) {
+                       String key = info[i][0];
+                       if (_basicOptsInv.containsKey(key)) {
+                               addLine(key, info[i][2]);
+                       }
+               }
+               printMatrix(2);
+       }
+
+       private void printAdvancedOptions(String[][] info) {
+               System.out.println("\nAdvanced options:");
+               for (int i = 0; i < info.length; i++) {
+                       String key = info[i][0];
+                       if (!_basicOptsInv.containsKey(key)) {
+                               addLine(key, info[i][2]);
+                       }
+               }
+               addLine("quality", "Sets quality (non-vector file formats only)");
+               addLine("resolution", "Sets resolution (non-vector file formats only)");
+               printMatrix(2);
+       }
+
+       private void displayLightHelpExit() throws ExitCode {
+               String[][] info = VARNAConfigLoader.getParameterInfo();
+               System.out.println(getDescription());
+               printUsage();
+               printMainOptions(info);
+               throw(new ExitCode(1,""));
+       }
+
+       private void displayDetailledHelpExit() throws ExitCode {
+               String[][] info = VARNAConfigLoader.getParameterInfo();
+               System.out.println(getDescription());
+               printUsage();
+               printMainOptions(info);
+               printAdvancedOptions(info);
+               throw(new ExitCode(1,""));
+       }
+
+       private void errorExit(String msg) throws ExitCode {
+               System.out.println(getDescription());
+               System.out.println("Error: " + msg + "\n");
+               printUsage();
+               printHelpOptions();
+               throw(new ExitCode(1,""));
+       }
+
+       public String getParameterValue(String key, String def) {
+               if (_optsValues.containsKey(key)) {
+                       return _optsValues.get(key);
+               }
+               return def;
+       }
+
+       public String formatOutputPath(String base,int index, int total)
+       {
+               String result = base;
+               
+               if (total>1)
+               {
+                       int indexDot = base.lastIndexOf('.');
+                       String pref;
+                       String ext;
+                       if (indexDot!=-1)
+                       {
+                         pref = base.substring(0,indexDot);
+                         ext = base.substring(indexDot);
+                       }
+                       else{
+                               pref=base;
+                               ext="";
+                       }
+                       result = pref+"-"+index+ext;
+               }
+               System.err.println("Output file: "+result);
+               return result;
+       }
+       
+       public void run() throws IOException, ExitCode {
+               VARNAConfigLoader VARNAcfg = new VARNAConfigLoader(this);
+               ArrayList<VARNAPanel> vpl;
+               ArrayList<FullBackup> confs = new ArrayList<FullBackup>();
+               try {
+                       if (!_inFile.equals("")) {
+                               if (!_inFile.toLowerCase().endsWith(".varna")) {
+                                       Collection<RNA> rnas = RNAFactory.loadSecStr(_inFile);
+                                        if (rnas.isEmpty())
+                                        {
+                                                FullBackup f = null;
+                                                               try{
+                                                               f = VARNAPanel.importSession(new FileInputStream(_inFile), _inFile);
+                                                               confs.add(f);
+                                                               }
+                                                               catch(Exception e)
+                                                               {
+                                                                       e.printStackTrace();
+                                                               }
+                                               if (f==null)
+                                               {
+                                                throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from file '"+_inFile+"'.");
+                                               }
+                                        }
+                                        else{
+                                                for (RNA r: rnas)
+                                                {
+                                                        confs.add(new FullBackup(r,_inFile));
+                                                }
+                                        }
+                               }
+                               else{
+                                       confs.add(VARNAPanel.importSession(_inFile));
+                               }
+                       } else {
+                               RNA r = new RNA();
+                               r.setRNA(this.getParameterValue("sequenceDBN",
+                                               ""), this.getParameterValue(
+                                               "structureDBN", ""));
+                               confs.add(new FullBackup(r,"From Params"));
+                       }
+                       if (!_outFile.equals(""))
+                       {
+                       int index = 1;
+                       for (FullBackup r: confs)
+                       {
+                               VARNAcfg.setRNA(r.rna);
+                               vpl = VARNAcfg.createVARNAPanels();
+                               if (vpl.size() > 0) {
+                               VARNAPanel _vp = vpl.get(0);
+                               if (r.hasConfig())
+                               {
+                                       _vp.setConfig(r.config);
+                               }
+                               RNA _rna = _vp.getRNA();
+                               Rectangle2D.Double bbox = _vp.getRNA().getBBox();
+                               //System.out.println(_vp.getRNA().getBBox());
+                               
+                               if (_outFile.toLowerCase().endsWith(".jpeg")
+                                               || _outFile.toLowerCase().endsWith(".jpg")
+                                               || _outFile.toLowerCase().endsWith(".png"))
+                               { 
+                                       _vp.setTitleFontSize((int)(_scale*_vp.getTitleFont().getSize())); 
+                                   _vp.setSize((int)(_baseWidth*_scale), (int)((_scale*_baseWidth*bbox.height)/((double)bbox.width)));
+                               }
+                               
+                               if (_outFile.toLowerCase().endsWith(".eps")) {
+                                       _rna.saveRNAEPS(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
+                               } else if (_outFile.toLowerCase().endsWith(".xfig")
+                                               || _outFile.toLowerCase().endsWith(".fig")) {
+                                       _rna.saveRNAXFIG(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
+                               } else if (_outFile.toLowerCase().endsWith(".svg")) {
+                                       _rna.saveRNASVG(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
+                               } else if (_outFile.toLowerCase().endsWith(".jpeg")
+                                               || _outFile.toLowerCase().endsWith(".jpg")) {
+                                       this.saveToJPEG(formatOutputPath(_outFile,index, confs.size()), _vp);
+                               } else if (_outFile.toLowerCase().endsWith(".png")) {
+                                       this.saveToPNG(formatOutputPath(_outFile,index, confs.size()), _vp);
+                               } else if (_outFile.toLowerCase().endsWith(".varna")) {
+                                       _vp.saveSession(formatOutputPath(_outFile,index, confs.size()));
+                               } else {
+                                       errorExit("Unknown extension for output file \"" + _outFile
+                                                       + "\"");
+                               }
+                       }
+                       index++;
+                       }
+                       }
+                       // No output file => Open GUI
+                       else
+                       {
+                               VARNAGUI d = new VARNAGUI();
+                               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                               d.pack();
+                               d.setVisible(true);
+                               for (FullBackup b: confs)
+                               {
+                                       RNA r = b.rna;
+                                       VARNAcfg.setRNA(r);
+                                       vpl = VARNAcfg.createVARNAPanels();
+                                       if (vpl.size() > 0) {
+                                               VARNAPanel _vp = vpl.get(0);
+                                               VARNAConfig cfg = _vp.getConfig();
+                                               if (b.hasConfig())
+                                               {
+                                                       cfg = b.config;
+                                               }
+                                               RNA rna = _vp.getRNA();
+                                               d.addRNA(rna, cfg);
+                                               
+                                       }
+                               }
+                       }
+               } catch (ExceptionWritingForbidden e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionJPEGEncoding e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionParameterError e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionNonEqualLength e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               } catch (ExceptionExportFailed e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionPermissionDenied e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionLoadingFailed e) {
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       e.setPath(_inFile);
+                       e.printStackTrace();
+                       throw(new ExitCode(1,""));
+               } catch (FileNotFoundException e) {
+                       throw(new ExitCode(1,"Error: Missing input file \""+_inFile+"\"."));
+               }
+               
+               if (!_outFile.equals(""))
+                       throw(new ExitCode(0,""));
+               
+               
+       
+       }
+
+       public void saveToJPEG(String filename, VARNAPanel vp)
+                       throws ExceptionJPEGEncoding, ExceptionExportFailed {
+               
+               BufferedImage myImage = new BufferedImage((int) Math.round(vp
+                               .getWidth()
+                               ), (int) Math.round(vp.getHeight() ),
+                               BufferedImage.TYPE_INT_RGB);
+               Graphics2D g2 = myImage.createGraphics();
+               vp.paintComponent(g2);
+               try {
+                       FileImageOutputStream out = new FileImageOutputStream(new File(filename));
+                       ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
+                       ImageWriteParam params = writer.getDefaultWriteParam();
+                       params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+                       params.setCompressionQuality(_quality);
+                       writer.setOutput(out);
+                       IIOImage myIIOImage = new IIOImage(myImage, null, null);
+                       writer.write(null, myIIOImage, params);
+                       out.close();
+               } catch (IOException e) {
+                       throw new ExceptionExportFailed(e.getMessage(), filename);
+               }
+
+       }
+
+       public void saveToPNG(String filename, VARNAPanel vp)
+                       throws ExceptionExportFailed {
+               BufferedImage myImage = new BufferedImage((int) Math.round(vp
+                               .getWidth()), (int) Math.round(vp.getHeight() ),
+                               BufferedImage.TYPE_INT_RGB);
+               Graphics2D g2 = myImage.createGraphics();
+               vp.paintComponent(g2);
+               g2.dispose();
+               try {
+                       ImageIO.write(myImage, "PNG", new File(filename));
+               } catch (IOException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static void main(String[] argv) {
+               Vector<String> opts = new Vector<String>();
+               for (int i = 0; i < argv.length; i++) {
+                       opts.add(argv[i]);
+               }
+               try {
+                       VARNAcmd app = new VARNAcmd(opts);
+                       app.run();
+               } catch (IOException e) {
+                       e.printStackTrace();
+               } catch (ExitCode e) {
+                       System.err.println(e.getExitMessage());
+                       System.exit(e.getExitCode());
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqAnnotationDataModel.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqAnnotationDataModel.java
new file mode 100644 (file)
index 0000000..15c0522
--- /dev/null
@@ -0,0 +1,112 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.Color;
+import java.awt.datatransfer.DataFlavor;
+import java.util.Hashtable;
+import java.util.Random;
+
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation.ChemProbAnnotationType;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class FragSeqAnnotationDataModel extends FragSeqModel {
+  private String _id;
+  private String _name;
+  private Hashtable<Integer, ChemProbModel> _values = new Hashtable<Integer, ChemProbModel>();  
+  
+  
+  public FragSeqAnnotationDataModel(String id, String name)
+  {
+       _id = id;
+       _name = name;
+  }
+
+  public FragSeqAnnotationDataModel()
+  {
+       this(Long.toHexString(Double.doubleToLongBits(Math.random())),Long.toHexString(Double.doubleToLongBits(Math.random())));
+  }
+
+  public void addValue(ChemProbModel cpm)
+  {
+         _values.put(cpm._baseNumber1,cpm);
+  }
+
+  static Random _rnd = new Random();
+  
+  public static void addRandomAnnotations(RNA r,FragSeqAnnotationDataModel data){
+         int nb = r.getSize()/5+_rnd.nextInt(r.getSize()/3);
+         Color[] colors = {Color.orange,Color.black,Color.blue.darker(),Color.green.darker(), Color.gray};
+         ChemProbAnnotationType[] types  = ChemProbAnnotationType.values();
+         for(int i=0;i<nb;i++)
+         {
+                 int index = _rnd.nextInt(r.getSize()-1);
+                 int number1 = r.getBaseNumber(index);
+                 int number2 = r.getBaseNumber(index+1);
+                 ChemProbModel cpm = data.new ChemProbModel(number1,number2,colors[_rnd.nextInt(colors.length)],2*_rnd.nextDouble(),types[_rnd.nextInt(types.length)],true);
+                 data.addValue(cpm);
+         }
+  }
+
+  
+  public String toString()
+  {
+         return _name;
+  }
+  
+  public String getID()
+  {
+         return _id;
+  }
+
+  public void applyTo(RNA r)
+  {
+       r.clearChemProbAnnotations();
+       for (ChemProbModel c : _values.values())
+       {
+               c.applyTo(r);
+       }
+  }
+
+  
+  public class ChemProbModel
+  {
+               private Color _color;
+               private double _intensity;
+               private ChemProbAnnotationType _type;
+               private boolean _out;
+               private int _baseNumber1;
+               private int _baseNumber2;
+               
+               public ChemProbModel (int baseNumber1,int baseNumber2, Color color, double intensity, ChemProbAnnotationType type, boolean out)
+               {
+                       _color= color;
+                       _intensity = intensity;
+                       _type= type;
+                       _out= out;
+                       _baseNumber1 = baseNumber1;
+                       _baseNumber2 = baseNumber2;
+               }
+
+               public void applyTo(RNA r)
+               {
+                       System.out.println(this);
+                       int i = r.getIndexFromBaseNumber(_baseNumber1);
+                       int j = r.getIndexFromBaseNumber(_baseNumber2);
+                       if (i!=-1 && j!=-1)
+                       {
+                               ModeleBase mb1 = r.getBaseAt(i);
+                               ModeleBase mb2 = r.getBaseAt(j);
+                               r.addChemProbAnnotation(new ChemProbAnnotation(mb1, mb2, _type, _intensity,_color, _out));
+                       }
+               }
+               
+               public String toString()
+               {
+                       return ""+_baseNumber1+": col="+_color+" int="+_intensity+" type="+_type+" out="+_out;
+               }
+  }
+  
+  public static DataFlavor Flavor = new DataFlavor(FragSeqAnnotationDataModel.class, "RNA Chem Prob Data");
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellEditor.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellEditor.java
new file mode 100644 (file)
index 0000000..4f9ff3c
--- /dev/null
@@ -0,0 +1,53 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.util.EventObject;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellEditor;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+class FragSeqCellEditor extends DefaultTreeCellEditor {  
+       
+       FragSeqTreeModel _m;
+       
+       private FragSeqCellRenderer _base;
+
+    public FragSeqCellEditor(JTree tree, DefaultTreeCellRenderer renderer, FragSeqTreeModel m) {
+               super(tree, renderer);
+               _base= new FragSeqCellRenderer(tree,m);
+               _m=m;
+       }
+    
+    
+    public Component getTreeCellEditorComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row)  
+    {  
+     
+    JPanel renderer = (JPanel) _base.baseElements(tree,_m,value,sel,expanded,leaf,row,true);
+    return renderer;  
+    }
+
+    public boolean isCellEditable(EventObject evt)
+    {
+        if (evt instanceof MouseEvent) {
+            int clickCount;
+            // For single-click activation
+            clickCount = 1;
+
+            return ((MouseEvent)evt).getClickCount() >= clickCount;
+        }
+        return true;
+
+    }
+
+    
+}  
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellRenderer.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqCellRenderer.java
new file mode 100644 (file)
index 0000000..d1e92bf
--- /dev/null
@@ -0,0 +1,225 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+class FragSeqCellRenderer extends DefaultTreeCellRenderer {  
+          
+       JTree _j;
+       FragSeqTreeModel _m;
+       
+       private static FragSeqCellRenderer _default = new FragSeqCellRenderer(null,null);
+
+       
+
+       public FragSeqCellRenderer (JTree j, FragSeqTreeModel m)
+       {
+               _j = j;
+               _m = m;
+       }
+       
+       public JComponent baseElements(JTree tree,FragSeqTreeModel m,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)
+       {
+               JLabel initValue = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+        JPanel result = new JPanel();
+        result.setLayout(new BorderLayout());
+        initValue.setBorder(null);
+        result.setBorder(null);
+        result.setBackground(initValue.getBackground());
+        /*if (hasFocus)
+        { 
+               //renderer.setBackground(Color.blue);            
+               //result.setBorder(BorderFactory.createLineBorder(Color.blue));
+            result.setBackground(UIManager.getColor("Tree.selectionBackground"));
+            result.setBorder(BorderFactory.createLineBorder(initValue.getBackground()));
+            initValue.setOpaque(true);
+        }
+        else
+        {
+               result.setBackground(Color.white);
+               result.setBorder(BorderFactory.createLineBorder(initValue.getBackground()));
+            
+        }*/
+        DefaultMutableTreeNode t = (DefaultMutableTreeNode)value;
+        Object o = t.getUserObject();
+        if (( o instanceof String))    
+        {  
+            if (expanded)  
+            {  
+               initValue.setIcon(_default.getOpenIcon());  
+            }  
+            else  
+            {  
+               initValue.setIcon(_default.getClosedIcon());  
+            }  
+            result.add(initValue,BorderLayout.WEST);
+               JButton del = new JButton();
+               del.setIcon(new SimpleIcon(Color.red,26,false));
+               Dimension d = getPreferredSize();
+               d.width=24;
+               del.setPreferredSize(d);
+               del.addActionListener(new FolderCloses((String)o,tree,m));
+            result.add(del,BorderLayout.EAST);
+        }  
+        else  if (( o instanceof FragSeqRNASecStrModel))
+        {  
+               initValue.setIcon(new SimpleIcon(Color.blue.darker()));  
+               result.add(initValue,BorderLayout.WEST);
+        }
+        else  if (( o instanceof FragSeqFileModel))
+        {  
+               initValue.setIcon(_default.getLeafIcon());  
+            FragSeqFileModel mod = (FragSeqFileModel) o;
+            result.add(initValue,BorderLayout.WEST);
+            if (mod.hasChanged())
+            {
+                 JButton refresh = new JButton("Refresh");
+              result.add(refresh,BorderLayout.EAST);
+            }
+        }
+        else  if (( o instanceof FragSeqModel))
+        {
+               FragSeqModel mod = (FragSeqModel) o;
+               initValue.setIcon(new SimpleIcon());  
+            result.add(initValue,BorderLayout.WEST);           
+        }
+        return result;
+       }
+       
+    public Component getDefaultTreeCellRendererComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)  
+    {
+      return super.getTreeCellRendererComponent(tree,value,sel,expanded,leaf,row,hasFocus);
+    }
+
+       public Component getTreeCellRendererComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)  
+    {
+       
+        return baseElements(tree,_m,value,sel,expanded,leaf,row,hasFocus);  
+    }  
+    public Dimension getPreferredSize(int row) {
+        Dimension size = super.getPreferredSize();
+        size.width = _j.getWidth();
+        System.out.println(size);
+        return size;
+    }
+
+
+//    @Override
+//    public void setBounds(final int x, final int y, final int width, final int height) {
+//    super.setBounds(x, y, Math.min(_j.getWidth()-x, width), height);
+//    }
+    
+    
+    public class FolderCloses implements ActionListener{
+       String _path;
+       JComponent _p;
+       FragSeqTreeModel _m;
+       
+       public FolderCloses(String path, JComponent p, FragSeqTreeModel m)
+       {
+               _path = path;
+               _p = p;
+               _m = m;
+       }
+               public void actionPerformed(ActionEvent e) {
+                       if (JOptionPane.showConfirmDialog(_p, "This folder will cease to be watched. Confirm?", "Closing folder", JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION)
+                       {
+                               _m.removeFolder(_path);
+                               System.out.println(_j);
+                               _j.updateUI();
+                       }
+               }
+                   
+    }
+
+    
+    public class SimpleIcon implements Icon{
+
+        private int _w = 16;
+        private int _h = 16;
+
+        private BasicStroke stroke = new BasicStroke(3);
+        private Color _r;
+        private boolean _drawBackground = true;
+
+        public SimpleIcon()
+        {
+               this(Color.magenta.darker());
+        }
+        public SimpleIcon(Color r)
+        {
+               this(r,16,true);
+        }
+        public SimpleIcon(Color r, int dim, boolean drawBackground)
+        {
+               this(r,dim,dim,drawBackground);
+        }
+
+        public SimpleIcon(Color r, int width, int height,boolean drawBackground)
+        {
+               _r=r;
+               _w=width;
+               _h=height;
+               _drawBackground=drawBackground;
+        }
+
+        public void paintIcon(Component c, Graphics g, int x, int y) {
+            Graphics2D g2d = (Graphics2D) g.create();
+
+            if (_drawBackground)
+            {
+            g2d.setColor(Color.WHITE);
+            g2d.fillRect(x +1 ,y + 1,_w -2 ,_h -2);
+
+            g2d.setColor(Color.BLACK);
+            g2d.drawRect(x +1 ,y + 1,_w -2 ,_h -2);
+            }
+
+            g2d.setColor(_r);
+
+            g2d.setStroke(stroke);
+            g2d.drawLine(x +10, y + 10, x + _w -10, y + _h -10);
+            g2d.drawLine(x +10, y + _h -10, x + _w -10, y + 10);
+
+            g2d.dispose();
+        }
+
+        public int getIconWidth() {
+            return _w;
+        }
+
+        public int getIconHeight() {
+            return _h;
+        }
+    }
+}  
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqFileModel.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqFileModel.java
new file mode 100644 (file)
index 0000000..6374db8
--- /dev/null
@@ -0,0 +1,158 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Random;
+import java.util.regex.Pattern;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class FragSeqFileModel implements Comparable<FragSeqFileModel> {
+       private ArrayList<FragSeqModel> _models = new ArrayList<FragSeqModel>();
+         protected Date _lastModified;
+         protected boolean _outOfSync = false;
+         protected String _caption = "";
+         protected String _path = "";
+         protected String _folder = "";
+         protected boolean _cached = false;
+
+         
+         public static Date lastModif(String path)
+         {
+                return new Date(new File(path).lastModified()) ;
+         }
+         
+         public FragSeqFileModel(String folder, String path)
+         {
+               this(folder,path,lastModif(path));
+         }
+         
+         
+         private static Random _rnd = new Random();
+         
+         public FragSeqFileModel(String folder, String path,Date lastModified)
+         {
+                 _lastModified = lastModified;
+                 _outOfSync = false;
+                 _folder =folder;
+                 _path = path;
+                 String[] s = path.split(Pattern.quote(File.separator));
+                 if (s.length>0)
+                   _caption = s[s.length-1];
+         }
+         
+         public void load()
+         {
+                 ArrayList<RNA> rnas = null;
+                 try {
+                         rnas = createRNAs();
+                       for (RNA r: rnas)
+                       {  
+                               this.addModel(new FragSeqRNASecStrModel(r));
+                                 int nb =_rnd.nextInt(5); 
+                                 for(int i=0;i<nb;i++)
+                                 {
+                                         FragSeqAnnotationDataModel data = new FragSeqAnnotationDataModel(r.getID(),""+i+"-"+r.getID());
+                                         FragSeqAnnotationDataModel.addRandomAnnotations(r,data);
+                                         addModel(data);
+                                 }               
+                       }
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (FileNotFoundException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionExportFailed e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionPermissionDenied e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionLoadingFailed e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+                 _cached = true;
+         }
+         
+         public boolean hasChanged()
+         {
+                 return _outOfSync;
+         }
+         
+         public boolean checkForModifications()
+         {
+               if (!lastModif(_path).equals(_lastModified) && !_outOfSync)
+               {
+                       _outOfSync = true;
+                         return true;
+               }
+               return false;
+         }
+         
+         
+         public String toString()
+         {
+                 return _caption + (this._outOfSync?"*":"");
+         }
+         
+         
+         public String getCaption()
+         {
+                 return _caption;
+         }
+
+         public String getFolder()
+         {
+                 return _folder;
+         }
+
+         public String getPath()
+         {
+                 return _path;
+         }
+
+
+       public int compareTo(FragSeqFileModel o) {
+               return _caption.compareTo(o._caption);
+       }
+         
+       public ArrayList<FragSeqModel> getModels()
+       {
+               if (!_cached)
+               {  load();  }
+               return _models;
+       }
+       public void addModel(FragSeqModel f)
+       {
+               _models.add(f);
+       }
+
+       
+       private ArrayList<RNA> createRNAs() throws ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax, FileNotFoundException, ExceptionExportFailed, ExceptionPermissionDenied, ExceptionLoadingFailed
+         {
+                 Collection<RNA> r = RNAFactory.loadSecStr(_path);
+                 for (RNA r2 : r)
+                 {
+                         r2.drawRNARadiate();
+                 }
+                 return new ArrayList<RNA>(r);
+         }
+         
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqGUI.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqGUI.java
new file mode 100644 (file)
index 0000000..db2c3ee
--- /dev/null
@@ -0,0 +1,958 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextPane;
+import javax.swing.JToolBar;
+import javax.swing.JTree;
+import javax.swing.ListSelectionModel;
+import javax.swing.LookAndFeel;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.ExpandVetoException;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.BasicINI;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.models.FullBackup;
+
+
+public class FragSeqGUI extends JFrame implements TreeModelListener, MouseListener,DropTargetListener, WindowListener, ComponentListener, ActionListener, TreeSelectionListener {
+
+       private enum Commands
+       {
+               NEW_FOLDER,
+               ADD_PANEL_UP,
+               ADD_PANEL_DOWN,
+               REMOVE_PANEL_UP,
+               REMOVE_PANEL_DOWN,
+               SORT_ID,
+               SORT_FILENAME,
+               REFRESH_ALL,
+               CHANGE_LNF,
+               TEST_XML,
+       };
+       
+       
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+
+               
+       private String _INIFilename = "FragSeqUI.ini";
+       private boolean redrawOnSlide = false;
+       private int dividerWidth = 5;
+       
+       private JPanel _varnaUpperPanels = new JPanel();
+       private JPanel _varnaLowerPanels = new JPanel();
+
+       private JPanel _listPanel = new JPanel();
+       private JPanel _infoPanel = new JPanel();
+       private FragSeqTree _sideList = null;
+
+       
+       private FragSeqTreeModel _treeModel;
+       private JToolBar _toolbar = new JToolBar();
+       private JFileChooser _choice = new JFileChooser();
+       
+       private JScrollPane _listScroller;
+
+       private JList _selectedElems;
+       private JSplitPane _splitLeft;
+       private JSplitPane _splitRight;
+       private JSplitPane _splitVARNA;
+       
+       private JComboBox _lnf; 
+
+
+       public FragSeqGUI() {
+               super("VARNA Explorer");
+               RNAPanelDemoInit();
+       }
+
+       
+       private void RNAPanelDemoInit() 
+       {
+               JFrame.setDefaultLookAndFeelDecorated(true);
+               this.addWindowListener(this);
+           
+               _selectedElems = new JList();
+
+               _lnf = new JComboBox(UIManager.getInstalledLookAndFeels());
+               
+               // Initializing Custom Tree Model
+               _treeModel = new FragSeqTreeModel();
+               _treeModel.addTreeModelListener(this);
+               
+               _sideList = new FragSeqTree(_treeModel);
+               _sideList.addMouseListener(this);
+               _sideList.setLargeModel(true);
+               _sideList.setEditable(true);
+               _sideList.addTreeWillExpandListener(_treeModel);
+               FragSeqCellRenderer renderer = new FragSeqCellRenderer(_sideList,_treeModel);
+               //_sideList.setUI(new CustomTreeUI());
+               _sideList.setCellRenderer(renderer);
+               _sideList.setCellEditor(new FragSeqCellEditor(_sideList,renderer,_treeModel));
+               TreeSelectionModel m = _sideList.getSelectionModel();
+               m.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+               _sideList.setSelectionModel(m);
+               m.addTreeSelectionListener(this);
+               _sideList.setShowsRootHandles(true);
+               _sideList.setDragEnabled(true);
+               _sideList.setRootVisible(false);
+               _sideList.setTransferHandler(new TransferHandler(null) 
+               {
+                       public int getSourceActions(JComponent c) {
+                               return COPY_OR_MOVE;
+                       }
+                       protected Transferable createTransferable(JComponent c) {
+                               JTree tree = (JTree) c;
+                               TreePath tp =tree.getSelectionPath();
+                               if (tp!=null)
+                               {
+                                       DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
+                                       if (node.getUserObject() instanceof FragSeqRNASecStrModel) {
+                                               return new Transferable(){
+                                                       public DataFlavor[] getTransferDataFlavors() {
+                                                               DataFlavor[] dt = {FragSeqRNASecStrModel.Flavor};
+                                                               return dt;
+                                                       }
+                                                       public Object getTransferData(DataFlavor df)
+                                                       throws UnsupportedFlavorException, IOException {
+                                                               if (!isDataFlavorSupported(df))
+                                                                       throw new UnsupportedFlavorException(df);
+                                                               DefaultMutableTreeNode node = (DefaultMutableTreeNode) _sideList.getSelectionPath().getLastPathComponent();
+                                                               return node.getUserObject();
+                                                       }
+                                                       public boolean isDataFlavorSupported(DataFlavor df) {
+                                                               return FragSeqRNASecStrModel.Flavor.equals(df);
+                                                       }
+                                               };
+                                       } else  if (node.getUserObject() instanceof FragSeqAnnotationDataModel) {
+                                                       return new Transferable(){
+                                                               public DataFlavor[] getTransferDataFlavors() {
+                                                                       DataFlavor[] dt = { FragSeqAnnotationDataModel.Flavor};
+                                                                       return dt;
+                                                               }
+                                                               public Object getTransferData(DataFlavor df)
+                                                               throws UnsupportedFlavorException, IOException {
+                                                                       if (!isDataFlavorSupported(df))
+                                                                               throw new UnsupportedFlavorException(df);
+                                                                       DefaultMutableTreeNode node = (DefaultMutableTreeNode) _sideList.getSelectionPath().getLastPathComponent();
+                                                                       return node.getUserObject();
+                                                               }
+                                                               public boolean isDataFlavorSupported(DataFlavor df) {
+                                                                       return FragSeqAnnotationDataModel.Flavor.equals(df);
+                                                               }
+                                                       };
+                                               } else {
+                                               return null;
+                                       }
+                               }
+                               return null;
+                       }
+               });
+
+               // Various buttons
+               JButton refreshAllFoldersButton = new JButton("Refresh All");
+               refreshAllFoldersButton.setActionCommand(""+Commands.REFRESH_ALL);
+               refreshAllFoldersButton.addActionListener(this);
+               
+               JButton watchFolderButton = new JButton("Add folder");
+               watchFolderButton.setActionCommand("" +Commands.NEW_FOLDER);
+               watchFolderButton.addActionListener(this);
+               
+               JButton addUpperButton = new JButton("+Up");
+               addUpperButton.setActionCommand(""+Commands.ADD_PANEL_UP);
+               addUpperButton.addActionListener(this);
+               
+               JButton removeUpperButton = new JButton("-Up");
+               removeUpperButton.setActionCommand(""+Commands.REMOVE_PANEL_UP);
+               removeUpperButton.addActionListener(this);
+               
+               JButton addLowerButton = new JButton("+Down");
+               addLowerButton.setActionCommand(""+Commands.ADD_PANEL_DOWN);
+               addLowerButton.addActionListener(this);
+               
+               JButton removeLowerButton = new JButton("-Down");
+               removeLowerButton.setActionCommand(""+Commands.REMOVE_PANEL_DOWN);
+               removeLowerButton.addActionListener(this);
+               
+               JButton changeLNFButton = new JButton("Change");
+               changeLNFButton.setActionCommand(""+Commands.CHANGE_LNF);
+               changeLNFButton.addActionListener(this);
+
+               JButton XMLButton = new JButton("Test XML");
+               XMLButton.setActionCommand(""+Commands.TEST_XML);
+               XMLButton.addActionListener(this);
+
+               _toolbar.setFloatable(false);
+               _toolbar.add(refreshAllFoldersButton);
+               _toolbar.addSeparator();
+               _toolbar.add(addUpperButton);
+               _toolbar.add(removeUpperButton);
+               _toolbar.add(addLowerButton);
+               _toolbar.add(removeLowerButton);
+               _toolbar.addSeparator();
+               _toolbar.add(XMLButton);
+               _toolbar.addSeparator();
+               _toolbar.add(_lnf);
+               _toolbar.add(changeLNFButton);
+               
+               // Scroller for File tree
+           _listScroller = new JScrollPane(_sideList,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+           _listScroller.setPreferredSize(new Dimension(300, 200));
+           _listScroller.addComponentListener(this);
+
+               
+               _listPanel.setLayout(new BorderLayout());
+               
+               _listPanel.add(_listScroller,BorderLayout.CENTER);
+               _listPanel.add(_selectedElems,BorderLayout.SOUTH);
+               _listPanel.setBorder(BorderFactory.createTitledBorder("Structures"));
+               _listPanel.setPreferredSize(new Dimension(300, 0));
+               
+               _varnaUpperPanels.setLayout(new GridLayout());
+               _varnaUpperPanels.setPreferredSize(new Dimension(800, 600));
+               
+               _varnaLowerPanels.setLayout(new GridLayout());
+               _varnaLowerPanels.setPreferredSize(new Dimension(800, 000));
+               
+           JRadioButton sortFileName = new JRadioButton("Directory");
+           sortFileName.setActionCommand("sortfilename");
+           sortFileName.setSelected(true);
+           sortFileName.setOpaque(false);
+           sortFileName.setActionCommand(""+Commands.SORT_FILENAME);
+           sortFileName.addActionListener(this);
+           JRadioButton sortID = new JRadioButton("ID");
+           sortID.setActionCommand("sortid");
+           sortID.setOpaque(false);
+           sortID.setActionCommand(""+Commands.SORT_ID);
+           sortID.addActionListener(this);
+
+           ButtonGroup group = new ButtonGroup();
+           group.add(sortFileName);
+           group.add(sortID);
+               
+               JToolBar listTools = new JToolBar();
+               listTools.setFloatable(false);
+               listTools.add(watchFolderButton);
+               listTools.addSeparator();
+           listTools.add(new JLabel("Sort by"));
+               listTools.add(sortFileName);
+               listTools.add(sortID);
+
+               JPanel sidePanel = new JPanel();
+               sidePanel.setLayout(new BorderLayout());
+               sidePanel.add(listTools,BorderLayout.NORTH);
+               sidePanel.add(_listPanel,BorderLayout.CENTER);
+               
+               
+               JPanel mainVARNAPanel = new JPanel();
+               mainVARNAPanel.setLayout(new BorderLayout());
+               _splitVARNA = new JSplitPane(JSplitPane.VERTICAL_SPLIT,redrawOnSlide,_varnaUpperPanels,_varnaLowerPanels);
+               _splitVARNA.setDividerSize(dividerWidth);
+               _splitVARNA.setResizeWeight(1.0);
+               _splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,redrawOnSlide,sidePanel,_splitVARNA);
+               _splitLeft.setResizeWeight(0.1);
+               _splitLeft.setDividerSize(dividerWidth);
+               _splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,redrawOnSlide,_splitLeft,_infoPanel);
+               _splitRight.setResizeWeight(0.85);
+               _splitRight.setDividerSize(dividerWidth);
+               
+               _infoPanel.setLayout(new GridLayout(0,1));
+               
+               this.restoreConfig();           
+               this.getContentPane().setLayout(new BorderLayout());
+               this.getContentPane().add(_splitRight, BorderLayout.CENTER);
+               this.getContentPane().add(_toolbar, BorderLayout.NORTH);
+               addUpperPanel();
+               addUpperPanel();
+               this.setVisible(true);
+       }
+       
+       public FragSeqGUI getSelf()
+       {
+               return this;
+       }
+       
+       public VARNAHolder createIntegratedPanel(int height)
+       {
+               VARNAHolder vh = new VARNAHolder(this);
+               _varnaPanels.add(vh);
+               return vh;
+       }
+       
+       
+       public void removeUpperPanel()
+       {
+               if (_varnaUpperPanels.getComponentCount()>1)
+               {
+                       VARNAHolder vh = (VARNAHolder) _varnaUpperPanels.getComponent(_varnaUpperPanels.getComponentCount()-1);
+                       _infoPanel.remove(vh.getInfoPane());
+                       _varnaUpperPanels.remove(vh);
+                       _splitLeft.validate();
+                       _splitRight.validate();
+               }
+       }
+
+       public void addUpperPanel()
+       {
+               VARNAHolder vh = createIntegratedPanel(100);
+               _varnaUpperPanels.add(vh);
+               _infoPanel.add(vh.getInfoPane());
+               _splitRight.validate();
+               _splitLeft.validate();
+       }
+
+       
+       public void removeLowerPanel()
+       {
+               if (_varnaLowerPanels.getComponentCount()>0)
+               {
+                       _varnaLowerPanels.remove(_varnaLowerPanels.getComponentCount()-1);
+                       if (_varnaLowerPanels.getComponentCount()==0)
+                       {
+                               _splitVARNA.setDividerLocation(1.0);
+                               _splitVARNA.validate();
+                               _splitVARNA.repaint();
+                       }
+                       _splitLeft.validate();
+               }
+       }
+
+       public void addLowerPanel()
+       {
+               if (_varnaLowerPanels.getComponentCount()==0)
+               {
+                       _splitVARNA.setDividerLocation(0.7);
+                       _splitVARNA.validate();
+               }
+               _varnaLowerPanels.add(createIntegratedPanel(400));
+               _splitLeft.validate();
+       }
+
+
+       public void treeNodesChanged(TreeModelEvent e) {
+              DefaultMutableTreeNode node;
+               node = (DefaultMutableTreeNode)
+                        (e.getTreePath().getLastPathComponent());
+
+               /*
+                * If the event lists children, then the changed
+                * node is the child of the node we have already
+                * gotten.  Otherwise, the changed node and the
+                * specified node are the same.
+                */
+               try {
+                   int index = e.getChildIndices()[0];
+                   node = (DefaultMutableTreeNode)
+                          (node.getChildAt(index));
+               } catch (NullPointerException exc) {}
+
+       }
+       
+       
+
+
+       public void addFolder(String path) {
+               addFolder( path, true); 
+       }
+
+    public void addFolder(String path,
+            boolean shouldBeVisible) 
+    {
+       DefaultMutableTreeNode childNode = _treeModel.addFolder(path);
+       
+               if ((childNode!=null) && shouldBeVisible ) {
+                         System.out.println("  Expanding: "+childNode.getUserObject());
+                         TreePath tp = new TreePath(childNode.getPath());
+                       _sideList.scrollPathToVisible(tp);                      
+                       _sideList.expandRow(_sideList.getRowForPath(tp));
+                       _sideList.updateUI();
+                       _sideList.validate();
+               }
+    }
+
+       public void treeNodesInserted(TreeModelEvent e) {
+               System.out.println(e);
+               
+       }
+
+       public void treeNodesRemoved(TreeModelEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void treeStructureChanged(TreeModelEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseClicked(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mousePressed(MouseEvent e) {
+               
+       }
+
+       int index = 0;
+       
+       public void mouseReleased(MouseEvent e) {
+               // TODO Auto-generated method stub
+               if (e.getSource() == this._sideList)
+               {
+                       if (e.getClickCount() == 1)
+                       {
+                               /*TreePath t = _sideList.getSelectionPath();
+                               if (t!=null)
+                               {
+                               DefaultMutableTreeNode node = (DefaultMutableTreeNode) t.getLastPathComponent();
+                               if (node.getUserObject() instanceof FragSeqFileModel)
+                               {
+                                       int row = _sideList.getRowForPath(t);
+                                       System.out.println("[A]"+row);
+                                       if (!_sideList.isExpanded(row))
+                                       {
+                                               try {
+                                                       _sideList.fireTreeWillExpand(t);
+                                                       _sideList.expandPath(t);
+                                               } catch (ExpandVetoException e1) {
+                                                       // TODO Auto-generated catch block
+                                                       e1.printStackTrace();
+                                               }
+                                       }
+                                       else 
+                                       {
+                                               try {
+                                                       _sideList.fireTreeWillCollapse(t);
+                                                       _sideList.collapsePath(t);
+                                               } catch (ExpandVetoException e1) {
+                                                       // TODO Auto-generated catch block
+                                                       e1.printStackTrace();
+                                               }
+                                       }
+                               }
+                               }*/
+                       }
+                       else if (e.getClickCount() == 2)
+                       {
+                               TreePath t = _sideList.getSelectionPath();
+                               if (t!= null)
+                               {
+                                       DefaultMutableTreeNode node = (DefaultMutableTreeNode) t.getLastPathComponent();
+                                       if (node.getUserObject() instanceof FragSeqFileModel)
+                                       {
+                                               if (!_sideList.isExpanded(t))
+                                               {
+                                                       try {
+                                                               _sideList.fireTreeWillExpand(t);
+                                                               _sideList.expandPath(t);
+                                                       } catch (ExpandVetoException e1) {
+                                                               e1.printStackTrace();
+                                                       }
+                                               }
+                                               else 
+                                               {
+                                                       try {
+                                                               _sideList.fireTreeWillCollapse(t);
+                                                               _sideList.collapsePath(t);
+                                                       } catch (ExpandVetoException e1) {
+                                                               e1.printStackTrace();
+                                                       }
+                                               }
+                                       }
+                                       else if (node.getUserObject() instanceof FragSeqModel)
+                                       {
+                                               FragSeqModel model = (FragSeqModel) node.getUserObject();
+                                               
+                                               // Figuring out which panel to add object to...
+                                               int res;
+                                               if (model instanceof FragSeqRNASecStrModel)
+                                               {
+                                                       res = index % (_varnaUpperPanels.getComponentCount()+_varnaLowerPanels.getComponentCount());
+                                               }
+                                               else 
+                                               {
+                                                       res = (index+_varnaUpperPanels.getComponentCount()+_varnaLowerPanels.getComponentCount()-1) % (_varnaUpperPanels.getComponentCount()+_varnaLowerPanels.getComponentCount());
+                                               }
+                                               Component c = null;
+                                               if (res<_varnaUpperPanels.getComponentCount())
+                                               {
+                                                       c = (VARNAHolder)_varnaUpperPanels.getComponent(res);
+                                               }
+                                               else
+                                               {
+                                                       res -= _varnaUpperPanels.getComponentCount();
+                                                       c = (VARNAHolder)_varnaLowerPanels.getComponent(res);
+                                               }
+                                               
+                                               if (c instanceof VARNAHolder)
+                                               {
+                                                       VARNAHolder h = (VARNAHolder) c;
+                                                       if (model instanceof FragSeqRNASecStrModel)
+                                                       {
+                                                               h.setSecStrModel((FragSeqRNASecStrModel)model);
+                                                               index ++;
+                                                       }
+                                                       else if (model instanceof FragSeqAnnotationDataModel)
+                                                       {
+                                                               h.setDataModel((FragSeqAnnotationDataModel)model);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       
+       
+       
+       class VARNAHolder extends JPanel
+       {
+               VARNAPanel vp;
+               FragSeqRNASecStrModel _m;
+               FragSeqAnnotationDataModel _data;
+               JPanel _infoPanel;
+               JTextPane _infoTxt;
+               
+               
+               public VARNAHolder(DropTargetListener f)
+               {
+                       super();
+                       vp = new VARNAPanel();
+                       vp.addFocusListener(new FocusListener(){
+                               public void focusGained(FocusEvent e) {
+                                       //focus(_m);
+                               }
+                               public void focusLost(FocusEvent e) {
+                               }});
+                       vp.setPreferredSize(new Dimension(800, 400));
+
+                       _infoTxt = new JTextPane(); 
+                       _infoTxt.setPreferredSize(new Dimension(200,0));
+                       _infoTxt.setContentType("text/html");
+
+                       JScrollPane scroll = new JScrollPane(_infoTxt,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 
+
+                       _infoPanel = new JPanel();
+                       _infoPanel.setLayout(new BorderLayout());
+                       _infoPanel.setPreferredSize(new Dimension(200,0));
+                       _infoPanel.setBorder(BorderFactory.createTitledBorder("Info"));
+                       _infoPanel.add(scroll,BorderLayout.CENTER);
+                       _infoPanel.validate();
+
+                       this.setLayout(new BorderLayout());
+                       this.setPreferredSize(new Dimension(300,600));
+                       this.setBorder(BorderFactory.createTitledBorder("None"));                       
+                       this.add(vp, BorderLayout.CENTER);
+                       
+                       DropTarget dt = new DropTarget(vp, f);
+               }
+               
+               VARNAPanel getVARNAPanel()
+               {
+                       return vp;
+               }
+               void setSecStrModel(FragSeqRNASecStrModel m)
+               {
+                       _m = m;
+                       vp.showRNAInterpolated(m.getRNA());
+                       setBorder(BorderFactory.createTitledBorder(m.toString()));
+                       _infoTxt.setText(m.getRNA().getHTMLDescription());
+                       _infoPanel.setBorder(BorderFactory.createTitledBorder("Info ("+_m+")"));
+                       vp.requestFocus();
+               }
+               void setDataModel(FragSeqAnnotationDataModel data)
+               {
+                       _data = data;
+                       data.applyTo(vp.getRNA());
+                       vp.repaint();
+                       vp.requestFocus();
+               }
+               FragSeqModel getModel()
+               {
+                       setBorder(BorderFactory.createTitledBorder(_m.toString()));
+                       return _m;
+               }
+               public void setInfoTxt(String s)
+               {
+                       _infoTxt.setText(vp.getRNA().getHTMLDescription());
+                       _infoTxt.validate();
+               }
+               public JPanel getInfoPane()
+               {
+                       return _infoPanel;
+               }
+               
+       }
+       
+
+       private ArrayList<VARNAHolder> _varnaPanels = new ArrayList<VARNAHolder>(); 
+       
+       private VARNAHolder getHolder(Component vp)
+       {
+               if (vp instanceof VARNAHolder)
+               {
+                       int i= _varnaPanels.indexOf(vp);
+                       if (i!=-1)
+                       {
+                               return _varnaPanels.get(i);
+                       }
+               }
+               if (vp instanceof VARNAPanel)
+               {
+                       for (VARNAHolder vh: _varnaPanels)
+                       {
+                               if (vh.getVARNAPanel()==vp)
+                               return vh;
+                       }
+               }
+               return null;
+       }
+
+
+       
+       public void mouseEntered(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+               
+       }
+
+       public void drop(DropTargetDropEvent arg0) {
+                       try {
+                               
+                               DropTarget o = (DropTarget)arg0.getSource();
+                               if (o.getComponent() instanceof VARNAPanel)
+                               {
+                                       VARNAHolder h = getHolder(o.getComponent());
+                                       if (h!=null)
+                                       {
+                                               System.out.println("[X]");
+                                               Transferable t = arg0.getTransferable();
+                                               if (t.isDataFlavorSupported(FragSeqRNASecStrModel.Flavor))
+                                               {
+                                                       Object data = t.getTransferData(FragSeqRNASecStrModel.Flavor);
+                                                       if (data instanceof FragSeqRNASecStrModel)
+                                                       {
+                                                               h.setSecStrModel((FragSeqRNASecStrModel) data);
+                                                       }
+                                               }
+                                               else if (t.isDataFlavorSupported(FragSeqAnnotationDataModel.Flavor))
+                                               {                                                       
+                                                       System.out.println("[Y]");
+                                                       Object data = t.getTransferData(FragSeqAnnotationDataModel.Flavor);
+                                                       if (data instanceof FragSeqAnnotationDataModel)
+                                                       {
+                                                               FragSeqAnnotationDataModel d = (FragSeqAnnotationDataModel) data;
+                                                               h.setDataModel(d);
+                                                       }
+                                               }
+                                       }
+                               }
+                       } catch (UnsupportedFlavorException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (IOException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } 
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowOpened(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowClosing(WindowEvent e) {
+               saveConfig();
+               System.exit(0);
+       }
+
+       public void windowClosed(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowIconified(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowDeiconified(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowActivated(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowDeactivated(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       private void restoreConfig()
+       {
+               BasicINI config = BasicINI.loadINI(_INIFilename);
+       ArrayList<String> vals = config.getItemList("folders");
+               System.out.print("[C]"+vals);
+       
+           for(String path:vals)
+               {
+               System.out.println("Loading folder "+path);
+                 addFolder(path);
+               }
+           _sideList.validate();
+           _listScroller.validate();
+       }
+
+       private void saveConfig()
+       {
+               BasicINI data = new BasicINI();
+               int i=0;
+               for (String folderPath: _treeModel.getFolders())
+               {
+                 data.addItem("folders", "val"+i, folderPath);
+                 i++;
+               }
+               BasicINI.saveINI(data, _INIFilename);
+       }
+
+
+       public void componentResized(ComponentEvent e) {
+               _sideList.validate();
+       }
+
+
+       public void componentMoved(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void componentShown(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void componentHidden(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void actionPerformed(ActionEvent e) {
+               String cmd = e.getActionCommand();
+               System.out.println(cmd);
+               if (cmd.equals(""+Commands.NEW_FOLDER))
+               {
+                       _choice.setDialogTitle("Watch new folder...");
+                       _choice.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+                       _choice.setAcceptAllFileFilterUsed(false);
+                       try {
+                               if (_choice.showOpenDialog(getSelf()) == JFileChooser.APPROVE_OPTION) { 
+                                       addFolder(_choice.getSelectedFile().getCanonicalPath());
+                               }
+                               else {
+                                       System.out.println("No Selection ");
+                               }
+                       } catch (IOException e1) {
+                               e1.printStackTrace();
+                       }
+               }
+               else if (cmd.equals(""+Commands.ADD_PANEL_DOWN))
+               {
+                       addLowerPanel();
+               }
+               else if (cmd.equals(""+Commands.ADD_PANEL_UP))
+               {
+                       addUpperPanel();
+               }
+               else if (cmd.equals(""+Commands.REMOVE_PANEL_DOWN))
+               {
+                       removeLowerPanel();
+               }
+               else if (cmd.equals(""+Commands.REMOVE_PANEL_UP))
+               {
+                       removeUpperPanel();
+               }
+               else if (cmd.equals(""+Commands.SORT_FILENAME))
+               {
+                       _sideList.switchToPath();
+               }
+               else if (cmd.equals(""+Commands.SORT_ID))
+               {
+                       _sideList.switchToID();
+               }
+               else if (cmd.equals(""+Commands.TEST_XML))
+               {
+                       String path = "temp.xml";
+                       VARNAHolder vh = (VARNAHolder) _varnaUpperPanels.getComponent(0);
+                       vh.vp.toXML(path);
+                       try {
+                               FullBackup b = vh.vp.importSession(path);
+                               VARNAHolder vh2 = (VARNAHolder) _varnaUpperPanels.getComponent(1);
+                               vh2.vp.setConfig(b.config);
+                               vh2.vp.showRNAInterpolated(b.rna);
+                               vh2.vp.repaint();
+                       } catch (ExceptionLoadingFailed e1) {
+                               e1.printStackTrace();
+                       }
+               }
+               else if (cmd.equals(""+Commands.CHANGE_LNF))
+               {
+                       try {
+                               
+                               Object o  = _lnf.getModel().getSelectedItem();
+                               System.out.println(o);
+                               UIManager.setLookAndFeel(((LookAndFeelInfo)_lnf.getModel().getSelectedItem()).getClassName());
+                               SwingUtilities.updateComponentTreeUI(this);
+                               this.pack();
+                       } catch (UnsupportedLookAndFeelException e1) {
+                               // TODO Auto-generated catch block
+                               e1.printStackTrace();
+                       } catch (ClassNotFoundException e2) {
+                               // TODO Auto-generated catch block
+                               e2.printStackTrace();
+                       } catch (InstantiationException e3) {
+                               // TODO Auto-generated catch block
+                               e3.printStackTrace();
+                       } catch (IllegalAccessException e4) {
+                               // TODO Auto-generated catch block
+                               e4.printStackTrace();
+                       }
+               }
+               else
+               {
+                       JOptionPane.showMessageDialog(this, "Command '"+cmd+"' not implemented yet.");
+               }
+       }
+
+
+       public void valueChanged(TreeSelectionEvent e) {
+               int[] t = _sideList.getSelectionRows();
+               if (t==null)
+               {
+                       System.out.print("null");
+               }
+               else
+               {
+                       System.out.print("[");
+                       for(int i=0;i<t.length;i++)
+                       {
+                               System.out.print(t[i]+",");
+                       }
+                       System.out.println("]");
+               }
+       }
+
+       
+       public static void main(String[] args) {
+               FragSeqGUI d = new FragSeqGUI();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqModel.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqModel.java
new file mode 100644 (file)
index 0000000..fdb7272
--- /dev/null
@@ -0,0 +1,32 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public abstract class FragSeqModel implements Comparable<FragSeqModel> {
+
+        public abstract String getID();
+        
+        public int compareTo(FragSeqModel o) {
+                       // TODO Auto-generated method stub
+                       return 0;
+               }
+  
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqNode.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqNode.java
new file mode 100644 (file)
index 0000000..efdd6b0
--- /dev/null
@@ -0,0 +1,17 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class FragSeqNode extends DefaultMutableTreeNode
+{
+       
+       public FragSeqNode(Object o)
+       {
+               super(o);
+       }
+       
+       public boolean isLeaf()
+       {
+               return (this.getUserObject() instanceof FragSeqModel);
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqRNASecStrModel.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqRNASecStrModel.java
new file mode 100644 (file)
index 0000000..6c3c6f2
--- /dev/null
@@ -0,0 +1,51 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class FragSeqRNASecStrModel extends FragSeqModel {
+       private RNA _r =null;
+       
+  public FragSeqRNASecStrModel(RNA r)
+  {
+       _r = r;
+  }
+
+
+  public String toString()
+  {
+         return _r.getName();
+  }
+  
+  public String getID()
+  {
+         return _r.getID();
+  }
+  
+
+  public RNA getRNA()
+  {
+         return _r;
+  }
+  public static DataFlavor Flavor = new DataFlavor(FragSeqRNASecStrModel.class, "RNA Sec Str Object");
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTree.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTree.java
new file mode 100644 (file)
index 0000000..d9bed27
--- /dev/null
@@ -0,0 +1,108 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragGestureRecognizer;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.MouseDragGestureRecognizer;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.Enumeration;
+
+import javax.swing.JComponent;
+import javax.swing.JTree;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.AbstractLayoutCache;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+public class FragSeqTree extends JTree implements MouseListener {
+       private Watcher _w;
+       
+       
+       public FragSeqTree(FragSeqTreeModel m)
+       {
+               super(m);
+               _w = new Watcher(m ); 
+               _w.start();
+       }
+       
+       public DefaultMutableTreeNode getSelectedNode()
+       {
+               TreePath t = getSelectionPath();
+               if (t!= null)
+               {
+                       return (DefaultMutableTreeNode) t.getLastPathComponent();
+               }
+               return null;
+       }
+
+       public void mouseClicked(MouseEvent e) {
+               int x = e.getX();
+               int y = e.getY();
+               TreePath tp = this.getPathForLocation(x, y);
+               if (tp!=null)
+               {
+                       DefaultMutableTreeNode n = (DefaultMutableTreeNode) tp.getLastPathComponent();
+                       
+               }
+       }
+       
+       public void switchToPath()
+       {
+               FragSeqTreeModel m = (FragSeqTreeModel) getModel();
+               cancelEditing();
+               m.setRoot(m.getPathViewRoot());
+               
+               Enumeration en = m.getRoot().depthFirstEnumeration();
+               while(en.hasMoreElements())
+               {
+                       FragSeqNode n = (FragSeqNode) en.nextElement();
+                       if(m.isExpanded(n))
+                       {
+                               expandPath(new TreePath(n.getPath()));
+                       }
+               }
+       }
+
+       public void switchToID()
+       {
+               FragSeqTreeModel m = (FragSeqTreeModel) getModel();
+               cancelEditing();
+               m.setRoot(m.getIDViewRoot());
+               Enumeration en = m.getRoot().depthFirstEnumeration();
+               while(en.hasMoreElements())
+               {
+                       FragSeqNode n = (FragSeqNode) en.nextElement();
+                       if(m.isExpanded(n))
+                       {
+                               expandPath(new TreePath(n.getPath()));
+                       }
+               }
+       }
+       
+       public void mousePressed(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseReleased(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseEntered(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTreeModel.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/FragSeqTreeModel.java
new file mode 100644 (file)
index 0000000..6586c04
--- /dev/null
@@ -0,0 +1,256 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.PriorityQueue;
+import java.util.Random;
+import java.util.TreeSet;
+
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeWillExpandListener;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.ExpandVetoException;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+public class FragSeqTreeModel extends DefaultTreeModel implements TreeWillExpandListener{
+       
+         private FragSeqNode _rootIDs = new FragSeqNode("IDs");
+         private FragSeqNode _rootFolders = new FragSeqNode("Folders");
+
+       private TreeSet<String> _folders = new TreeSet<String>();
+  private Hashtable<String,FragSeqNode> _folderPathToFolderNode = new Hashtable<String,FragSeqNode>();
+
+  private Hashtable<String,FragSeqNode> _idsToNode = new Hashtable<String,FragSeqNode>();
+  private Hashtable<String,ArrayList<FragSeqNode>> _pathToIDFileNodes = new Hashtable<String,ArrayList<FragSeqNode>>();
+  
+  public enum SORT_MODE{
+         PATH,
+         ID
+  }
+  
+  private SORT_MODE _mode = SORT_MODE.PATH; 
+       
+  public FragSeqTreeModel()
+  {
+         this(new FragSeqNode("Folders"));  
+         
+  }
+  public FragSeqTreeModel(TreeNode t)
+  {
+         super(t);
+         this.setRoot(_rootFolders);
+  }
+  
+  
+  public FragSeqNode getPathViewRoot()
+  {
+         return _rootFolders;
+  }
+
+  public FragSeqNode getIDViewRoot()
+  {
+         return _rootIDs;
+  }
+
+  public void switchToIDView()
+  {
+         if (_mode!=SORT_MODE.ID)
+         {
+                 this.setRoot(this._rootIDs);
+                 
+         }
+         _mode=SORT_MODE.ID;
+         
+  }
+
+  private void removeAllNodes(ArrayList<FragSeqNode> toBeRemoved)
+  {
+         for(FragSeqNode leafNode : toBeRemoved)
+         {
+                 FragSeqNode parent = (FragSeqNode) leafNode.getParent();
+                 parent.remove(leafNode);
+                 if (parent.getChildCount()==0)
+                 {
+                         parent.removeFromParent();
+                         _folderPathToFolderNode.remove(parent);
+                         if (parent.getUserObject() instanceof String)
+                         {
+                                 String path = parent.getUserObject().toString();
+                         }
+                 }
+                 else
+                 {
+                         reload(parent);
+                 }
+         }       
+  }
+
+  
+  public FragSeqNode getNodeForId(String id)
+  {
+         if(!_idsToNode.containsKey(id))
+         {
+                 FragSeqNode idNode = new FragSeqNode(id);
+                 _idsToNode.put(id, idNode);
+                 _rootIDs.add(idNode);
+         }
+         FragSeqNode idNode = _idsToNode.get(id);
+         return idNode;
+  }
+
+  public void removeFolder(String path)
+  {
+         ArrayList<FragSeqNode> toBeRemoved = new ArrayList<FragSeqNode>(); 
+         Enumeration en = _folderPathToFolderNode.get(path).children();
+         while(en.hasMoreElements())
+         {
+                 FragSeqNode n = (FragSeqNode) en.nextElement();
+                 toBeRemoved.add(n);
+         }
+         removeAllNodes(toBeRemoved);
+         _folders.remove(path);
+  }
+  
+  
+  public FragSeqNode insertGroupNode(String crit, TreeSet<String> t)
+  {
+         FragSeqNode groupNode = new FragSeqNode(crit);
+         FragSeqNode parent = getRoot();
+         int pos = t.headSet(crit).size();
+         parent.insert(groupNode, pos);         
+         reload(groupNode);
+         return groupNode;
+  }
+  
+
+  
+  public void insertFileNode(FragSeqNode parent, FragSeqFileModel m)
+  {
+         FragSeqNode leafNode = new FragSeqNode(m);
+         parent.add(leafNode);
+  }
+
+  public FragSeqNode addFolder(String path)
+  {
+         FragSeqNode groupNode = null;
+         try {
+                 if (!_folders.contains(path))
+                 {
+                         File dir = new File(path);
+                         if (dir.isDirectory())
+                         {
+                                 path = dir.getCanonicalPath();
+                                 _folders.add(path);
+                                 groupNode = insertGroupNode(path, _folders);
+                                 _folderPathToFolderNode.put(path,groupNode);
+                                 for(File f:dir.listFiles(_f))
+                                 {
+                                         addFile(path,f.getCanonicalPath());
+                                 }
+                         }
+                 }
+         } catch (IOException e) {
+                 e.printStackTrace();
+         }
+         return groupNode;
+  }
+    
+  private void addFile(String folder, String path)
+  {
+         System.out.println("  => "+path);
+         FragSeqFileModel m = new FragSeqFileModel(folder,path);
+         addFolder(folder);
+         insertFileNode(_folderPathToFolderNode.get(folder), m);
+  }
+  
+  public FragSeqNode getRoot()
+  {
+         return (FragSeqNode) super.getRoot();
+  }
+  
+  public ArrayList<String> getFolders()
+  {
+         ArrayList<String> result = new ArrayList<String>(_folders);
+         return result;
+  }
+  
+  
+   FilenameFilter _f = new FilenameFilter(){
+               public boolean accept(File dir, String name) {
+                       return name.toLowerCase().endsWith(".dbn") 
+                       || name.toLowerCase().endsWith(".ct")
+                       || name.toLowerCase().endsWith(".bpseq")
+                       || name.toLowerCase().endsWith(".rnaml");       
+               }};
+         
+  public FilenameFilter getFileNameFilter()
+  {
+       return _f;
+  }
+
+  public void setFileNameFilter(FilenameFilter f)
+  {
+       _f = f;
+  }
+
+  
+private Hashtable<FragSeqNode,Boolean> _isExpanded = new Hashtable<FragSeqNode,Boolean>();
+  
+public boolean isExpanded(FragSeqNode n)
+{
+  if(_isExpanded.containsKey(n))
+  {
+       return _isExpanded.get(n);
+  }
+  else
+         return false;
+}
+
+public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
+       if (event.getSource() instanceof FragSeqTree)
+       {
+               FragSeqTree tree = (FragSeqTree) event.getSource();
+               TreePath t = event.getPath();
+               FragSeqNode n = (FragSeqNode) t.getLastPathComponent();
+               _isExpanded.put(n, true);
+               Object o = n.getUserObject();
+               if (o instanceof FragSeqFileModel)
+               {
+                       FragSeqFileModel f = (FragSeqFileModel) o;
+                       if (!f._cached)
+                       {                         
+                               String path = f.getPath();
+                               if (!_pathToIDFileNodes.containsKey(path))
+                               {
+                                       _pathToIDFileNodes.put(path, new ArrayList<FragSeqNode>());
+                               }
+                               ArrayList<FragSeqNode> nodesForID = _pathToIDFileNodes.get(path);
+                               for(FragSeqModel m: f.getModels())
+                               { 
+                                       n.add(new FragSeqNode(m));
+                                       FragSeqNode nid = getNodeForId(m.getID());
+                                       nid.add(new FragSeqNode(m));
+                                       nodesForID.add(nid);
+                               } 
+                       }
+               }
+       }
+}
+
+public void treeWillCollapse(TreeExpansionEvent event)
+               throws ExpandVetoException {
+       // TODO Auto-generated method stub
+       TreePath t = event.getPath();
+       FragSeqNode n = (FragSeqNode) t.getLastPathComponent();
+       _isExpanded.put(n, false);
+       
+       
+}
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/fragseq/Watcher.java b/srcjar/fr/orsay/lri/varna/applications/fragseq/Watcher.java
new file mode 100644 (file)
index 0000000..443ba27
--- /dev/null
@@ -0,0 +1,47 @@
+package fr.orsay.lri.varna.applications.fragseq;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+public class Watcher  extends Thread {
+       
+       private FragSeqTreeModel _model;
+       private boolean _terminated = false; 
+       
+       public Watcher(FragSeqTreeModel model)
+       {
+               _model = model;
+       }
+       
+       public void run() {
+               while (!_terminated)
+               {
+                       ArrayList<String> folders = _model.getFolders();
+                       for (String path: folders)
+                       {
+                         _model.addFolder(path);
+                         System.out.println("Watching ["+path+"]");
+                       }
+                       try {
+                               this.sleep(1000);
+                       } catch (InterruptedException e) {
+                       }
+               }
+               
+       }
+       
+
+
+       
+       
+       public void finish()
+       {
+               _terminated = true;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUI.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUI.java
new file mode 100644 (file)
index 0000000..26a7334
--- /dev/null
@@ -0,0 +1,750 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextPane;
+import javax.swing.JToolBar;
+import javax.swing.JTree;
+import javax.swing.ListSelectionModel;
+import javax.swing.TransferHandler;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.BasicINI;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+
+
+public class VARNAGUI extends JFrame implements TreeModelListener, MouseListener,DropTargetListener, WindowListener, ComponentListener, ActionListener {
+
+       private enum Commands
+       {
+               NEW_FOLDER,
+               ADD_PANEL_UP,
+               ADD_PANEL_DOWN,
+               REMOVE_PANEL_UP,
+               REMOVE_PANEL_DOWN,
+               SORT_ID,
+               SORT_FILENAME,
+               REFRESH_ALL,
+       };
+       
+       
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -790155708306987257L;
+
+
+               
+       private String _INIFilename = "FragSeqUI.ini";
+       private Color _backgroundColor = Color.white;
+       private boolean redrawOnSlide = false;
+       private int dividerWidth = 5;
+       
+       private JPanel _varnaUpperPanels = new JPanel();
+       private JPanel _varnaLowerPanels = new JPanel();
+
+       private JPanel _listPanel = new JPanel();
+       private JPanel _infoPanel = new JPanel();
+       private VARNAGUITree _sideList = null;
+
+       
+       private VARNAGUITreeModel _treeModel;
+       private JToolBar _toolbar = new JToolBar();
+       private JFileChooser _choice = new JFileChooser();
+       
+       private JScrollPane _listScroller;
+
+       private JList _selectedElems;
+       private JSplitPane _splitLeft;
+       private JSplitPane _splitRight;
+       private JSplitPane _splitVARNA;
+       
+
+
+       public VARNAGUI() {
+               super("VARNA Explorer");
+               RNAPanelDemoInit();
+       }
+
+       
+       private void RNAPanelDemoInit() 
+       {
+               JFrame.setDefaultLookAndFeelDecorated(true);
+               this.addWindowListener(this);
+           
+               _selectedElems = new JList();
+               
+               // Initializing Custom Tree Model
+               _treeModel = new VARNAGUITreeModel();
+               _treeModel.addTreeModelListener(this);
+               
+               _sideList = new VARNAGUITree(_treeModel);
+               _sideList.addMouseListener(this);
+               _sideList.setLargeModel(true);
+               _sideList.setEditable(true);
+               VARNAGUIRenderer renderer = new VARNAGUIRenderer(_sideList,_treeModel);
+               //_sideList.setUI(new CustomTreeUI());
+               _sideList.setCellRenderer(renderer);
+               _sideList.setCellEditor(new VARNAGUICellEditor(_sideList,renderer,_treeModel));
+               TreeSelectionModel m = _sideList.getSelectionModel();
+               m.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+               _sideList.setSelectionModel(m);
+               _sideList.setShowsRootHandles(true);
+               _sideList.setDragEnabled(true);
+               _sideList.setRootVisible(false);
+               _sideList.setTransferHandler(new TransferHandler(null) 
+               {
+                       public int getSourceActions(JComponent c) {
+                               return COPY_OR_MOVE;
+                       }
+                       protected Transferable createTransferable(JComponent c) {
+                               JTree tree = (JTree) c;
+                               TreePath tp =tree.getSelectionPath();
+                               if (tp!=null)
+                               {
+                                       DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
+                                       if (node.getUserObject() instanceof VARNAGUIModel) {
+                                               return new Transferable(){
+                                                       public DataFlavor[] getTransferDataFlavors() {
+                                                               DataFlavor[] dt = {VARNAGUIModel.Flavor};
+                                                               return dt;
+                                                       }
+                                                       public Object getTransferData(DataFlavor df)
+                                                       throws UnsupportedFlavorException, IOException {
+                                                               if (!isDataFlavorSupported(df))
+                                                                       throw new UnsupportedFlavorException(df);
+                                                               DefaultMutableTreeNode node = (DefaultMutableTreeNode) _sideList.getSelectionPath().getLastPathComponent();
+                                                               return node.getUserObject();
+                                                       }
+                                                       public boolean isDataFlavorSupported(DataFlavor df) {
+                                                               return VARNAGUIModel.Flavor.equals(df);
+                                                       }
+                                               };
+                                       } else {
+                                               return null;
+                                       }
+                               }
+                               return null;
+                       }
+               });
+
+               // Various buttons
+               JButton refreshAllFoldersButton = new JButton("Refresh All");
+               refreshAllFoldersButton.setActionCommand(""+Commands.REFRESH_ALL);
+               refreshAllFoldersButton.addActionListener(this);
+               JButton watchFolderButton = new JButton("Add folder");
+               watchFolderButton.setActionCommand("" +Commands.NEW_FOLDER);
+               watchFolderButton.addActionListener(this);
+               JButton addUpperButton = new JButton("+Up");
+               addUpperButton.setActionCommand(""+Commands.ADD_PANEL_UP);
+               addUpperButton.addActionListener(this);
+               JButton removeUpperButton = new JButton("-Up");
+               removeUpperButton.setActionCommand(""+Commands.REMOVE_PANEL_UP);
+               removeUpperButton.addActionListener(this);
+               JButton addLowerButton = new JButton("+Down");
+               addLowerButton.setActionCommand(""+Commands.ADD_PANEL_DOWN);
+               addLowerButton.addActionListener(this);
+               JButton removeLowerButton = new JButton("-Down");
+               removeLowerButton.setActionCommand(""+Commands.REMOVE_PANEL_DOWN);
+               removeLowerButton.addActionListener(this);
+
+               _toolbar.setFloatable(false);
+               _toolbar.add(refreshAllFoldersButton);
+               _toolbar.addSeparator();
+               _toolbar.add(addUpperButton);
+               _toolbar.add(removeUpperButton);
+               _toolbar.add(addLowerButton);
+               _toolbar.add(removeLowerButton);
+               
+               // Scroller for File tree
+           _listScroller = new JScrollPane(_sideList,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+           _listScroller.setPreferredSize(new Dimension(300, 200));
+           _listScroller.addComponentListener(this);
+
+               
+               _listPanel.setLayout(new BorderLayout());
+               
+               _listPanel.add(_listScroller,BorderLayout.CENTER);
+               _listPanel.add(_selectedElems,BorderLayout.SOUTH);
+               _listPanel.setBorder(BorderFactory.createTitledBorder("Structures"));
+               _listPanel.setPreferredSize(new Dimension(300, 0));
+               
+               _varnaUpperPanels.setLayout(new GridLayout());
+               _varnaUpperPanels.setPreferredSize(new Dimension(800, 600));
+               
+               _varnaLowerPanels.setLayout(new GridLayout());
+               _varnaLowerPanels.setPreferredSize(new Dimension(800, 000));
+               
+           JRadioButton sortFileName = new JRadioButton("Filename");
+           sortFileName.setActionCommand("sortfilename");
+           sortFileName.setSelected(true);
+           sortFileName.setOpaque(false);
+           sortFileName.setActionCommand(""+Commands.SORT_FILENAME);
+           sortFileName.addActionListener(this);
+           JRadioButton sortID = new JRadioButton("ID");
+           sortID.setActionCommand("sortid");
+           sortID.setOpaque(false);
+           sortID.setActionCommand(""+Commands.SORT_ID);
+           sortID.addActionListener(this);
+
+           ButtonGroup group = new ButtonGroup();
+           group.add(sortFileName);
+           group.add(sortID);
+               
+               JToolBar listTools = new JToolBar();
+               listTools.setFloatable(false);
+               listTools.add(watchFolderButton);
+               listTools.addSeparator();
+           listTools.add(new JLabel("Sort by"));
+               listTools.add(sortFileName);
+               listTools.add(sortID);
+
+               JPanel sidePanel = new JPanel();
+               sidePanel.setLayout(new BorderLayout());
+               sidePanel.add(listTools,BorderLayout.NORTH);
+               sidePanel.add(_listPanel,BorderLayout.CENTER);
+               
+               
+               JPanel mainVARNAPanel = new JPanel();
+               mainVARNAPanel.setLayout(new BorderLayout());
+               _splitVARNA = new JSplitPane(JSplitPane.VERTICAL_SPLIT,redrawOnSlide,_varnaUpperPanels,_varnaLowerPanels);
+               _splitVARNA.setDividerSize(dividerWidth);
+               _splitVARNA.setResizeWeight(1.0);
+               _splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,redrawOnSlide,sidePanel,_splitVARNA);
+               _splitLeft.setResizeWeight(0.1);
+               _splitLeft.setDividerSize(dividerWidth);
+               _splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,redrawOnSlide,_splitLeft,_infoPanel);
+               _splitRight.setResizeWeight(0.85);
+               _splitRight.setDividerSize(dividerWidth);
+               
+               _infoPanel.setLayout(new GridLayout(0,1));
+               
+               this.restoreConfig();           
+               this.setBackground(_backgroundColor);
+               this.getContentPane().setLayout(new BorderLayout());
+               this.getContentPane().add(_splitRight, BorderLayout.CENTER);
+               this.getContentPane().add(_toolbar, BorderLayout.NORTH);
+               addUpperPanel();
+               this.setVisible(true);
+       }
+       
+       public VARNAGUI getSelf()
+       {
+               return this;
+       }
+       
+       public VARNAHolder createIntegratedPanel(int height)
+       {
+               VARNAHolder vh = new VARNAHolder(this);
+               _varnaPanels.add(vh);
+               return vh;
+       }
+       
+       
+       public void removeUpperPanel()
+       {
+               if (_varnaUpperPanels.getComponentCount()>1)
+               {
+                       VARNAHolder vh = (VARNAHolder) _varnaUpperPanels.getComponent(_varnaUpperPanels.getComponentCount()-1);
+                       _infoPanel.remove(vh.getInfoPane());
+                       _varnaUpperPanels.remove(vh);
+                       _splitLeft.validate();
+                       _splitRight.validate();
+               }
+       }
+
+       public void addUpperPanel()
+       {
+               VARNAHolder vh = createIntegratedPanel(100);
+               _varnaUpperPanels.add(vh);
+               _infoPanel.add(vh.getInfoPane());
+               _splitRight.validate();
+               _splitLeft.validate();
+       }
+
+       
+       public void removeLowerPanel()
+       {
+               if (_varnaLowerPanels.getComponentCount()>0)
+               {
+                       _varnaLowerPanels.remove(_varnaLowerPanels.getComponentCount()-1);
+                       if (_varnaLowerPanels.getComponentCount()==0)
+                       {
+                               _splitVARNA.setDividerLocation(1.0);
+                               _splitVARNA.validate();
+                               _splitVARNA.repaint();
+                       }
+                       _splitLeft.validate();
+               }
+       }
+
+       public void addLowerPanel()
+       {
+               if (_varnaLowerPanels.getComponentCount()==0)
+               {
+                       _splitVARNA.setDividerLocation(0.7);
+                       _splitVARNA.validate();
+               }
+               _varnaLowerPanels.add(createIntegratedPanel(400));
+               _splitLeft.validate();
+       }
+
+       public static void main(String[] args) {
+               try {
+                   for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
+                       if ("Nimbus".equals(info.getName())) {
+                           UIManager.setLookAndFeel(info.getClassName());
+                           break;
+                       }
+                   }
+               } catch (Exception e) {
+                   // If Nimbus is not available, you can set the GUI to another look and feel.
+               }
+               VARNAGUI d = new VARNAGUI();
+               d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               d.pack();
+               d.setVisible(true);
+       }
+
+       public void treeNodesChanged(TreeModelEvent e) {
+              DefaultMutableTreeNode node;
+               node = (DefaultMutableTreeNode)
+                        (e.getTreePath().getLastPathComponent());
+
+               /*
+                * If the event lists children, then the changed
+                * node is the child of the node we have already
+                * gotten.  Otherwise, the changed node and the
+                * specified node are the same.
+                */
+               try {
+                   int index = e.getChildIndices()[0];
+                   node = (DefaultMutableTreeNode)
+                          (node.getChildAt(index));
+               } catch (NullPointerException exc) {}
+
+       }
+       
+       
+
+
+       public void addFolder(String path) {
+               addFolder( path, true); 
+       }
+
+    public void addFolder(String path,
+            boolean shouldBeVisible) 
+    {
+       DefaultMutableTreeNode childNode = _treeModel.addFolder(path);
+       
+               if ((childNode!=null) && shouldBeVisible ) {
+                         System.out.println("  Expanding: "+childNode.getUserObject());
+                         TreePath tp = new TreePath(childNode.getPath());
+                       _sideList.scrollPathToVisible(tp);                      
+                       _sideList.expandRow(_sideList.getRowForPath(tp));
+                       _sideList.updateUI();
+                       _sideList.validate();
+               }
+    }
+
+       public void treeNodesInserted(TreeModelEvent e) {
+               System.out.println(e);
+               
+       }
+
+       public void treeNodesRemoved(TreeModelEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void treeStructureChanged(TreeModelEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseClicked(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mousePressed(MouseEvent e) {
+               
+       }
+
+       int index = 0;
+       
+       public void mouseReleased(MouseEvent e) {
+               // TODO Auto-generated method stub
+               if (e.getSource() == this._sideList)
+               {
+                       if (e.getClickCount() == 2)
+                       {
+                               TreePath t = _sideList.getSelectionPath();
+                               if (t!= null)
+                               {
+                                       DefaultMutableTreeNode node = (DefaultMutableTreeNode) t.getLastPathComponent();
+                                       if (node.getUserObject() instanceof VARNAGUIModel)
+                                       {
+                                               VARNAGUIModel model = (VARNAGUIModel) node.getUserObject();
+                                               int res = index % (_varnaUpperPanels.getComponentCount()+_varnaLowerPanels.getComponentCount());
+                                               Component c = null;
+                                               if (res<_varnaUpperPanels.getComponentCount())
+                                               {
+                                                       c = (VARNAHolder)_varnaUpperPanels.getComponent(res);
+                                               }
+                                               else
+                                               {
+                                                       res -= _varnaUpperPanels.getComponentCount();
+                                                       c = (VARNAHolder)_varnaLowerPanels.getComponent(res);
+                                               }
+                                               if (c instanceof VARNAHolder)
+                                               {
+                                                       VARNAHolder h = (VARNAHolder) c;
+                                                       h.setModel(model);
+                                               }
+                                               index ++;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       
+       
+       
+       class VARNAHolder extends JPanel
+       {
+               VARNAPanel vp;
+               VARNAGUIModel _m;
+               JPanel _infoPanel;
+               JTextPane _infoTxt;
+               public VARNAHolder(DropTargetListener f)
+               {
+                       super();
+                       vp = new VARNAPanel();
+                       vp.addFocusListener(new FocusListener(){
+                               public void focusGained(FocusEvent e) {
+                                       //focus(_m);
+                               }
+                               public void focusLost(FocusEvent e) {
+                               }});
+                       vp.setPreferredSize(new Dimension(800, 400));
+                       vp.setBackground(_backgroundColor);
+
+                       _infoTxt = new JTextPane(); 
+                       _infoTxt.setPreferredSize(new Dimension(200,0));
+                       _infoTxt.setContentType("text/html");
+
+                       JScrollPane scroll = new JScrollPane(_infoTxt,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 
+
+                       _infoPanel = new JPanel();
+                       _infoPanel.setLayout(new BorderLayout());
+                       _infoPanel.setPreferredSize(new Dimension(200,0));
+                       _infoPanel.setBorder(BorderFactory.createTitledBorder("Info"));
+                       _infoPanel.add(scroll,BorderLayout.CENTER);
+                       _infoPanel.validate();
+
+                       this.setLayout(new BorderLayout());
+                       this.setPreferredSize(new Dimension(300,600));
+                       this.setBorder(BorderFactory.createTitledBorder("None"));                       
+                       this.add(vp, BorderLayout.CENTER);
+                       
+                       DropTarget dt = new DropTarget(vp, f);
+               }
+               
+               VARNAPanel getVARNAPanel()
+               {
+                       return vp;
+               }
+               void setModel(VARNAGUIModel m)
+               {
+                       _m = m;
+                       vp.showRNAInterpolated(m.getRNA());
+                       setBorder(BorderFactory.createTitledBorder(m.toString()));
+                       _infoTxt.setText(m.getRNA().getHTMLDescription());
+                       _infoPanel.setBorder(BorderFactory.createTitledBorder("Info ("+_m+")"));
+                       vp.requestFocus();
+               }
+               VARNAGUIModel getModel()
+               {
+                       setBorder(BorderFactory.createTitledBorder(_m.toString()));
+                       return _m;
+               }
+               public void setInfoTxt(String s)
+               {
+                       _infoTxt.setText(vp.getRNA().getHTMLDescription());
+                       _infoTxt.validate();
+               }
+               public JPanel getInfoPane()
+               {
+                       return _infoPanel;
+               }
+               
+       }
+       
+
+       private ArrayList<VARNAHolder> _varnaPanels = new ArrayList<VARNAHolder>(); 
+       
+       private VARNAHolder getHolder(Component vp)
+       {
+               if (vp instanceof VARNAHolder)
+               {
+                       int i= _varnaPanels.indexOf(vp);
+                       if (i!=-1)
+                       {
+                               return _varnaPanels.get(i);
+                       }
+               }
+               if (vp instanceof VARNAPanel)
+               {
+                       for (VARNAHolder vh: _varnaPanels)
+                       {
+                               if (vh.getVARNAPanel()==vp)
+                               return vh;
+                       }
+               }
+               return null;
+       }
+
+
+       
+       public void mouseEntered(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+               
+       }
+
+       public void drop(DropTargetDropEvent arg0) {
+                       try {
+                               
+                               DropTarget o = (DropTarget)arg0.getSource();
+                               if (o.getComponent() instanceof VARNAPanel)
+                               {
+                                       VARNAHolder h = getHolder(o.getComponent());
+                                       if (h!=null)
+                                       {
+                                               h.setModel((VARNAGUIModel) arg0.getTransferable().getTransferData(VARNAGUIModel.Flavor));
+                                       }
+                               }
+                       } catch (UnsupportedFlavorException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (IOException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } 
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowOpened(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowClosing(WindowEvent e) {
+               saveConfig();
+               System.exit(0);
+       }
+
+       public void windowClosed(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowIconified(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowDeiconified(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowActivated(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void windowDeactivated(WindowEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       private void restoreConfig()
+       {
+               BasicINI config = BasicINI.loadINI(_INIFilename);
+       ArrayList<String> vals = config.getItemList("folders");
+               System.out.print("[C]"+vals);
+       
+           for(String path:vals)
+               {
+               System.out.println("Loading folder "+path);
+                 addFolder(path);
+               }
+           _sideList.validate();
+           _listScroller.validate();
+       }
+
+       private void saveConfig()
+       {
+               BasicINI data = new BasicINI();
+               int i=0;
+               for (String folderPath: _treeModel.getFolders())
+               {
+                 data.addItem("folders", "val"+i, folderPath);
+                 i++;
+               }
+               BasicINI.saveINI(data, _INIFilename);
+       }
+
+
+       public void componentResized(ComponentEvent e) {
+               _sideList.validate();
+       }
+
+
+       public void componentMoved(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void componentShown(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void componentHidden(ComponentEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void actionPerformed(ActionEvent e) {
+               String cmd = e.getActionCommand();
+               System.out.println(cmd);
+               if (cmd.equals(""+Commands.NEW_FOLDER))
+               {
+                       _choice.setDialogTitle("Watch new folder...");
+                       _choice.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+                       _choice.setAcceptAllFileFilterUsed(false);
+                       try {
+                               if (_choice.showOpenDialog(getSelf()) == JFileChooser.APPROVE_OPTION) { 
+                                       addFolder(_choice.getSelectedFile().getCanonicalPath());
+                               }
+                               else {
+                                       System.out.println("No Selection ");
+                               }
+                       } catch (IOException e1) {
+                               e1.printStackTrace();
+                       }
+               }
+               else if (cmd.equals(""+Commands.ADD_PANEL_DOWN))
+               {
+                       addLowerPanel();
+               }
+               else if (cmd.equals(""+Commands.ADD_PANEL_UP))
+               {
+                       addUpperPanel();
+               }
+               else if (cmd.equals(""+Commands.REMOVE_PANEL_DOWN))
+               {
+                       removeLowerPanel();
+               }
+               else if (cmd.equals(""+Commands.REMOVE_PANEL_UP))
+               {
+                       removeUpperPanel();
+               }
+               else
+               {
+                       JOptionPane.showMessageDialog(this, "Command '"+cmd+"' not implemented yet.");
+               }
+       }
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUICellEditor.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUICellEditor.java
new file mode 100644 (file)
index 0000000..4b1d3b5
--- /dev/null
@@ -0,0 +1,53 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.util.EventObject;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellEditor;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+class VARNAGUICellEditor extends DefaultTreeCellEditor {  
+       
+       VARNAGUITreeModel _m;
+       
+       private VARNAGUIRenderer _base;
+
+    public VARNAGUICellEditor(JTree tree, DefaultTreeCellRenderer renderer, VARNAGUITreeModel m) {
+               super(tree, renderer);
+               _base= new VARNAGUIRenderer(tree,m);
+               _m=m;
+       }
+    
+    
+    public Component getTreeCellEditorComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row)  
+    {  
+     
+    JPanel renderer = (JPanel) _base.baseElements(tree,_m,value,sel,expanded,leaf,row,true);
+    return renderer;  
+    }
+
+    public boolean isCellEditable(EventObject evt)
+    {
+        if (evt instanceof MouseEvent) {
+            int clickCount;
+            // For single-click activation
+            clickCount = 1;
+
+            return ((MouseEvent)evt).getClickCount() >= clickCount;
+        }
+        return true;
+
+    }
+
+    
+}  
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIModel.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIModel.java
new file mode 100644 (file)
index 0000000..edfd56f
--- /dev/null
@@ -0,0 +1,142 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VARNAGUIModel implements Comparable<VARNAGUIModel> {
+  private Date _lastModified;
+  private boolean _outOfSync = false;
+  private RNA _r = null;
+  private String _caption = "";
+  private String _path = "";
+  private String _folder = "";
+
+  
+  public static Date lastModif(String path)
+  {
+        return new Date(new File(path).lastModified()) ;
+  }
+  
+  public VARNAGUIModel(String folder, String path)
+  {
+       this(folder,path,lastModif(path));
+  }
+  
+  public VARNAGUIModel(String folder, String path,Date lastModified)
+  {
+         _lastModified = lastModified;
+         _outOfSync = false;
+         _folder =folder;
+         _path = path;
+         String[] s = path.split(Pattern.quote(File.separator));
+         if (s.length>0)
+           _caption = s[s.length-1];
+  }
+  
+  public boolean hasChanged()
+  {
+         return _outOfSync;
+  }
+  
+  public boolean checkForModifications()
+  {
+       if (!lastModif(_path).equals(_lastModified) && !_outOfSync)
+       {
+               _outOfSync = true;
+                 return true;
+       }
+       return false;
+  }
+  
+  
+  public RNA getRNA()
+  {
+         if (_r ==null)
+         {
+                 try {
+                       createRNA();
+               } catch (ExceptionUnmatchedClosingParentheses e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (FileNotFoundException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionExportFailed e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionPermissionDenied e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ExceptionLoadingFailed e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+         }
+         return _r;
+  }
+  
+  private RNA createRNA() throws ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax, FileNotFoundException, ExceptionExportFailed, ExceptionPermissionDenied, ExceptionLoadingFailed
+  {
+         Collection<RNA> r = RNAFactory.loadSecStr(_path);
+         if (r.size()>0)
+         {
+                 _r = r.iterator().next();
+                 _r.drawRNARadiate();
+         }
+         else
+         {
+                 throw new ExceptionFileFormatOrSyntax("No valid RNA defined in this file.");
+         }
+         return _r;
+  }
+  
+  public String toString()
+  {
+         return _caption + (this._outOfSync?"*":"");
+  }
+  
+  public String getID()
+  {
+         return getRNA().getID();
+  }
+  
+  public String getCaption()
+  {
+         return _caption;
+  }
+
+  public String getFolder()
+  {
+         return _folder;
+  }
+
+  public static DataFlavor Flavor = new DataFlavor(VARNAGUIModel.class, "VARNA Object");
+
+
+public int compareTo(VARNAGUIModel o) {
+       return _caption.compareTo(o._caption);
+}
+  
+
+  
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIRenderer.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUIRenderer.java
new file mode 100644 (file)
index 0000000..9ea6a47
--- /dev/null
@@ -0,0 +1,141 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+class VARNAGUIRenderer extends DefaultTreeCellRenderer {  
+          
+       JTree _j;
+       VARNAGUITreeModel _m;
+       
+       private static VARNAGUIRenderer _default = new VARNAGUIRenderer(null,null);
+
+       
+
+       public VARNAGUIRenderer (JTree j, VARNAGUITreeModel m)
+       {
+               _j = j;
+               _m = m;
+       }
+       
+       public JComponent baseElements(JTree tree,VARNAGUITreeModel m,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)
+       {
+               JLabel initValue = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+        JPanel result = new JPanel();
+        result.setLayout(new BorderLayout());
+        initValue.setBorder(null);
+        if (hasFocus)
+        { 
+               //renderer.setBackground(Color.blue);            
+               result.setBorder(BorderFactory.createLineBorder(Color.blue));
+            result.setBackground(UIManager.getColor("Tree.selectionBackground"));
+            initValue.setOpaque(true);
+        }
+        else
+        {
+               result.setBackground(Color.white);
+               result.setBorder(BorderFactory.createLineBorder(initValue.getBackground()));
+            
+        }
+        DefaultMutableTreeNode t = (DefaultMutableTreeNode)value;
+        Object o = t.getUserObject();
+        if (!( o instanceof VARNAGUIModel))    
+        {  
+            if (expanded)  
+            {  
+               initValue.setIcon(_default.getOpenIcon());  
+            }  
+            else  
+            {  
+               initValue.setIcon(_default.getClosedIcon());  
+            }  
+            result.add(initValue,BorderLayout.WEST);
+               JButton del = new JButton("X");
+               del.addActionListener(new FolderCloses((String)o,tree,m));
+            result.add(del,BorderLayout.EAST);
+        }  
+        else  
+        {  
+               VARNAGUIModel mod = (VARNAGUIModel) o;
+               initValue.setIcon(_default.getLeafIcon());  
+            result.add(initValue,BorderLayout.WEST);
+            if (mod.hasChanged())
+            {
+                 JButton refresh = new JButton("Refresh");
+              result.add(refresh,BorderLayout.EAST);
+            }
+        }
+        return result;
+       }
+       
+    public Component getDefaultTreeCellRendererComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)  
+    {
+      return super.getTreeCellRendererComponent(tree,value,sel,expanded,leaf,row,hasFocus);
+    }
+
+       public Component getTreeCellRendererComponent(JTree tree,  
+            Object value, boolean sel, boolean expanded, boolean leaf,  
+            int row, boolean hasFocus)  
+    {
+       
+        return baseElements(tree,_m,value,sel,expanded,leaf,row,hasFocus);  
+    }  
+    public Dimension getPreferredSize(int row) {
+        Dimension size = super.getPreferredSize();
+        size.width = _j.getWidth();
+        System.out.println(size);
+        return size;
+    }
+
+
+//    @Override
+//    public void setBounds(final int x, final int y, final int width, final int height) {
+//    super.setBounds(x, y, Math.min(_j.getWidth()-x, width), height);
+//    }
+    
+    
+    public class FolderCloses implements ActionListener{
+       String _path;
+       JComponent _p;
+       VARNAGUITreeModel _m;
+       
+       public FolderCloses(String path, JComponent p, VARNAGUITreeModel m)
+       {
+               _path = path;
+               _p = p;
+               _m = m;
+       }
+               public void actionPerformed(ActionEvent e) {
+                       if (JOptionPane.showConfirmDialog(_p, "This folder will cease to be watched. Confirm?", "Closing folder", JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION)
+                       {
+                               _m.removeFolder(_path);
+                               System.out.println(_j);
+                               _j.updateUI();
+                       }
+               }
+                   
+    }
+}  
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITree.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITree.java
new file mode 100644 (file)
index 0000000..0fd2468
--- /dev/null
@@ -0,0 +1,74 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragGestureRecognizer;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.MouseDragGestureRecognizer;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.JComponent;
+import javax.swing.JTree;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.AbstractLayoutCache;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+public class VARNAGUITree extends JTree implements MouseListener {
+       private Watcher _w;
+       
+       
+       public VARNAGUITree(VARNAGUITreeModel m)
+       {
+               super(m);
+               _w = new Watcher(m ); 
+               _w.start();
+       }
+       
+       public DefaultMutableTreeNode getSelectedNode()
+       {
+               TreePath t = getSelectionPath();
+               if (t!= null)
+               {
+                       return (DefaultMutableTreeNode) t.getLastPathComponent();
+               }
+               return null;
+       }
+
+       public void mouseClicked(MouseEvent e) {
+               int x = e.getX();
+               int y = e.getY();
+               TreePath tp = this.getPathForLocation(x, y);
+               if (tp!=null)
+               {
+                       DefaultMutableTreeNode n = (DefaultMutableTreeNode) tp.getLastPathComponent();
+                       
+               }
+       }
+
+       public void mousePressed(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseReleased(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseEntered(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void mouseExited(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITreeModel.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/VARNAGUITreeModel.java
new file mode 100644 (file)
index 0000000..e307543
--- /dev/null
@@ -0,0 +1,169 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.PriorityQueue;
+import java.util.TreeSet;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+
+public class VARNAGUITreeModel extends DefaultTreeModel{
+       
+  private TreeSet<String> _folders = new TreeSet<String>();
+  private TreeSet<String> _ids = new TreeSet<String>();
+  private Hashtable<String,TreeSet<VARNAGUIModel>> _criterionToFiles = new Hashtable<String,TreeSet<VARNAGUIModel>>();
+  private Hashtable<String,DefaultMutableTreeNode> _criterionToNodes = new Hashtable<String,DefaultMutableTreeNode>();
+  private ArrayList<DefaultMutableTreeNode> _fileNodes = new ArrayList<DefaultMutableTreeNode>();
+  
+  public enum SORT_MODE{
+         PATH,
+         ID
+  }
+  
+  private SORT_MODE _mode = SORT_MODE.PATH; 
+       
+  public VARNAGUITreeModel()
+  {
+         super(new DefaultMutableTreeNode("Folders"));
+  }
+
+
+  public void removeFolder(String path)
+  {
+         if (_mode==SORT_MODE.PATH)
+         {
+                 int pos = _folders.headSet(path).size();
+                 DefaultMutableTreeNode parent = (DefaultMutableTreeNode) getRoot().getChildAt(pos);
+                 parent.removeAllChildren();
+                 reload(parent);
+                 getRoot().remove(parent);
+                 _criterionToNodes.remove(path);
+                 _criterionToFiles.remove(path);
+         }
+         else if (_mode==SORT_MODE.ID)
+         {
+                 ArrayList<DefaultMutableTreeNode> toBeRemoved = new ArrayList<DefaultMutableTreeNode>(); 
+                 for(DefaultMutableTreeNode leafNode : _fileNodes)
+                 {
+                         VARNAGUIModel m = (VARNAGUIModel)leafNode.getUserObject();
+                         if (m.getFolder().equals(path))
+                         {
+                                 toBeRemoved.add(leafNode);
+                         }
+                 }
+                 for(DefaultMutableTreeNode leafNode : toBeRemoved)
+                 {
+                         _fileNodes.remove(leafNode);
+                         DefaultMutableTreeNode parent = (DefaultMutableTreeNode) leafNode.getParent();
+                         parent.remove(leafNode);
+                 }
+         }
+         _folders.remove(path);
+  }
+  
+  
+  public DefaultMutableTreeNode insertGroupNode(String crit, TreeSet<String> t)
+  {
+         DefaultMutableTreeNode groupNode = new DefaultMutableTreeNode(crit);
+         DefaultMutableTreeNode parent = getRoot();
+         int pos = t.headSet(crit).size();
+         parent.insert(groupNode, pos);         
+         reload(groupNode);
+         return groupNode;
+  }
+
+  public void insertLeafNode(DefaultMutableTreeNode parent, VARNAGUIModel m, TreeSet<VARNAGUIModel> t)
+  {
+         DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode(m);
+         int pos = t.headSet(m).size();
+         parent.insert(leafNode, pos);
+         _fileNodes.add(leafNode);
+  }
+
+  public DefaultMutableTreeNode addFolder(String path)
+  {
+         DefaultMutableTreeNode groupNode = null;
+         try {
+                 if (!_folders.contains(path))
+                 {
+                         System.out.println("Folder: "+path);
+                         File dir = new File(path);
+                         if (dir.isDirectory())
+                         {
+                                 path = dir.getCanonicalPath();
+                                 _folders.add(path);
+                                 if (_mode==SORT_MODE.PATH)
+                                 {
+                                         System.out.println("  Adding: "+path);
+                                         groupNode = insertGroupNode(path, _folders);
+                                         _criterionToNodes.put(path,groupNode);
+                                         _criterionToFiles.put(path, new TreeSet<VARNAGUIModel>());
+                                 }
+                                 for(File f:dir.listFiles(_f))
+                                 {
+                                         addFile(path,f.getCanonicalPath());
+                                 }
+                         }
+                 }
+         } catch (IOException e) {
+                 e.printStackTrace();
+         }
+         return groupNode;
+  }
+    
+  private void addFile(String folder, String path)
+  {
+         System.out.println("  => "+path);
+         VARNAGUIModel m = new VARNAGUIModel(folder,path);
+         if (_mode==SORT_MODE.PATH)
+         {
+                 addFolder(folder);
+                 insertLeafNode(_criterionToNodes.get(folder), m, _criterionToFiles.get(folder));
+         }
+         else if (_mode==SORT_MODE.ID)
+         {
+                 String id = m.getID();
+                 if (!_criterionToNodes.containsKey(id))
+                 {
+                         _criterionToNodes.put(id, insertGroupNode(id, _ids));
+                 }
+                 insertLeafNode(_criterionToNodes.get(id), m, _criterionToFiles.get(id));
+         }
+  }
+  
+  public DefaultMutableTreeNode getRoot()
+  {
+         return (DefaultMutableTreeNode) super.getRoot();
+  }
+  
+  public ArrayList<String> getFolders()
+  {
+         ArrayList<String> result = new ArrayList<String>(_folders);
+         return result;
+  }
+  
+  
+   FilenameFilter _f = new FilenameFilter(){
+               public boolean accept(File dir, String name) {
+                       return name.toLowerCase().endsWith(".dbn") 
+                       || name.toLowerCase().endsWith(".ct")
+                       || name.toLowerCase().endsWith(".bpseq")
+                       || name.toLowerCase().endsWith(".rnaml");       
+               }};
+         
+  public FilenameFilter getFileNameFilter()
+  {
+       return _f;
+  }
+
+  public void setFileNameFilter(FilenameFilter f)
+  {
+       _f = f;
+  }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/newGUI/Watcher.java b/srcjar/fr/orsay/lri/varna/applications/newGUI/Watcher.java
new file mode 100644 (file)
index 0000000..563b5a9
--- /dev/null
@@ -0,0 +1,47 @@
+package fr.orsay.lri.varna.applications.newGUI;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+public class Watcher  extends Thread {
+       
+       private VARNAGUITreeModel _model;
+       private boolean _terminated = false; 
+       
+       public Watcher(VARNAGUITreeModel model)
+       {
+               _model = model;
+       }
+       
+       public void run() {
+               while (!_terminated)
+               {
+                       ArrayList<String> folders = _model.getFolders();
+                       for (String path: folders)
+                       {
+                         _model.addFolder(path);
+                         System.out.println("Watching ["+path+"]");
+                       }
+                       try {
+                               this.sleep(1000);
+                       } catch (InterruptedException e) {
+                       }
+               }
+               
+       }
+       
+
+
+       
+       
+       public void finish()
+       {
+               _terminated = true;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/Connection.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/Connection.java
new file mode 100644 (file)
index 0000000..56200aa
--- /dev/null
@@ -0,0 +1,23 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+
+public class Connection {
+       public GraphicalTemplateElement _h1;
+       public GraphicalTemplateElement.RelativePosition _edge1;
+       public GraphicalTemplateElement _h2;
+       public GraphicalTemplateElement.RelativePosition _edge2;
+       
+       public Connection(GraphicalTemplateElement h1, GraphicalTemplateElement.RelativePosition edge1,GraphicalTemplateElement h2, GraphicalTemplateElement.RelativePosition edge2)
+       {
+               _h1 = h1;
+               _h2 = h2;
+               _edge1 = edge1;
+               _edge2 = edge2;
+       }
+       
+       public boolean equals( Connection c)
+       {
+               return ((_h1==c._h1)&&(_h2==c._h2)&&(_edge1==c._edge1)&&(_edge2==c._edge2));
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/Couple.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/Couple.java
new file mode 100644 (file)
index 0000000..2f60b01
--- /dev/null
@@ -0,0 +1,35 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+
+public class Couple<T,U> {
+       public T first;
+       public U second;
+       private static final int HASH_PRIME = 1000003;
+    
+       
+       public Couple(T a, U b)
+       {
+               first = a;
+               second = b;
+       }
+
+       public boolean equals( Object c)
+       {
+               if (!(c instanceof Couple))
+               {
+                       return false;
+               }
+               Couple<T,U> cc = (Couple<T,U>) c; 
+               return (cc.first.equals(first) && (cc.second.equals(second)));
+       }
+
+       public int hashCode()
+       {
+               return HASH_PRIME*first.hashCode()+second.hashCode();
+       }
+       
+       public String toString()
+       {
+               return "("+first+","+second+")";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/GraphicalTemplateElement.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/GraphicalTemplateElement.java
new file mode 100644 (file)
index 0000000..7e92960
--- /dev/null
@@ -0,0 +1,254 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected;
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+
+public abstract class GraphicalTemplateElement {
+               
+       public boolean _debug = false;
+    protected HashMap<RelativePosition,Color> _mainColors = new HashMap<RelativePosition,Color>();
+    
+    public Color _dominantColor = new Color(0.5f,0.5f,0.5f,0.9f);
+       
+       public enum RelativePosition {
+               RP_OUTER,RP_INNER_GENERAL,RP_INNER_MOVE,RP_EDIT_START,RP_EDIT_END,RP_CONNECT_START5,RP_CONNECT_START3,RP_CONNECT_END5,RP_CONNECT_END3,
+               RP_EDIT_TANGENT_3,RP_EDIT_TANGENT_5;
+       };
+       
+       static final Color  BACKBONE_COLOR = Color.gray;
+       static final Color  CONTROL_COLOR = Color.decode("#D0D0FF");
+       static final Font  NUMBER_FONT = new Font("Arial", Font.BOLD,18);
+       static final Color NUMBER_COLOR = Color.gray;
+       static final Color  BASE_PAIR_COLOR = Color.blue;
+       static final Color  BASE_COLOR = Color.gray;
+       static final Color  BASE_FILL_COLOR = Color.white;
+       static final Color  BASE_FILL_3_COLOR = Color.red;
+       static final Color  BASE_FILL_5_COLOR = Color.green;
+       static final Color  MAGNET_COLOR = CONTROL_COLOR;
+
+       public void setDominantColor(Color c)
+       {
+               _dominantColor = c;
+       }
+
+       public Color getDominantColor()
+       {
+               return _dominantColor;
+       }
+
+       public abstract RelativePosition getRelativePosition(double x, double y);
+       
+       public abstract void draw(Graphics2D g2d, boolean selected);
+       public abstract Polygon getBoundingPolygon();
+       public abstract void translate(double x, double y);
+       public abstract RelativePosition getClosestEdge(double x, double y);
+       public abstract ArrayList<RelativePosition> getConnectedEdges();
+       public abstract RNATemplateElement getTemplateElement();
+       public void setMainColor(RelativePosition edge,Color c)
+       {
+               _mainColors.put(edge, c);
+       }
+
+       public abstract Shape getArea();
+       
+       public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate
+       {
+               _attachedElements.put(edgeOrig, new Couple<RelativePosition,GraphicalTemplateElement>(edgeDest,e));
+       }
+       
+       
+       /**
+        * Same as attach(), but without touching the underlying
+        * RNATemplateElement objects.
+        * This is useful if the underlying RNATemplate already contains edges
+        * but not the graphical template.
+        */
+       public void graphicalAttach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest)
+       {
+               _attachedElements.put(edgeOrig, new Couple<RelativePosition,GraphicalTemplateElement>(edgeDest,e));
+       }
+       
+       public void detach(RelativePosition edge)
+       {
+               if (_attachedElements.containsKey(edge))
+               {
+                       Couple<RelativePosition,GraphicalTemplateElement> c = _attachedElements.get(edge);
+                       _attachedElements.remove(edge);
+                       c.second.detach(c.first);
+               }
+       }
+       public Couple<RelativePosition,GraphicalTemplateElement> getAttachedElement(RelativePosition localedge)
+       {
+               if (_attachedElements.containsKey(localedge))
+                       return _attachedElements.get(localedge);
+               return null;
+       }
+       public boolean hasAttachedElement(RelativePosition localedge)
+       {
+               return _attachedElements.containsKey(localedge);
+       }
+       public abstract EdgeEndPoint getEndPoint(RelativePosition r);
+       public abstract boolean isIn(RelativePosition r);
+       
+       public void draw(Graphics2D g2d)
+       {
+               draw(g2d,false);
+       }
+       
+       private HashMap<RelativePosition,Couple<RelativePosition,GraphicalTemplateElement>> _attachedElements = new HashMap<RelativePosition,Couple<RelativePosition,GraphicalTemplateElement>>();
+
+       
+       private Dimension getStringDimension(Graphics2D g, String s) {
+               FontMetrics fm = g.getFontMetrics();
+               Rectangle2D r = fm.getStringBounds(s, g);
+               return (new Dimension((int) r.getWidth(), (int) fm.getAscent()
+                               - fm.getDescent()));
+       }
+
+       
+
+       public void drawStringCentered(Graphics2D g2, String res, double x,
+                       double y) {
+               Dimension d = getStringDimension(g2, res);
+               x -= (double) d.width / 2.0;
+               y += (double) d.height / 2.0;
+               if (_debug)
+                       g2.drawRect((int) x, (int) y - d.height, d.width, d.height);
+               g2.drawString(res, (int) Math.round(x), (int) Math.round(y));
+       }
+       
+       protected Stroke  _boldStroke = new BasicStroke(2.5f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 3.0f);
+       protected Stroke  _solidStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 3.0f);
+       private float[] dash = { 5.0f, 5.0f };
+       protected Stroke  _dashedStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 3.0f, dash, 0);
+       
+       public abstract RelativePosition getConnectedEdge(RelativePosition edge);
+       public abstract Point2D.Double getEdgePosition(RelativePosition edge);
+       public abstract void setEdgePosition(RelativePosition edge, Point2D.Double pos);
+       public abstract RelativePosition relativePositionFromEdgeEndPointPosition(EdgeEndPointPosition endPointPosition);
+       
+       public static boolean canConnect(GraphicalTemplateElement el1, RelativePosition e1,GraphicalTemplateElement el2, RelativePosition e2)
+       {
+               return (!el1.hasAttachedElement(e1))&&(!el2.hasAttachedElement(e2))&&(el1.isIn(e1)!=el2.isIn(e2));
+       }
+
+       
+       protected void drawMove(Graphics2D g2d, Point2D.Double center)
+       {
+               g2d.setStroke(_solidStroke);
+               g2d.setColor(CONTROL_COLOR);
+               g2d.fillOval((int)((center.x-Helix.MOVE_RADIUS)), (int)((center.y-Helix.MOVE_RADIUS)), (int)(2.0*Helix.MOVE_RADIUS), (int)(2.0*Helix.MOVE_RADIUS));
+               g2d.setColor(BACKBONE_COLOR);
+               g2d.drawOval((int)((center.x-Helix.MOVE_RADIUS)), (int)((center.y-Helix.MOVE_RADIUS)), (int)(2.0*Helix.MOVE_RADIUS), (int)(2.0*Helix.MOVE_RADIUS));
+               double arrowLength = Helix.MOVE_RADIUS-2.0;
+               double width = 3.0;
+               drawArrow(g2d,center,new Point2D.Double(center.x+arrowLength,center.y),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x-arrowLength,center.y),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x,center.y+arrowLength),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x,center.y-arrowLength),width);
+       }
+       
+       protected void drawEditStart(Graphics2D g2d, Helix h, double dx,double dy,double nx,double ny)
+       {
+               Point2D.Double center = h.getCenterEditStart();
+               drawEdit(g2d, center, dx,dy,nx,ny);
+       }
+       protected void drawEditEnd(Graphics2D g2d, Helix h, double dx,double dy,double nx,double ny)
+       {
+               Point2D.Double center = h.getCenterEditEnd();
+               drawEdit(g2d, center, dx,dy,nx,ny);
+       }
+
+       protected void drawEdit(Graphics2D g2d, Point2D.Double center, double dx,double dy,double nx,double ny)
+       {
+               g2d.setColor(CONTROL_COLOR);
+               g2d.fillOval((int)((center.x-Helix.EDIT_RADIUS)), (int)((center.y-Helix.EDIT_RADIUS)), (int)(2.0*Helix.EDIT_RADIUS), (int)(2.0*Helix.EDIT_RADIUS));
+               g2d.setColor(BACKBONE_COLOR);
+               g2d.drawOval((int)((center.x-Helix.EDIT_RADIUS)), (int)((center.y-Helix.EDIT_RADIUS)), (int)(2.0*Helix.EDIT_RADIUS), (int)(2.0*Helix.EDIT_RADIUS));
+               double arrowLength = Helix.EDIT_RADIUS-2.0;
+               double width = 3.0;
+               drawArrow(g2d,center,new Point2D.Double(center.x+nx*arrowLength,center.y+ny*arrowLength),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x-nx*arrowLength,center.y-ny*arrowLength),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x+dx*arrowLength,center.y+dy*arrowLength),width);
+               drawArrow(g2d,center,new Point2D.Double(center.x-dx*arrowLength,center.y-dy*arrowLength),width);
+       }
+       
+       protected void drawArrow(Graphics2D g2d, Point2D.Double orig, Point2D.Double dest, double width)
+       {
+               g2d.setStroke(_solidStroke);
+               g2d.drawLine((int)orig.x,(int)orig.y,(int)dest.x,(int)dest.y);
+               double dx = (orig.x-dest.x)/(orig.distance(dest));
+               double dy = (orig.y-dest.y)/(orig.distance(dest));
+               double nx = dy;
+               double ny = -dx;
+               g2d.drawLine((int)dest.x,(int)dest.y,(int)(dest.x-width*(-dx+nx)),(int)(dest.y-width*(-dy+ny)));
+               g2d.drawLine((int)dest.x,(int)dest.y,(int)(dest.x-width*(-dx-nx)),(int)(dest.y-width*(-dy-ny)));
+       }
+
+       
+       
+       protected void drawAnchor(Graphics2D g2d, Point2D.Double p)
+       { drawAnchor(g2d,p,CONTROL_COLOR); }
+
+       protected void drawAnchor5(Graphics2D g2d, Point2D.Double p)
+       { drawAnchor(g2d,p,BASE_FILL_5_COLOR); }
+
+       protected void drawAnchor3(Graphics2D g2d, Point2D.Double p)
+       { drawAnchor(g2d,p,BASE_FILL_3_COLOR); }
+       
+       protected void drawAnchor(Graphics2D g2d, Point2D.Double p, Color c)
+       {
+               g2d.setColor(c);                                
+               g2d.fillOval((int)(p.x-Helix.EDGE_BASE_RADIUS),(int)(p.y-Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS));
+               g2d.setColor(BASE_COLOR);                               
+               g2d.drawOval((int)(p.x-Helix.EDGE_BASE_RADIUS),(int)(p.y-Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS));
+
+       }
+
+       protected void drawMagnet(Graphics2D g2d, Point2D.Double p)
+       {
+               drawAnchor(g2d, p, MAGNET_COLOR);                               
+               g2d.setColor(BASE_COLOR);                               
+               g2d.drawOval((int)(p.x-Helix.EDGE_BASE_RADIUS),(int)(p.y-Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS),(int)(2.0*Helix.EDGE_BASE_RADIUS));
+               g2d.drawOval((int)(p.x-2),(int)(p.y-2),(int)(2.0*2),(int)(2.0*2));
+
+       }
+       
+       
+       
+       protected void drawBase(Graphics2D g2d, Point2D.Double p)
+       {
+               g2d.setColor(BASE_FILL_COLOR);
+               g2d.fillOval((int)(p.x-Helix.BASE_RADIUS),(int)(p.y-Helix.BASE_RADIUS),(int)(2.0*Helix.BASE_RADIUS),(int)(2.0*Helix.BASE_RADIUS));
+               g2d.setColor(BASE_COLOR);
+               g2d.drawOval((int)(p.x-Helix.BASE_RADIUS),(int)(p.y-Helix.BASE_RADIUS),(int)(2.0*Helix.BASE_RADIUS),(int)(2.0*Helix.BASE_RADIUS));
+       }
+
+       public boolean equals(Object b)
+       {
+         if (b instanceof GraphicalTemplateElement)
+         {
+               return b==this;  
+         }
+         else
+                 return false;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/Helix.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/Helix.java
new file mode 100644 (file)
index 0000000..4c81bed
--- /dev/null
@@ -0,0 +1,687 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+
+
+public class Helix extends GraphicalTemplateElement{
+       
+  RNATemplateHelix _h;
+  
+  public Helix(double x, double y, RNATemplate tmp, List<GraphicalTemplateElement> existingRNAElements)
+  {
+         this(x,y,getNextAutomaticCaption(existingRNAElements),tmp);
+  }
+  
+  public Helix(double x, double y, String cap, RNATemplate tmp)
+  {
+         _h = tmp.new RNATemplateHelix(cap);
+         _h.setStartPosition(new Point2D.Double(x,y));
+         _h.setEndPosition(new Point2D.Double(x,y));
+         _h.setLength(1);
+         _h.setCaption(cap);
+  }
+  
+  public Helix(RNATemplateHelix templateHelix) {
+         _h = templateHelix;
+  }
+  
+  
+  private static String getNextAutomaticCaption(List<GraphicalTemplateElement> existingRNAElements) {
+         // Find which captions are already used
+         Set<String> captions = new HashSet<String>();
+         for (GraphicalTemplateElement element: existingRNAElements) {
+                 if (element instanceof Helix) {
+                         Helix helix = (Helix) element;
+                         if (helix.getCaption() != null) {
+                                 captions.add(helix.getCaption());
+                         }
+                 }
+         }
+         // Find a non-conflicting name for this helix
+         for (int i=1;;i++) {
+                 String candidateCaption = "H" + i;
+                 if (! captions.contains(candidateCaption)) {
+                         return candidateCaption;
+                 }
+         }
+  }
+  
+  public void toggleFlipped()
+  {
+         _h.setFlipped(!_h.isFlipped());
+         updateAttachedUnpairedRegions();
+  }
+  
+
+  /**
+   * When an helix is moved/resized/etc... it is necessary to update
+   * the positions of endpoints from unpaired regions that are attached
+   * to the helix. This function updates the endpoints positions of
+   * attached unpaired regions.
+   */
+  public void updateAttachedUnpairedRegions() {
+         for (RelativePosition rpos: getConnectedEdges()) {
+                 Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(rpos);
+                 if (c != null && c.second instanceof UnpairedRegion) {
+                         UnpairedRegion unpairedRegion = (UnpairedRegion) c.second;
+                         Point2D.Double pos = getEdgePosition(rpos);
+                         if (c.first == RelativePosition.RP_CONNECT_START5) {
+                                 unpairedRegion.setEdge5(pos);
+                         } else if (c.first == RelativePosition.RP_CONNECT_END3) {
+                                 unpairedRegion.setEdge3(pos);
+                         }
+                 }
+         }
+  }
+
+  public double getPosX()
+  {
+         return _h.getStartPosition().x;
+  }
+
+  public String getCaption()
+  {
+         return _h.getCaption();
+  }
+  
+  public double getPosY()
+  {
+         return _h.getStartPosition().y;
+  }
+  
+  public RNATemplateHelix getTemplateElement()
+  {
+         return _h;
+  }
+
+  public void setX(double x)
+  {
+         _h.getStartPosition().x = x;
+  }
+  
+  public void setY(double y)
+  {
+         _h.getStartPosition().y = y;
+  }
+
+  public void setPos(Point2D.Double p)
+  {
+        _h.setStartPosition(p);
+         updateLength();
+  }
+
+  public void setPos(double x, double y)
+  {
+         setPos(new Point2D.Double(x,y));
+  }
+
+  public Point2D.Double getPos()
+  {
+         return _h.getStartPosition();
+  }
+
+  public void moveCenter(double x, double y)
+  {
+         Point2D.Double center = new Point2D.Double((_h.getStartPosition().x+_h.getEndPosition().x)/2.0,(_h.getStartPosition().y+_h.getEndPosition().y)/2.0);
+         double dx = x-center.x;
+         double dy = y-center.y;
+         _h.setStartPosition(new Point2D.Double(_h.getStartPosition().x+dx,_h.getStartPosition().y+dy));
+         _h.setEndPosition(new Point2D.Double(_h.getEndPosition().x+dx,_h.getEndPosition().y+dy));
+  }
+
+  
+  public void setExtent(double x, double y)
+  {
+        setExtent(new Point2D.Double(x,y));
+  }
+
+  private void updateLength()
+  {
+         _h.setLength(getNbBP());
+  }
+  
+  public void setExtent(Point2D.Double p)
+  {
+        _h.setEndPosition(p);
+         updateLength();
+  }
+
+  public double getExtentX()
+  {
+         return _h.getEndPosition().x;
+  }
+
+  public Point2D.Double getExtent()
+  {
+         return _h.getEndPosition();
+  }
+
+  public double getExtentY()
+  {
+         return _h.getEndPosition().y;
+  }
+  
+       public static final double BASE_PAIR_DISTANCE = RNA.BASE_PAIR_DISTANCE;
+       public static final double LOOP_DISTANCE = RNA.LOOP_DISTANCE;
+       public static final double SELECTION_RADIUS = 15.0;
+       
+       
+       
+
+       public Point2D.Double getAbsStart5()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Point2D.Double start5 = new Point2D.Double((getPosX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               return start5;
+       }
+
+       public Point2D.Double getAbsStart3()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Point2D.Double start3 = new Point2D.Double((getPosX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               return start3;
+       }
+
+       public Point2D.Double getAbsEnd5()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Point2D.Double end5 = new Point2D.Double((getExtentX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               return end5;
+       }
+
+       public Point2D.Double getAbsEnd3()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Point2D.Double end3 = new Point2D.Double((getExtentX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()+Helix.BASE_PAIR_DISTANCE*ny/2.0));
+               return end3;
+       }
+       
+       public Point2D.Double getStart5()
+       {
+               if (_h.isFlipped())
+                       return getAbsStart3();
+               else
+                       return getAbsStart5();
+               
+       }
+
+       public Point2D.Double getStart3()
+       {
+               if (_h.isFlipped())
+                       return getAbsStart5();
+               else
+                       return getAbsStart3();
+       }
+
+       public Point2D.Double getEnd5()
+       {
+               if (_h.isFlipped())
+                       return getAbsEnd3();
+               else
+                       return getAbsEnd5();
+       }
+
+       public Point2D.Double getEnd3()
+       {
+               if (_h.isFlipped())
+                       return getAbsEnd5();
+               else
+                       return getAbsEnd3();
+       }
+
+       public Polygon getBoundingPolygon()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Point2D.Double start5 = new Point2D.Double((getPosX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               Point2D.Double end5 = new Point2D.Double((getExtentX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               Point2D.Double start3 = new Point2D.Double((getPosX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); 
+               Point2D.Double end3 = new Point2D.Double((getExtentX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()-Helix.BASE_PAIR_DISTANCE*ny/2.0));
+               Polygon p = new Polygon();
+               p.addPoint((int)start5.x, (int)start5.y);
+               p.addPoint((int)end5.x, (int)end5.y);
+               p.addPoint((int)end3.x, (int)end3.y);
+               p.addPoint((int)start3.x, (int)start3.y);
+               return p;
+       }
+
+       
+       public Point2D.Double getCenter()
+       {
+               return new Point2D.Double((int)((_h.getStartPosition().x+_h.getEndPosition().x)/2.0),
+                               (int)((_h.getStartPosition().y+_h.getEndPosition().y)/2.0));
+       }
+
+
+       public Point2D.Double getCenterEditStart()
+       {
+               double dist = _h.getStartPosition().distance(_h.getEndPosition());
+               double dx = (_h.getEndPosition().x-_h.getStartPosition().x)/(dist);
+               double dy = (_h.getEndPosition().y-_h.getStartPosition().y)/(dist);
+               return new Point2D.Double((int)(_h.getStartPosition().x+(dist-10.0)*dx),
+                               (int)(_h.getStartPosition().y+(dist-10.0)*dy));
+       }       
+
+       public Point2D.Double getCenterEditEnd()
+       {
+               double dist = _h.getStartPosition().distance(_h.getEndPosition());
+               double dx = (_h.getEndPosition().x-_h.getStartPosition().x)/(dist);
+               double dy = (_h.getEndPosition().y-_h.getStartPosition().y)/(dist);
+               return new Point2D.Double((int)(_h.getStartPosition().x+(10.0)*dx),
+                               (int)(_h.getStartPosition().y+(10.0)*dy));
+       }       
+
+       
+       public Shape getSelectionBox()
+       {
+               double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition()));
+               double nx = dy;
+               double ny = -dx;
+               Polygon hbox = getBoundingPolygon();
+               Polygon p = new Polygon();
+               Point2D.Double start5 = new Point2D.Double(hbox.xpoints[0]+SELECTION_RADIUS*(dx+nx),hbox.ypoints[0]+SELECTION_RADIUS*(dy+ny)); 
+               Point2D.Double end5 = new Point2D.Double(hbox.xpoints[1]+SELECTION_RADIUS*(-dx+nx),hbox.ypoints[1]+SELECTION_RADIUS*(-dy+ny)); 
+               Point2D.Double end3  = new Point2D.Double(hbox.xpoints[2]+SELECTION_RADIUS*(-dx-nx),hbox.ypoints[2]+SELECTION_RADIUS*(-dy-ny));;
+               Point2D.Double start3 = new Point2D.Double(hbox.xpoints[3]+SELECTION_RADIUS*(dx-nx),hbox.ypoints[3]+SELECTION_RADIUS*(dy-ny));; 
+               p.addPoint((int)start5.x, (int)start5.y);
+               p.addPoint((int)end5.x, (int)end5.y);
+               p.addPoint((int)end3.x, (int)end3.y);
+               p.addPoint((int)start3.x, (int)start3.y);
+               return p;
+       }
+
+       public Shape getArea()
+       {
+               return getSelectionBox();
+       }
+
+       
+       public static final double EDIT_RADIUS = 10.0;
+       public static final double MOVE_RADIUS = 13.0;
+       public static final double BASE_RADIUS = 8.0;
+       public static final double EDGE_BASE_RADIUS = 7.0;
+       
+       
+       public RelativePosition getRelativePosition(double x, double y)
+       {
+               Point2D.Double current = new Point2D.Double(x,y);
+               Shape p = getSelectionBox();
+               if (p.contains(current))
+               {
+                       if (getCenterEditStart().distance(current)<EDIT_RADIUS)
+                       {
+                               return RelativePosition.RP_EDIT_START;
+                       }
+                       else if (getCenterEditEnd().distance(current)<EDIT_RADIUS)
+                       {
+                               return RelativePosition.RP_EDIT_END;
+                       }
+                       else if (getCenter().distance(current)<MOVE_RADIUS)
+                       {
+                               return RelativePosition.RP_INNER_MOVE;
+                       }
+                       else if (getEnd3().distance(current)<EDGE_BASE_RADIUS)
+                       {
+                               return RelativePosition.RP_CONNECT_END3;
+                       }
+                       else if (getEnd5().distance(current)<EDGE_BASE_RADIUS)
+                       {
+                               return RelativePosition.RP_CONNECT_END5;
+                       }
+                       else if (getStart3().distance(current)<EDGE_BASE_RADIUS)
+                       {
+                               return RelativePosition.RP_CONNECT_START3;
+                       }
+                       else if (getStart5().distance(current)<EDGE_BASE_RADIUS)
+                       {
+                               return RelativePosition.RP_CONNECT_START5;
+                       }
+                               
+                       return RelativePosition.RP_INNER_GENERAL;
+               }
+               else 
+                       return RelativePosition.RP_OUTER;  
+       }
+
+       public RelativePosition getClosestEdge(double x, double y)
+       {
+               RelativePosition result = RelativePosition.RP_OUTER;
+               double dist = Double.MAX_VALUE;
+               Point2D.Double current = new Point2D.Double(x,y);
+               double dcand = getStart5().distance(current);
+           if (dcand<dist)
+           {
+               dist = dcand;
+               result = RelativePosition.RP_CONNECT_START5;
+           }
+               dcand = getStart3().distance(current);
+           if (dcand<dist)
+           {
+               dist = dcand;
+               result = RelativePosition.RP_CONNECT_START3;
+           }
+               dcand = getEnd5().distance(current);
+           if (dcand<dist)
+           {
+               dist = dcand;
+               result = RelativePosition.RP_CONNECT_END5;
+           }
+               dcand = getEnd3().distance(current);
+           if (dcand<dist)
+           {
+               dist = dcand;
+               result = RelativePosition.RP_CONNECT_END3;
+           }
+               return result;
+       }
+       
+       public Point2D.Double getEdgePosition(Helix.RelativePosition edge)
+       {
+               switch (edge)
+               {
+                       case RP_CONNECT_END3:
+                               return getEnd3();
+                       case RP_CONNECT_END5:
+                               return getEnd5();
+                       case RP_CONNECT_START5:
+                               return getStart5();
+                       case RP_CONNECT_START3:
+                               return getStart3();
+                       case RP_EDIT_START:
+                               return getPos();
+                       case RP_EDIT_END:
+                               return  getExtent();
+                       case RP_INNER_MOVE:
+                               return  getCenter();
+               }
+               return getCenter();
+       }
+
+       public RelativePosition getConnectedEdge(RelativePosition edge)
+       {
+               switch (edge)
+               {
+                       case RP_CONNECT_END3:
+                               return RelativePosition.RP_CONNECT_START3;
+                       case RP_CONNECT_END5:
+                               return RelativePosition.RP_CONNECT_START5;
+                       case RP_CONNECT_START5:
+                               return RelativePosition.RP_CONNECT_END5;
+                       case RP_CONNECT_START3:
+                               return RelativePosition.RP_CONNECT_END3;
+               }
+               return RelativePosition.RP_OUTER;
+       }
+       
+       public boolean isAnchored5Start()
+       {
+               return (_h.getIn1().getOtherElement()!=null);
+       }
+
+       public boolean isAnchored5End()
+       {
+               return (_h.getOut1().getOtherElement()!=null);
+       }
+       
+       public boolean isAnchored3Start()
+       {
+               return (_h.getOut2().getOtherElement()!=null);
+       }
+
+       public boolean isAnchored3End()
+       {
+               return (_h.getIn2().getOtherElement()!=null);
+       }
+       
+       public int getNbBP()
+       {
+               Point2D.Double pos = getPos();
+               Point2D.Double extent = getExtent();
+               double helLength =  pos.distance(extent);
+               return Math.max((int)Math.round(helLength/Helix.LOOP_DISTANCE) + 1, 2);
+       }
+       
+       public void draw(Graphics2D g2d,boolean isSelected)
+       {
+               g2d.setStroke(_solidStroke);
+               Point2D.Double pos = getPos();
+               Point2D.Double extent = getExtent();
+               double dx = (pos.x-extent.x)/pos.distance(extent);
+               double dy = (pos.y-extent.y)/pos.distance(extent);
+               double nx = Helix.BASE_PAIR_DISTANCE*dy/2.0;
+               double ny = -Helix.BASE_PAIR_DISTANCE*dx/2.0;
+               Point2D.Double start5 = getStart5(); 
+               Point2D.Double end5 = getEnd5(); 
+               Point2D.Double start3 = getStart3(); 
+               Point2D.Double end3 = getEnd3();
+               
+               for (RelativePosition e:this.getConnectedEdges())
+               {
+                       g2d.setStroke(_solidStroke);
+                       g2d.setColor(BACKBONE_COLOR);
+                       Point2D.Double p1 = this.getEdgePosition(e);
+                       Point2D.Double p2 = this.getEdgePosition(getConnectedEdge(e));
+                       if (_mainColors.containsKey(e))
+                       { 
+                               g2d.setColor(_mainColors.get(e));
+                               g2d.setStroke(this._boldStroke);
+                       }
+                       g2d.drawLine((int)p1.x,(int)p1.y,(int)(p1.x+p2.x)/2,(int)(p1.y+p2.y)/2);
+               }
+
+               g2d.setColor(NUMBER_COLOR);
+               double captionx = (_h.isFlipped()?-1.0:1.0)*1.5*nx+(start3.x+end3.x)/2.0;
+               double captiony = (_h.isFlipped()?-1.0:1.0)*1.5*ny+(start3.y+end3.y)/2.0;
+               drawStringCentered(g2d, getCaption(),captionx,captiony);
+
+               int nbBasePairs = _h.getLength();
+
+               g2d.setStroke(_solidStroke);
+
+               for (int i=0;i<nbBasePairs;i++)
+               {
+                       g2d.setColor(BASE_PAIR_COLOR);
+                       Point2D.Double p5 = new Point2D.Double(
+                                       (i*start5.x+(nbBasePairs-1-i)*end5.x)/(nbBasePairs-1),
+                                       (i*start5.y+(nbBasePairs-1-i)*end5.y)/(nbBasePairs-1));
+                       Point2D.Double p3 = new Point2D.Double(
+                                       (i*start3.x+(nbBasePairs-1-i)*end3.x)/(nbBasePairs-1),
+                                       (i*start3.y+(nbBasePairs-1-i)*end3.y)/(nbBasePairs-1));
+                       g2d.drawLine((int)p3.x,(int)p3.y,(int)p5.x,(int)p5.y);
+                       if (i==0)
+                       {
+                               if (isAnchored5End())
+                               { drawMagnet(g2d, p5); }
+                               else
+                               { drawAnchor3(g2d, p5); }
+                               
+                               if (isAnchored3End())
+                               { drawMagnet(g2d, p3); }
+                               else
+                               { drawAnchor5(g2d, p3); }
+                       }
+                       else if (i==nbBasePairs-1)
+                       {
+                               if (isAnchored5Start())
+                               { drawMagnet(g2d, p5); }
+                               else
+                               { drawAnchor5(g2d, p5); }
+                               
+                               if (isAnchored3Start())
+                               { drawMagnet(g2d, p3); }
+                               else
+                               { drawAnchor3(g2d, p3); }
+                       }
+                       else
+                       {
+                               drawBase(g2d, p3);
+                               drawBase(g2d, p5);
+                       }
+               }
+
+               if (isSelected)
+               {
+                       nx = dy;
+                       ny = -dx;
+                       Shape p = getSelectionBox();
+                       g2d.setColor(BACKBONE_COLOR);
+                       g2d.setStroke(_dashedStroke);
+                       g2d.draw(p);
+                       Point2D.Double center = getCenter();
+                       g2d.setStroke(_solidStroke);
+                       drawMove(g2d,center);
+                       drawEditStart(g2d,this,-dx,-dy,nx,ny);                  
+                       drawEditEnd(g2d,this,-dx,-dy,nx,ny);                    
+               }
+       }
+
+
+       public void translate(double x, double y) {
+                 Point2D.Double pos = getPos();
+                 Point2D.Double extent = getExtent();
+                 setPos(pos.x+x,pos.y+y);
+                 setExtent(extent.x+x,extent.y+y);
+       }
+
+       public RNATemplateHelix getHelix() {
+               return _h;
+       }
+       
+
+       public EdgeEndPoint getEndPoint(RelativePosition r) {
+               switch(r)
+               {
+                       case RP_CONNECT_START5:
+                       return _h.getIn1();                             
+                       case RP_CONNECT_START3:
+                       return _h.getOut2();                            
+                       case RP_CONNECT_END3:
+                               return _h.getIn2();
+                       case RP_CONNECT_END5:
+                               return _h.getOut1();
+               }
+               return null;
+       }
+
+       public boolean isIn(RelativePosition r) {
+               switch(r)
+               {
+               case RP_CONNECT_START5:
+                       return true;                            
+               case RP_CONNECT_START3:
+                       return false;                           
+               case RP_CONNECT_END3:
+                       return true;
+               case RP_CONNECT_END5:
+                       return false;
+               }
+               return true;
+       }
+       
+       
+       public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionInvalidRNATemplate
+       {
+               super.attach(e,edgeOrig,edgeDest);
+               EdgeEndPoint e1 = this.getEndPoint(edgeOrig);
+               EdgeEndPoint e2 = e.getEndPoint(edgeDest);
+               boolean parity1 = this.isIn(edgeOrig);
+               boolean parity2 = e.isIn(edgeDest);
+               if ((e1!=null)&&(e2!=null)&&(parity1!=parity2))
+               {
+                  e1.disconnect();
+                  e2.disconnect();
+                  e1.connectTo(e2);
+               }
+       }
+       
+
+
+       public void detach(RelativePosition edge)
+       {
+               // If the underlying template element is still connected, disconnect it
+               if (getEndPoint(edge).isConnected())
+               {
+                       getEndPoint(edge).disconnect();
+               }
+               
+               // Call the parent class detach function, which will also take care to disconnect this other endpoint of this edge
+               super.detach(edge);
+       }
+
+       public void setEdgePosition(RelativePosition edge, java.awt.geom.Point2D.Double pos) {
+               switch (edge)
+               {
+                       case RP_EDIT_START:
+                               setPos(pos);
+                               break;
+                       case RP_EDIT_END:
+                               setExtent(pos);
+                               break;
+                       case RP_INNER_MOVE:
+                               moveCenter(pos.x,pos.y);
+                               break;
+               }
+               updateAttachedUnpairedRegions();
+       }
+
+       public ArrayList<RelativePosition> getConnectedEdges() {
+               ArrayList<RelativePosition> result = new ArrayList<RelativePosition>();
+               result.add(RelativePosition.RP_CONNECT_START5);
+               result.add(RelativePosition.RP_CONNECT_START3);
+               result.add(RelativePosition.RP_CONNECT_END5);
+               result.add(RelativePosition.RP_CONNECT_END3);
+               return result;
+       }
+       
+       public String toString()
+       {
+               return "Helix " + getCaption();
+       }
+
+       public RelativePosition relativePositionFromEdgeEndPointPosition(
+                       EdgeEndPointPosition pos) {
+               switch (pos) {
+               case IN1:
+                       return RelativePosition.RP_CONNECT_START5;
+               case OUT1:
+                       return RelativePosition.RP_CONNECT_END5;
+               case IN2:
+                       return RelativePosition.RP_CONNECT_END3;
+               case OUT2:
+                       return RelativePosition.RP_CONNECT_START3;
+               default:
+                       return null;
+               }
+       }
+
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/MouseControler.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/MouseControler.java
new file mode 100644 (file)
index 0000000..5eb3e5d
--- /dev/null
@@ -0,0 +1,345 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.Point2D;
+
+
+
+public class MouseControler implements MouseListener, MouseMotionListener, MouseWheelListener {
+
+       private int _granularity = 8;
+       private final int HYSTERESIS_DISTANCE = 10;
+       TemplatePanel _sp;
+       GraphicalTemplateElement _elem;
+       TemplateEditorPanelUI _ui;
+       private GraphicalTemplateElement.RelativePosition _currentMode = Helix.RelativePosition.RP_OUTER;
+       private boolean movingView = false;
+       private Point2D.Double _clickedPos = new Point2D.Double();
+       private Point _clickedPosScreen = new Point();
+
+       public MouseControler(TemplatePanel sp, TemplateEditorPanelUI ui)
+       {
+               _sp = sp;
+               _elem = null;
+               _ui = ui;
+       }
+
+       public void mouseWheelMoved(MouseWheelEvent e) {
+               if (e.getWheelRotation() == -1) {
+                       _sp.zoomIn();
+               }
+               else {
+                       _sp.zoomOut();
+               }
+               e.consume();
+       }
+
+
+       public void mouseClicked(MouseEvent arg0) {
+       }
+
+       public void mouseEntered(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+
+       }
+
+       public void mouseExited(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+
+       }
+
+
+       
+       /**
+        * Get mouse position in logical coordinates, ie. those used in the template XML file.
+        * It differs from the screen coordinates relative to panel because of the scaling factor.
+        */
+       public Point2D.Double getLogicalMouseCoords(MouseEvent event) {
+               return new Point2D.Double(event.getX()/_sp.getScaleFactor(), event.getY()/_sp.getScaleFactor());
+       }
+
+       public void mousePressed(MouseEvent arg0) {
+               movingView = false;
+               _clickedPos = new Point2D.Double(arg0.getX(), arg0.getY());
+               _clickedPosScreen.x = arg0.getXOnScreen();
+               _clickedPosScreen.y = arg0.getYOnScreen();
+               
+               // middle-click
+               if (arg0.getButton() == MouseEvent.BUTTON2) {
+                       movingView = true;
+                       
+               } else {
+                       
+                       Point2D.Double logicalMousePos = getLogicalMouseCoords(arg0);
+                       GraphicalTemplateElement elem = _sp.getElementAt(logicalMousePos.getX(), logicalMousePos.getY());
+                       _sp.Unselect();
+                       
+                       if (elem==null)
+                       {
+                               if (arg0.getButton()==MouseEvent.BUTTON1
+                                               && _ui.getSelectedTool() == TemplateEditorPanelUI.Tool.CREATE_HELIX)
+                               {
+                                       _currentMode = Helix.RelativePosition.RP_EDIT_START;
+                               }
+                               else if ((arg0.getButton()==MouseEvent.BUTTON1
+                                               && _ui.getSelectedTool() == TemplateEditorPanelUI.Tool.CREATE_UNPAIRED))
+                               {
+                                       // Create a new unpaired region
+                                       UnpairedRegion n = new UnpairedRegion(logicalMousePos.getX(),logicalMousePos.getY(),_sp.getTemplate());
+                                       n.setDominantColor(_sp.nextBackgroundColor());
+                                       _ui.addElementUI(n);
+                                       _sp.setSelected(n);
+                                       _sp.repaint();
+                                       _elem = n;
+                                       _currentMode = GraphicalTemplateElement.RelativePosition.RP_EDIT_START;
+                               }
+                       }
+                       else if (arg0.getButton()==MouseEvent.BUTTON1) 
+                       {
+                               _currentMode = elem.getRelativePosition(logicalMousePos.getX(), logicalMousePos.getY());
+                               _sp.setSelected(elem);
+                               _elem = elem;
+                               switch (_currentMode)
+                               {
+                               case RP_EDIT_START:
+                               case RP_EDIT_END:
+                               case RP_EDIT_TANGENT_5:
+                               case RP_EDIT_TANGENT_3:
+                                       break;
+                               case RP_INNER_MOVE:
+                                       break;
+                               case RP_INNER_GENERAL:
+                                       _currentMode = Helix.RelativePosition.RP_INNER_MOVE; 
+                                       break;
+                               case RP_CONNECT_END3:
+                               case RP_CONNECT_END5:
+                               case RP_CONNECT_START5:
+                               case RP_CONNECT_START3:
+                               {
+                                       Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> al = _sp.getPartner(elem, _currentMode);
+                                       boolean isConnected = (al!=null); 
+                                       if (isConnected)
+                                       {
+                                               Connection c = _sp.getConnection(elem, _currentMode);
+                                               _ui.removeConnectionUI(c);
+                                               GraphicalTemplateElement p1 = c._h1;
+                                               GraphicalTemplateElement p2 = c._h2;
+                                               boolean p1IsHelix = (p1 instanceof Helix);
+                                               boolean p1IsUnpaired = (p1 instanceof UnpairedRegion);
+                                               boolean p2IsHelix = (p2 instanceof Helix);
+                                               boolean p2IsUnpaired = (p2 instanceof UnpairedRegion);
+                                               boolean p1StillAttached = (p1 == elem);
+       
+                                               if ((p1IsUnpaired && p2IsHelix))
+                                               {
+                                                       p1StillAttached = false;
+                                               }
+                                               if (p1StillAttached)
+                                               { 
+                                                       _elem = p2;
+                                                       _currentMode = c._edge2;
+                                               }
+                                               else if (!p1StillAttached)
+                                               { 
+                                                       _elem=p1;
+                                                       _currentMode = c._edge1;
+                                               }
+       
+                                       }
+                                       if (_elem instanceof Helix)
+                                       { 
+                                               _sp.setPointerPos(new Point2D.Double(logicalMousePos.getX(),logicalMousePos.getY()));
+                                               _sp.setSelectedEdge(_currentMode);
+                                       }
+                                       _sp.setSelected(_elem);
+                               }
+                               break;
+                               case RP_OUTER:
+                                       _sp.Unselect();
+                                       _elem = null;
+                               }
+                               _sp.repaint();
+                       }
+               }
+       }
+
+       public void mouseReleased(MouseEvent arg0) {
+               movingView = false;
+               Point2D.Double logicalMousePos = getLogicalMouseCoords(arg0);
+               if (_elem!=null)
+               {
+                       switch (_currentMode)
+                       {
+                       case RP_EDIT_START:
+                       case RP_EDIT_END:
+                       {
+                               if (_elem instanceof Helix)
+                               {
+                                       Helix h = (Helix) _elem;
+                                       if (h.getPos().distance(h.getExtent())<10.0)
+                                       {
+                                               _ui.removeElementUI(_elem);
+                                               _sp.Unselect();
+                                       }
+                               }
+                       }
+                       break;
+                       case RP_INNER_MOVE:
+                               break;
+                       case RP_CONNECT_END3:
+                       case RP_CONNECT_END5:
+                       case RP_CONNECT_START5:
+                       case RP_CONNECT_START3:
+                       {
+                               GraphicalTemplateElement t = _sp.getElementAt(logicalMousePos.getX(), logicalMousePos.getY(),_elem);
+                               if (t!=null)
+                               {
+                                       GraphicalTemplateElement.RelativePosition edge = t.getClosestEdge(logicalMousePos.getX(), logicalMousePos.getY());
+                                       _ui.addConnectionUI(_elem,_currentMode,t,edge);
+                               }
+                               _sp.setSelectedEdge(Helix.RelativePosition.RP_OUTER);
+                       }
+                       break;
+                       }
+                       _elem = null;           
+                       _sp.rescale();
+               }
+               _sp.setSelectedEdge(Helix.RelativePosition.RP_OUTER);
+               _currentMode = Helix.RelativePosition.RP_OUTER;
+               _sp.repaint();
+       }
+
+
+       private Point2D.Double projectPoint(double x, double y, Point2D.Double ref)
+       {
+               Point2D.Double result = new Point2D.Double();
+               double nx = x-ref.x;
+               double ny = y-ref.y;
+               double tmp = Double.MIN_VALUE;
+               for (int i=0;i<this._granularity;i++)
+               {
+                       double angle = 2.0*Math.PI*((double)i/(double)_granularity);
+                       double dx = Math.cos(angle);
+                       double dy = Math.sin(angle);
+                       double norm  = nx*dx+ny*dy;
+                       //System.out.println(""+angle+" "+norm);
+                       if (norm > tmp)
+                       {
+                               tmp = norm;
+                               result.x = ref.x+dx*norm;
+                               result.y = ref.y+dy*norm;
+                       }
+               }
+               return result;
+       }
+
+       public void mouseDragged(MouseEvent arg0) 
+       {
+               if (movingView) {
+                       Point trans = new Point(
+                                       arg0.getXOnScreen() - _clickedPosScreen.x,
+                                       arg0.getYOnScreen() - _clickedPosScreen.y);
+                       _sp.translateView(trans);
+                       _clickedPosScreen.x = arg0.getXOnScreen();
+                       _clickedPosScreen.y = arg0.getYOnScreen();
+               } else {
+                       Point2D.Double logicalMousePos = getLogicalMouseCoords(arg0);
+                       if (_elem == null)
+                       {
+                               switch (_currentMode)
+                               {
+                               case RP_EDIT_START:
+                               {
+                                       if (_clickedPos.distance(arg0.getX(),arg0.getY())>HYSTERESIS_DISTANCE)
+                                       {
+                                               System.out.println("Creating Helix...");
+                                               Helix h1 = new Helix(logicalMousePos.getX(),logicalMousePos.getY(),_sp.getTemplate(),_sp.getRNAComponents());
+                                               h1.setDominantColor(_sp.nextBackgroundColor());
+                                               _ui.addElementUI(h1);
+                                               _sp.setSelected(h1);
+                                               _sp.repaint();
+                                               _elem = h1;
+                                       }
+                               }
+                               break;
+       
+                               }
+                       }
+                       else
+                       {
+                               if (_elem instanceof Helix)
+                               {
+                                       Helix h = (Helix) _elem;
+                                       switch (_currentMode)
+                                       {
+                                       case RP_EDIT_START:
+                                       {
+                                               Point2D.Double d = projectPoint(logicalMousePos.getX(),logicalMousePos.getY(),h.getPos());
+                                               _ui.setHelixExtentUI(h, d.x,d.y);
+                                       }
+                                       break;
+                                       case RP_EDIT_END:
+                                       {
+                                               Point2D.Double d = projectPoint( logicalMousePos.getX(),logicalMousePos.getY(),h.getExtent());
+                                               _ui.setHelixPosUI(h, d.x,d.y);
+                                       }
+                                       break;
+                                       case RP_INNER_MOVE:
+                                               _ui.moveHelixUI(h, logicalMousePos.getX(),logicalMousePos.getY());
+                                               break;
+                                       case RP_CONNECT_END3:
+                                       case RP_CONNECT_END5:
+                                       case RP_CONNECT_START5:
+                                       case RP_CONNECT_START3:
+                                               _sp.setPointerPos(new Point2D.Double(logicalMousePos.getX(),logicalMousePos.getY()));
+                                               _sp.repaint();                          
+                                               break;
+                                       }
+                               }
+                               else if (_elem instanceof UnpairedRegion)
+                               {
+                                       UnpairedRegion ur = (UnpairedRegion) _elem;
+                                       Point2D.Double p = new Point2D.Double(logicalMousePos.getX(),logicalMousePos.getY());
+                                       switch (_currentMode)
+                                       {
+                                       case RP_EDIT_TANGENT_5:
+                                       {
+                                               _ui.setEdge5TangentUI(ur,logicalMousePos.getX(),logicalMousePos.getY());
+                                               _sp.repaint();
+                                               break;
+                                       }
+                                       case RP_EDIT_TANGENT_3:
+                                       {
+                                               _ui.setEdge3TangentUI(ur,logicalMousePos.getX(),logicalMousePos.getY());
+                                               _sp.repaint();
+                                               break;
+                                       }
+                                       case RP_INNER_MOVE:
+                                       {
+                                               _ui.moveUnpairedUI(ur,logicalMousePos.getX(),logicalMousePos.getY());
+                                               _sp.repaint();
+                                               break;
+                                       }
+                                       case RP_CONNECT_START5:
+                                               _ui.setEdge5UI(ur,logicalMousePos.getX(),logicalMousePos.getY());
+                                               break;
+                                       case RP_CONNECT_END3:
+                                               _ui.setEdge3UI(ur,logicalMousePos.getX(),logicalMousePos.getY());
+                                               break;
+                                       }
+                                       _sp.repaint();                          
+                               }
+                       }
+               }
+       }
+
+       public void mouseMoved(MouseEvent arg0) {
+               // TODO Auto-generated method stub
+
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditor.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditor.java
new file mode 100644 (file)
index 0000000..fe60aae
--- /dev/null
@@ -0,0 +1,577 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.ComponentOrientation;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.undo.UndoManager;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.FileNameExtensionFilter;
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionXMLGeneration;
+import fr.orsay.lri.varna.models.templates.Benchmark;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateCurveMethod;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateMethod;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplateDrawingAlgorithmException;
+import fr.orsay.lri.varna.models.templates.RNATemplateMapping;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+
+
+
+
+public class TemplateEditor extends JFrame implements KeyListener, ActionListener,DropTargetListener {
+
+       private TemplatePanel _sk;
+       private VARNAPanel _vp;
+       private File currentFilePath = null;
+       private JButton saveButton;
+       private JScrollPane jp;
+       private UndoManager manager;
+       private JButton flipButton;
+       private JComboBox ellipseMethodList;
+       private JComboBox applyMethodList;
+       /*private JRadioButton ellipseButtons[];
+       private JRadioButton methodButtons[];
+       */
+
+
+       public TemplateEditor()
+       {
+               init();
+               clearCurrentFilePath();
+       }       
+
+       public JScrollPane getJp() {
+               return jp;
+       }
+       
+       private void init()
+       {
+               try {
+                       _vp = new VARNAPanel(" ",".");
+               } catch (ExceptionNonEqualLength e) {
+                       e.printStackTrace();
+               }
+               _vp.setNumPeriod(0);
+               JPanel p = new JPanel();
+               p.setLayout(new GridLayout(1,2));
+               
+               JToolBar systemBar = new JToolBar();
+               JToolBar optionsBar = new JToolBar();
+               JButton newButton = new JButton("New",UIManager.getIcon("FileView.fileIcon")); 
+               newButton.setActionCommand("new");
+               newButton.addActionListener(this);
+               newButton.addKeyListener(this);
+               JButton loadButton = new JButton("Open...",UIManager.getIcon("FileView.directoryIcon")); 
+               loadButton.setActionCommand("open");
+               loadButton.addActionListener(this);
+               loadButton.addKeyListener(this);
+               saveButton = new JButton("Save",UIManager.getIcon("FileView.floppyDriveIcon")); 
+               saveButton.setActionCommand("save");
+               saveButton.addActionListener(this);
+               saveButton.addKeyListener(this);
+               saveButton.setEnabled(false);
+               JButton saveAsButton = new JButton("Save As...",UIManager.getIcon("FileView.floppyDriveIcon")); 
+               saveAsButton.setActionCommand("save as");
+               saveAsButton.addActionListener(this);
+               saveAsButton.addKeyListener(this);
+               JButton undoButton = new JButton("Undo"); 
+               undoButton.setActionCommand("undo");
+               undoButton.addActionListener(this);
+               undoButton.addKeyListener(this);
+               JButton redoButton = new JButton("Redo"); 
+               redoButton.setActionCommand("redo");
+               redoButton.addActionListener(this);
+               redoButton.addKeyListener(this);
+               
+               JButton benchmarkButton = new JButton("Benchmark"); 
+               benchmarkButton.setActionCommand("benchmark");
+               benchmarkButton.addActionListener(this);
+               benchmarkButton.addKeyListener(this);
+               
+               DrawRNATemplateMethod applyMethods[] = DrawRNATemplateMethod.values();
+               applyMethodList = new JComboBox(applyMethods); 
+               applyMethodList.setSelectedItem(DrawRNATemplateMethod.getDefault());
+               
+               DrawRNATemplateCurveMethod ellipseMethods[] = DrawRNATemplateCurveMethod.values();
+               ellipseMethodList = new JComboBox(ellipseMethods); 
+               ellipseMethodList.setSelectedItem(DrawRNATemplateCurveMethod.getDefault());
+
+               JButton applyButton = new JButton("Apply"); 
+               applyButton.setActionCommand("apply");
+               applyButton.addActionListener(this);
+               applyButton.addKeyListener(this);
+
+               JButton retrieveButton = new JButton("Retrieve Templates"); 
+               retrieveButton.setActionCommand("retrieve");
+               retrieveButton.addActionListener(this);
+               
+               
+               flipButton = new JButton("Flip helix"); 
+               flipButton.setActionCommand("flip");
+               flipButton.addActionListener(this);
+               flipButton.addKeyListener(this);
+               flipButton.setEnabled(false);
+
+               
+               systemBar.add(newButton);
+               systemBar.add(loadButton);
+               systemBar.add(saveButton);
+               systemBar.add(saveAsButton);
+               systemBar.addSeparator();
+               
+               systemBar.addSeparator();
+               systemBar.addSeparator();
+               systemBar.add(benchmarkButton);
+               systemBar.addKeyListener(this);
+               
+               optionsBar.setLayout(new FlowLayout(FlowLayout.LEFT));
+               optionsBar.add(new JLabel("Single-Stranded "));
+               optionsBar.add(this.ellipseMethodList);
+               optionsBar.addSeparator();
+               optionsBar.add(new JLabel("Layout "));
+               optionsBar.add(this.applyMethodList);
+               optionsBar.addSeparator();
+               optionsBar.add(applyButton);
+               optionsBar.addSeparator();
+               optionsBar.add(retrieveButton);
+               optionsBar.doLayout();
+               
+               /*optionsBar.add(new JLabel("Curves:"));
+               for (int i=0; i<ellipseButtons.length; i++)
+                       optionsBar.add(ellipseButtons[i]);
+               optionsBar.addSeparator();
+               optionsBar.add(new JLabel("Helix positions:"));
+               for (int i=0; i<methodButtons.length; i++)
+                       optionsBar.add(methodButtons[i]);
+               optionsBar.addKeyListener(this);
+               */
+
+               JToolBar toolBar = new JToolBar();
+               ButtonGroup bg = new ButtonGroup(); 
+               toolBar.setOrientation(JToolBar.VERTICAL);
+               //toolBar.setLayout(new BoxLayout(toolBar, BoxLayout.PAGE_AXIS));
+               JToggleButton selectButton = new JToggleButton("Select"); 
+               selectButton.setActionCommand("select");
+               selectButton.addActionListener(this);
+               selectButton.addKeyListener(this);
+               JToggleButton helixButton = new JToggleButton("Helix"); 
+               helixButton.setActionCommand("helix");
+               helixButton.addActionListener(this);
+               helixButton.addKeyListener(this);
+               helixButton.setSelected(true);
+               JToggleButton unpairedButton = new JToggleButton("Unpaired"); 
+               unpairedButton.setActionCommand("unpaired");
+               unpairedButton.addActionListener(this);
+               unpairedButton.addKeyListener(this);
+
+               bg.add(selectButton);
+               bg.add(helixButton);
+               bg.add(unpairedButton);
+               
+               toolBar.add(undoButton);
+               toolBar.add(redoButton);
+               toolBar.addSeparator();
+               toolBar.add(new JLabel("Tools:"));
+               toolBar.add(selectButton);
+               toolBar.add(helixButton);
+               toolBar.add(unpairedButton);
+               toolBar.addSeparator();
+               toolBar.add(flipButton);
+               systemBar.addKeyListener(this);
+
+               
+               this.setLayout(new BorderLayout());
+               _sk = new TemplatePanel(this);
+               _sk.setPreferredSize(new Dimension(800,600));
+               manager = new UndoManager();
+               manager.setLimit(2000);
+          _sk.addUndoableEditListener(manager);
+          _sk.addKeyListener(this);
+               
+               jp = new JScrollPane(_sk,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+               p.add(jp);
+               p.add(_vp);
+               JPanel bars = new JPanel();
+               BoxLayout barsLayout = new BoxLayout(bars, BoxLayout.Y_AXIS);
+               bars.setLayout(barsLayout);
+               systemBar.setAlignmentX(0);
+               bars.add(systemBar);
+               optionsBar.setAlignmentX(0);
+               bars.add(optionsBar);
+               getContentPane().add(bars,BorderLayout.PAGE_START);
+               getContentPane().add(toolBar,BorderLayout.WEST);
+               getContentPane().add(p,BorderLayout.CENTER);
+               this.addKeyListener(this);
+               
+           new DropTarget(_vp, this);
+           new DropTarget(_sk, this);
+           this.pack();
+
+               _sk.requestFocusInWindow();
+       }
+       
+       
+       private File getCurrentFilePath() {
+               return currentFilePath;
+       }
+
+       private void setCurrentFilePath(File currentFilePath) {
+               this.currentFilePath = currentFilePath;
+               saveButton.setEnabled(true);
+               setTitle("VARNA Template Editor: " + currentFilePath);
+       }
+       
+       private void clearCurrentFilePath() {
+               currentFilePath = null;
+               saveButton.setEnabled(false);
+               setTitle("VARNA Template Editor: New file");
+       }
+       
+       
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -5942729520783690050L;
+
+       public static void main(String[] argv)
+       {
+               try {
+                       LookAndFeelInfo[] lfs = UIManager.getInstalledLookAndFeels();
+                       int i = 1;
+                   LookAndFeelInfo info = lfs[i % lfs.length];
+            UIManager.setLookAndFeel(info.getClassName());
+               } catch (Exception e) {
+                   // If Nimbus is not available, you can set the GUI to another look and feel.
+               }
+               TemplateEditor frame = new TemplateEditor();
+               frame.pack();
+               frame.setVisible(true);
+               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+       }
+
+       public void keyPressed(KeyEvent e) {
+               System.out.println(e);
+               switch (e.getKeyCode())
+               {
+                 case (KeyEvent.VK_DELETE):
+                 {
+                         GraphicalTemplateElement h = _sk.getSelected();
+                         _sk.Unselect();
+                         _sk.getTemplateUI().removeElementUI(h);
+                         _sk.repaint();
+                 }
+                 break;
+                 case (KeyEvent.VK_Z):
+                 {
+                         if (e.isControlDown())
+                         {
+                                 undo();
+                         }
+                 }
+                 break;
+                 case (KeyEvent.VK_Y):
+                 {
+                         if (e.isControlDown())
+                         {
+                                 redo();
+                         }
+                 }
+                 break;
+               }
+       }
+       
+       public void undo()
+       {
+               if (manager.canUndo())
+               {
+                       //System.out.println("Undo: "+manager.getUndoPresentationName());
+                       manager.undo();
+               }
+       }
+
+       public void redo()
+       {
+                 if (manager.canRedo())
+                 {
+                         //System.out.println("Redo: "+manager.getRedoPresentationName());
+                     manager.redo();
+                 }
+       }
+       
+       public void clearTemplate() {
+               _sk.clearTemplate();
+               clearCurrentFilePath();
+               
+               // Empty the cancel history
+               manager.discardAllEdits();
+       }
+       
+       public void loadTemplate(File templatePath) {
+               _sk.loadFromXmlFile(templatePath);
+               setCurrentFilePath(templatePath);
+               
+               // Empty the cancel history
+               manager.discardAllEdits();
+       }
+       
+       public void keyReleased(KeyEvent e) {
+               System.out.println(e);
+       }
+
+       public void keyTyped(KeyEvent e) {
+               System.out.println(e);
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               if (e.getActionCommand().equals("undo"))
+               {
+                       undo();
+               }
+               else if (e.getActionCommand().equals("redo"))
+               {
+                       redo();
+               }
+               else if (e.getActionCommand().equals("flip"))
+               {
+                       GraphicalTemplateElement gr = _sk.getSelected();
+                       if (gr != null)
+                       {
+                               if (gr instanceof Helix)
+                               {
+                                       _sk.getTemplateUI().flipHelixUI((Helix)gr);
+                               }
+                       }
+               }
+               else if (e.getActionCommand().equals("select")) {
+                       _sk.getTemplateUI().setSelectedTool(TemplateEditorPanelUI.Tool.SELECT);
+               }
+               else if (e.getActionCommand().equals("helix")) {
+                       _sk.getTemplateUI().setSelectedTool(TemplateEditorPanelUI.Tool.CREATE_HELIX);
+               }
+               else if (e.getActionCommand().equals("retrieve")) {
+                       retrieveTemplates();
+               }               
+               else if (e.getActionCommand().equals("unpaired")) {
+                       _sk.getTemplateUI().setSelectedTool(TemplateEditorPanelUI.Tool.CREATE_UNPAIRED);
+               }
+               else if (e.getActionCommand().equals("apply"))
+               {
+                       try {
+                               DrawRNATemplateMethod method = (DrawRNATemplateMethod)applyMethodList.getSelectedItem();
+                               DrawRNATemplateCurveMethod curveMethod = (DrawRNATemplateCurveMethod)ellipseMethodList.getSelectedItem();
+                               RNATemplateMapping map =  _vp.getRNA().drawRNATemplate(_sk.getTemplate(),_vp.getConfig(), method, curveMethod, getStraightBulges());
+                               for(int i: map.getSourceElemsAsSet())
+                               {
+                                       RNATemplateElement t = map.getPartner(i);
+                                       Color c = _sk.getElement(t).getDominantColor();
+                                       _vp.getRNA().getBaseAt(i).getStyleBase().setBaseInnerColor(c);
+                               }
+                               _vp.repaint();
+                       } catch (RNATemplateDrawingAlgorithmException e1) {
+                               e1.printStackTrace();
+                               JOptionPane.showMessageDialog(this, e1.getMessage(), "Template-based RNA drawing error", JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+               else if (e.getActionCommand().equals("save"))
+               {
+                       try {
+                               _sk.getTemplate().toXMLFile(getCurrentFilePath());
+                               System.out.println("Template saved in " + getCurrentFilePath().toString());
+                       } catch (ExceptionXMLGeneration e1) {
+                               e1.printStackTrace();
+                       } catch (ExceptionInvalidRNATemplate e1) {
+                               e1.printStackTrace();
+                       }
+               }
+               else if (e.getActionCommand().equals("save as"))
+               {
+                       JFileChooser chooser = new JFileChooser();
+                       FileFilter filter = new FileNameExtensionFilter("VARNA RNA template (.xml)", "xml");
+                   chooser.setFileFilter(filter);
+                       if (chooser.showSaveDialog(_sk) == JFileChooser.APPROVE_OPTION) {
+                               String path = chooser.getSelectedFile().getAbsolutePath();
+                               if (!path.toLowerCase().endsWith(".xml")) {
+                                       path = path + ".xml";
+                               }
+                               try {
+                                       _sk.getTemplate().toXMLFile(new File(path));
+                                       System.out.println("Template saved in " + path);
+                                       setCurrentFilePath(new File(path));
+                               } catch (ExceptionXMLGeneration e1) {
+                                       e1.printStackTrace();
+                               } catch (ExceptionInvalidRNATemplate e1) {
+                                       e1.printStackTrace();
+                               }
+                       }
+               }
+               else if (e.getActionCommand().equals("new"))
+               {
+                       clearTemplate();
+               }
+               else if (e.getActionCommand().equals("open"))
+               {
+                       JFileChooser chooser = new JFileChooser();
+                   FileFilter filter = new FileNameExtensionFilter("VARNA RNA template (.xml)", "xml");
+                   chooser.setFileFilter(filter);
+                       if (chooser.showOpenDialog(_sk) == JFileChooser.APPROVE_OPTION) {
+                               File templatePath = chooser.getSelectedFile();
+                               loadTemplate(templatePath);
+                       }
+               }
+               else if (e.getActionCommand().equals("benchmark")) {
+                       new Benchmark(_vp.getRNA()).printAll();
+               }
+
+       }
+       
+       
+       private boolean getStraightBulges() {
+               return false;
+       }
+
+       public void dragEnter(DropTargetDragEvent arg0) {
+       }
+
+       public void dragExit(DropTargetEvent arg0) {
+       }
+
+       public void dragOver(DropTargetDragEvent arg0) {
+       }
+
+       public void drop(DropTargetDropEvent dtde) 
+       {
+         try 
+         {
+           Transferable tr = dtde.getTransferable();
+           DataFlavor[] flavors = tr.getTransferDataFlavors();
+           for (int i = 0; i < flavors.length; i++) 
+           {
+             if (flavors[i].isFlavorJavaFileListType()) 
+             {
+                 dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+                 List list = (List) tr.getTransferData(flavors[i]);
+                 for (int j = 0; j < list.size(); j++) 
+                 {
+                         Object o = list.get(j);
+                         if (dtde.getSource() instanceof DropTarget)
+                         {
+                                 DropTarget dt = (DropTarget) dtde.getSource();
+                                 Component c = dt.getComponent();
+                                 if (c instanceof VARNAPanel)
+                                 {
+                                         VARNAPanel vp = (VARNAPanel) c;
+                                         //String path = o.toString();
+                                         vp.loadFile((File) o, true); // BH SwingJS 
+                                         vp.repaint();
+                                 }
+                                 else if (c instanceof TemplatePanel)
+                                 {
+                                         TemplatePanel sk = (TemplatePanel) c;
+                                         //String path = o.toString();
+                                         sk.loadFromXmlFile((File) o); // BH SwingJS
+                                         sk.repaint();
+                                 }
+                         }
+                 }
+                 dtde.dropComplete(true);
+                 return;
+             }
+           }
+        dtde.rejectDrop();
+        } 
+        catch (Exception e) 
+        {
+                e.printStackTrace();
+            dtde.rejectDrop();
+         }
+       }
+
+       public void dropActionChanged(DropTargetDragEvent arg0) {
+       }
+
+       public VARNAPanel getVarnaPanel() {
+               return _vp;
+       }
+       
+       public void flipButtonEnable() {
+               flipButton.setEnabled(true);
+       }
+       
+       public void flipButtonDisable() {
+               flipButton.setEnabled(false);
+       }
+       
+       public void retrieveTemplates()
+       {
+               URL u;
+               
+               try {
+                       u = new URL("http://127.0.0.1/VARNA/templateShare/actions.php?action=retrieve&nbHelices=3&nbMultiLoops=1&length=50");
+               URLConnection uc = u.openConnection();
+               InputStreamReader  isr = new InputStreamReader(uc.getInputStream());
+               BufferedReader b = new BufferedReader(isr);
+               ArrayList<String> res = new ArrayList<String>();
+               String s = b.readLine();
+               while(s!=null)
+               {
+                       res.add(s);
+                       s = b.readLine();
+               }
+               } catch (MalformedURLException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditorPanelUI.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEditorPanelUI.java
new file mode 100644 (file)
index 0000000..9a7725e
--- /dev/null
@@ -0,0 +1,112 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.geom.Point2D;
+
+import javax.swing.undo.UndoManager;
+import javax.swing.undo.UndoableEditSupport;
+
+import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement.RelativePosition;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+
+public class TemplateEditorPanelUI {
+
+       private UndoableEditSupport _undoableEditSupport;
+       private TemplatePanel _tp;
+       private Tool selectedTool = Tool.CREATE_HELIX;
+       
+
+
+       public enum Tool {
+               SELECT, CREATE_HELIX, CREATE_UNPAIRED
+       }
+
+       public TemplateEditorPanelUI(TemplatePanel tp)
+       {
+               _tp = tp;
+                _undoableEditSupport = new UndoableEditSupport(tp);
+       }
+       
+       public Tool getSelectedTool() {
+               return selectedTool;
+       }
+
+       public void setSelectedTool(Tool selectedTool) {
+               this.selectedTool = selectedTool;
+       }
+       
+       /* Generic undoable event firing for edge movement */
+       public void undoableEdgeMove(GraphicalTemplateElement h, GraphicalTemplateElement.RelativePosition edge,double nx, double ny)
+       {
+               _undoableEditSupport.postEdit(new TemplateEdits.ElementEdgeMoveTemplateEdit( h,edge,nx,ny,_tp));
+               h.setEdgePosition(edge, new Point2D.Double(nx,ny));
+               _tp.repaint();          
+       }
+       
+       public void setEdge5UI(GraphicalTemplateElement h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_CONNECT_START5, nx,ny); }     
+       public void setEdge3UI(UnpairedRegion h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_CONNECT_END3, nx,ny); }       
+       public void setEdge5TangentUI(UnpairedRegion h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_EDIT_TANGENT_5, nx,ny); }
+       public void setEdge3TangentUI(UnpairedRegion h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_EDIT_TANGENT_3, nx,ny); }     
+       public void moveUnpairedUI(UnpairedRegion u, double nx, double ny)
+       { undoableEdgeMove(u,GraphicalTemplateElement.RelativePosition.RP_INNER_MOVE, nx,ny); } 
+       public void moveHelixUI(Helix h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_INNER_MOVE, nx,ny); } 
+       public void setHelixPosUI(Helix h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_EDIT_START, nx,ny); } 
+       public void setHelixExtentUI(Helix h, double nx, double ny)
+       { undoableEdgeMove(h,GraphicalTemplateElement.RelativePosition.RP_EDIT_END, nx,ny); }   
+       
+
+       public void addElementUI(GraphicalTemplateElement h)
+       {
+               _undoableEditSupport.postEdit(new TemplateEdits.ElementAddTemplateEdit( h,_tp));
+               _tp.addElement(h);
+       }
+
+       public void removeElementUI(GraphicalTemplateElement h)
+       {
+               _undoableEditSupport.postEdit(new TemplateEdits.ElementRemoveTemplateEdit( h,_tp));
+               _tp.removeElement(h);
+       }
+       
+
+       public void addUndoableEditListener(UndoManager manager)
+       {
+               _undoableEditSupport.addUndoableEditListener(manager);
+       }
+       
+       public void addConnectionUI(GraphicalTemplateElement h1,
+                       GraphicalTemplateElement.RelativePosition e1,  
+                       GraphicalTemplateElement h2,
+                       GraphicalTemplateElement.RelativePosition e2)
+       {
+               if (GraphicalTemplateElement.canConnect(h1, e1,h2, e2))
+               {
+               Connection c = _tp.addConnection(h1,e1,h2,e2);
+               _undoableEditSupport.postEdit(new TemplateEdits.ElementAttachTemplateEdit(c,_tp));
+               }
+       }
+
+       public void removeConnectionUI(Connection c)
+       {
+               _undoableEditSupport.postEdit(new TemplateEdits.ElementDetachTemplateEdit(c,_tp));
+               _tp.removeConnection(c);
+       }
+
+       public void flipHelixUI(Helix h)
+       {
+                         _undoableEditSupport.postEdit(new TemplateEdits.HelixFlipTemplateEdit(h,_tp));
+                         _tp.flip(h);
+                         _tp.repaint();
+       }
+       
+       public RNATemplate getTemplate()
+       {
+               return _tp.getTemplate();
+       }
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEdits.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplateEdits.java
new file mode 100644 (file)
index 0000000..d086989
--- /dev/null
@@ -0,0 +1,174 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.geom.Point2D;
+
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.UndoableEdit;
+
+public class TemplateEdits {
+       public static final double MAX_DISTANCE= 15.0;
+               
+       public static  class ElementAddTemplateEdit extends AbstractUndoableEdit
+       {
+               private GraphicalTemplateElement _h;
+               private TemplatePanel _p;
+               public ElementAddTemplateEdit(GraphicalTemplateElement h,TemplatePanel p)
+               {
+                       _h = h;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _p.removeElement(_h);
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _p.addElement(_h);
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Graphical element added"; }
+       };
+       public static  class ElementRemoveTemplateEdit extends AbstractUndoableEdit
+       {
+               private GraphicalTemplateElement _h;
+               private TemplatePanel _p;
+               public ElementRemoveTemplateEdit(GraphicalTemplateElement h,TemplatePanel p)
+               {
+                       _h = h;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _p.addElement(_h);
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _p.removeElement(_h);
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Graphical element removed"; }
+       };
+       public static  class ElementAttachTemplateEdit extends AbstractUndoableEdit
+       {
+               Connection _c;
+               private TemplatePanel _p;
+               public ElementAttachTemplateEdit(Connection c,
+                               TemplatePanel p)
+               {
+                       _c = c;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _p.removeConnection(_c);
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _c = _p.addConnection(_c._h1,_c._edge1,_c._h2,_c._edge2);
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Graphical elements attached"; }
+       };
+       public static  class ElementDetachTemplateEdit extends AbstractUndoableEdit
+       {
+               Connection _c;
+               private TemplatePanel _p;
+               public ElementDetachTemplateEdit(Connection c,
+                               TemplatePanel p)
+               {
+                       _c = c;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _c = _p.addConnection(_c._h1,_c._edge1,_c._h2,_c._edge2);
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _p.removeConnection(_c);
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Graphical elements detached"; }
+       };
+       
+       public static  class ElementEdgeMoveTemplateEdit extends AbstractUndoableEdit
+       {
+               private GraphicalTemplateElement _ur;
+               GraphicalTemplateElement.RelativePosition _edge;
+               private double _ox; 
+               private double _oy;
+               private double _nx; 
+               private double _ny;
+               private TemplatePanel _p;
+               public ElementEdgeMoveTemplateEdit(GraphicalTemplateElement ur, GraphicalTemplateElement.RelativePosition edge, double nx, double ny, TemplatePanel p)
+               {
+                       _ur = ur;
+                       _edge = edge;
+                       _ox = ur.getEdgePosition(edge).x;
+                       _oy = ur.getEdgePosition(edge).y;
+                       _nx = nx;
+                       _ny = ny;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _ur.setEdgePosition(_edge,new Point2D.Double(_ox,_oy));
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _ur.setEdgePosition(_edge,new Point2D.Double(_nx,_ny));
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Edge moved "+_edge; }
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof ElementEdgeMoveTemplateEdit)
+                       {
+                               ElementEdgeMoveTemplateEdit e = (ElementEdgeMoveTemplateEdit) anEdit;
+                               if (e._edge==_edge)
+                               {
+                                       Point2D.Double po1 = new Point2D.Double(_ox,_oy);
+                                       Point2D.Double pn1 = new Point2D.Double(_nx,_ny);
+                                       Point2D.Double po2 = new Point2D.Double(e._ox,e._oy);
+                                       Point2D.Double pn2 = new Point2D.Double(e._nx,e._ny);
+                                       if ((_ur==e._ur)&&(pn1.equals(po2))&&(po1.distance(pn2)<MAX_DISTANCE))
+                                       {
+                                               _nx = e._nx;
+                                               _ny = e._ny;
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               }
+       };
+       public static  class HelixFlipTemplateEdit extends AbstractUndoableEdit
+       {
+               private Helix _h;
+               private TemplatePanel _p;
+               public HelixFlipTemplateEdit(Helix h, TemplatePanel p)
+               {
+                       _h = h;
+                       _p = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _h.toggleFlipped();
+                       _p.repaint();                   
+               }
+               public void redo() throws CannotRedoException {
+                       _h.toggleFlipped();
+                       _p.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Helix flipped "; }
+       };
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplatePanel.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/TemplatePanel.java
new file mode 100644 (file)
index 0000000..ce47716
--- /dev/null
@@ -0,0 +1,662 @@
+/**
+ * 
+ */
+package fr.orsay.lri.varna.applications.templateEditor;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.geom.Point2D;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.undo.UndoManager;
+
+import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement.RelativePosition;
+import fr.orsay.lri.varna.controlers.ControleurMolette;
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.exceptions.ExceptionXmlLoading;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+
+
+
+
+/**
+ * @author ponty
+ *
+ */
+public class TemplatePanel extends JPanel {
+  /**
+        * 
+        */
+       private static final long serialVersionUID = 3162771335587335679L;
+       
+       
+       private ArrayList<GraphicalTemplateElement> _RNAComponents;
+       private ArrayList<Connection> _RNAConnections;
+       private Hashtable<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Connection> _helixToConnection;
+       private TemplateEditorPanelUI _ui;
+       
+       private RNATemplate _template;
+
+       
+       private static Color[] BackgroundColors = {Color.blue,Color.red,Color.cyan,Color.green,Color.lightGray,Color.magenta,Color.PINK};
+
+       private int _nextBackgroundColor = 0;
+       
+       private static double scaleFactorDefault = 0.7;
+       private double scaleFactor = scaleFactorDefault;
+       
+       
+       private TemplateEditor _editor;
+       
+       
+       public double getScaleFactor() {
+               return scaleFactor;
+       }
+
+       public void setScaleFactor(double scaleFactor) {
+               this.scaleFactor = scaleFactor;
+       }
+
+       public Color nextBackgroundColor()
+       {
+               Color c = BackgroundColors[_nextBackgroundColor++];
+               _nextBackgroundColor = _nextBackgroundColor % BackgroundColors.length;
+               return new Color(c.getRed(),c.getBlue(),c.getGreen(),50);
+       }
+       
+       public TemplatePanel(TemplateEditor parent)
+       {
+               _editor = parent;
+               init();
+       }
+
+       public RNATemplate getTemplate()
+       {
+               return _template;
+       }
+       
+       List<GraphicalTemplateElement> getRNAComponents() {
+               return _RNAComponents;
+       }
+       
+       private void init()
+       {
+               _ui = new TemplateEditorPanelUI(this); 
+               
+               _RNAComponents = new ArrayList<GraphicalTemplateElement>();
+               _RNAConnections = new ArrayList<Connection>();
+               _helixToConnection = new Hashtable<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Connection>();
+               
+               _template = new RNATemplate();
+               
+               setBackground(Color.WHITE);
+               MouseControler mc = new MouseControler(this,_ui); 
+               addMouseListener(mc);
+               addMouseMotionListener(mc);
+               addMouseWheelListener(mc);
+               _solidStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 3.0f);
+               float[] dash = { 5.0f, 5.0f };
+               _dashedStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 3.0f, dash, 0);
+       }
+       
+       public void addUndoableEditListener(UndoManager manager)
+       {
+               _ui.addUndoableEditListener(manager);
+       }
+       
+       public TemplateEditorPanelUI getTemplateUI()
+       {
+               return _ui;
+       }
+
+       
+       public void flip(Helix h)
+       { 
+               h.toggleFlipped(); 
+       }
+       
+       public void addElement(GraphicalTemplateElement h)
+       { 
+               _RNAComponents.add(h); 
+       }
+
+       public void removeElement(GraphicalTemplateElement h)
+       { 
+               _RNAComponents.remove(h);
+               try {
+                       _template.removeElement(h.getTemplateElement());
+               } catch (ExceptionInvalidRNATemplate e) {
+                       //e.printStackTrace();
+               }
+       }
+
+       private GraphicalTemplateElement _selected = null;
+       
+       public GraphicalTemplateElement getSelected()
+       {
+               return _selected;
+       }
+
+       public void setSelected(GraphicalTemplateElement sel)
+       {
+               _selected = sel;
+               if (_selected instanceof Helix) {
+                       _editor.flipButtonEnable();
+               } else {
+                       _editor.flipButtonDisable();
+               }
+       }
+
+       Helix.RelativePosition _relpos = Helix.RelativePosition.RP_OUTER;
+       
+       public void setSelectedEdge(Helix.RelativePosition rel)
+       {
+               _relpos = rel;
+       }
+
+       public void unselectEdge(Helix.RelativePosition rel)
+       {
+               _relpos = rel;
+       }
+       
+       Point2D.Double _mousePos = new Point2D.Double();
+
+       public void setPointerPos(Point2D.Double p)
+       {
+               _mousePos = p;
+       }       
+       
+       public void Unselect()
+       {
+               _editor.flipButtonDisable();
+               _selected = null;
+       }
+
+       public GraphicalTemplateElement getElement(RNATemplateElement t)
+       {
+               for(GraphicalTemplateElement t2: _RNAComponents)
+                       if (t==t2.getTemplateElement())
+                               return t2;
+               return null;
+       }
+
+       
+       public GraphicalTemplateElement getElementAt(double x, double y)
+       {
+               return getElementAt(x, y, null);
+       }
+       public GraphicalTemplateElement getElementAt(double x, double y, GraphicalTemplateElement excluded)
+       {
+               GraphicalTemplateElement h = null;
+               for (int i=0; i<_RNAComponents.size();i++)
+               {
+                       GraphicalTemplateElement h2 = _RNAComponents.get(i);
+                       if ((
+                                          (h2.getRelativePosition(x, y)== Helix.RelativePosition.RP_CONNECT_END3)
+                                       || (h2.getRelativePosition(x, y)== Helix.RelativePosition.RP_CONNECT_END5)
+                                       || (h2.getRelativePosition(x, y)== Helix.RelativePosition.RP_CONNECT_START3)
+                                       || (h2.getRelativePosition(x, y)== Helix.RelativePosition.RP_CONNECT_START5))
+                               && (excluded!=h2))
+                       {
+                               h = h2;
+                       }
+               }
+               if (h==null)
+               { h = getElementCloseTo(x, y, excluded);};
+               return h;
+       }
+
+       public GraphicalTemplateElement getElementCloseTo(double x, double y)
+       {
+               return getElementCloseTo(x, y, null);
+       }
+       public GraphicalTemplateElement getElementCloseTo(double x, double y, GraphicalTemplateElement excluded)
+       {
+               GraphicalTemplateElement h = null;
+               for (int i=0; i<_RNAComponents.size();i++)
+               {
+                       GraphicalTemplateElement h2 = _RNAComponents.get(i);
+                       if ((h2.getRelativePosition(x, y) != Helix.RelativePosition.RP_OUTER)
+                               && (excluded!=h2))
+                       {
+                               h = h2;
+                       }
+               }
+               return h;
+       }
+       
+       public void addConnection(Connection c)
+       {
+               _RNAConnections.add(c);
+               _helixToConnection.put(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h1,c._edge1), c);
+               _helixToConnection.put(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h2,c._edge2), c);
+                       try {
+                               c._h1.attach(c._h2, c._edge1, c._edge2);
+                               c._h2.attach(c._h1, c._edge2, c._edge1);
+                       } catch (ExceptionInvalidRNATemplate e) {
+                               System.out.println(e.toString());// TODO Auto-generated catch block
+               }
+
+       }
+
+       public Connection addConnection(GraphicalTemplateElement h1, GraphicalTemplateElement.RelativePosition edge1,GraphicalTemplateElement h2, GraphicalTemplateElement.RelativePosition edge2)
+       {
+               if ((h1!=h2)&&(getPartner(h1,edge1)==null)&&(getPartner(h2,edge2)==null))
+               {
+                       Connection c = new Connection(h1,edge1,h2,edge2);
+                       addConnection(c);
+                       return c;
+               }
+               return null;
+       }
+       
+       /**
+        * When there is already a connection in the underlying RNATemplate
+        * and we want to create one at the graphical level.
+        */
+       public void addGraphicalConnection(GraphicalTemplateElement h1, GraphicalTemplateElement.RelativePosition edge1,GraphicalTemplateElement h2, GraphicalTemplateElement.RelativePosition edge2) {
+               //System.out.println("Connecting " + h1 + " " + edge1 + " to " + h2 + " " + edge2);
+               Connection c = new Connection(h1,edge1,h2,edge2);
+               _RNAConnections.add(c);
+               _helixToConnection.put(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h1,c._edge1), c);
+               _helixToConnection.put(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h2,c._edge2), c);
+               c._h1.graphicalAttach(c._h2, c._edge1, c._edge2);
+               c._h2.graphicalAttach(c._h1, c._edge2, c._edge1);
+       }
+       
+
+       public void removeConnection(Connection c)
+       {
+               _RNAConnections.remove(c);
+               _helixToConnection.remove(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h1,c._edge1));
+               _helixToConnection.remove(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h2,c._edge2));
+               System.out.println("[A]"+c);
+               c._h1.detach(c._edge1);
+       }
+       
+       public boolean isInCycle(GraphicalTemplateElement el, GraphicalTemplateElement.RelativePosition edge)
+       {
+               Stack<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> > p = new Stack<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>>();
+               Hashtable<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Integer> alreadySeen = new Hashtable<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Integer>(); 
+               p.add(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(el,edge));
+               while(!p.empty())
+               {
+                       Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> c2 = p.pop();
+                       if (alreadySeen.containsKey(c2))
+                       {
+                               return true;
+                       }
+                       else
+                       {
+                               alreadySeen.put(c2, new Integer(1));
+                       }
+                       GraphicalTemplateElement.RelativePosition next = c2.first.getConnectedEdge(c2.second);
+                       Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> otherEnd = new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c2.first,next);
+                       if (!alreadySeen.containsKey(otherEnd))
+                       {
+                               p.push(otherEnd);
+                       }
+                       else
+                       {
+                               Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> child =  getPartner(c2.first,c2.second);
+                               if (child!=null)
+                               {
+                                       p.push(child);
+                               }
+                       }
+                       
+               }
+               
+               return false;   
+       }
+       
+       private static Color[] _colors = {Color.gray,Color.pink,Color.cyan,Color.RED,Color.green,Color.orange};
+       
+       public static Color getIndexedColor(int n)
+       {
+               return _colors[n%_colors.length];
+       }
+       
+       public HashMap<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Integer> buildConnectedComponents()
+       {
+               HashMap<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Integer> alreadySeen = new HashMap<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>,Integer>();
+               int numConnectedComponents = 0;
+               for (GraphicalTemplateElement el : this._RNAComponents)
+               {
+                       for (GraphicalTemplateElement.RelativePosition edge : el.getConnectedEdges())
+                       {
+                               Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> c = new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(el,edge);
+                               if (!alreadySeen.containsKey(c))
+                               {
+                                       Stack<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> > p = new Stack<Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>>();
+                                       p.add(c);
+                                       p.add(new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(el,el.getConnectedEdge(edge)));
+                                       while(!p.empty())
+                                       {
+                                               Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> c2 = p.pop();
+                                               if (!alreadySeen.containsKey(c2))
+                                               {
+                                                       //System.out.println("  "+numConnectedComponents+"  "+c2);
+                                                       c2.first.setMainColor(c2.second, getIndexedColor(numConnectedComponents));
+                                                       alreadySeen.put(c2, new Integer(numConnectedComponents));
+                                                       GraphicalTemplateElement.RelativePosition next = c2.first.getConnectedEdge(c2.second);
+                                                       Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> otherEnd = new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c2.first,next);
+                                                       p.push(otherEnd);
+                                                       Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> child =  getPartner(c2.first,c2.second);
+                                                       if (child!=null)
+                                                       { p.push(child); }
+                                               }       
+                                       }
+                                       numConnectedComponents += 1;
+                               }
+                       }
+               }
+               return alreadySeen;
+       }
+       
+       public boolean isInCycle(Connection c)
+       {
+               return isInCycle(c._h1,c._edge1); 
+       }
+       
+       public Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> getPartner(GraphicalTemplateElement h, GraphicalTemplateElement.RelativePosition edge)
+       {
+               Connection c = getConnection(h, edge);
+               if (c != null)
+               {
+                         if ((c._h1==h)&&(c._edge1==edge))
+                         {  return new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h2,c._edge2);  }
+                         else 
+                         {  return new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(c._h1,c._edge1); }
+               }
+               else
+               {
+                       return null;
+               }
+       }
+
+       public Connection getConnection(GraphicalTemplateElement h, Helix.RelativePosition edge)
+       {
+               Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> target = new Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition>(h,edge);
+               if (_helixToConnection.containsKey(target))
+               {
+                       return _helixToConnection.get(target);
+               }
+               else
+               { return null; }
+       }
+       
+       private boolean isConnected(Helix h, GraphicalTemplateElement.RelativePosition edge)
+       {
+               Couple<GraphicalTemplateElement,GraphicalTemplateElement.RelativePosition> partner = getPartner(h,edge);
+               return partner!=null;
+       }
+       
+
+       // Aspects graphiques
+       
+       private static final Color  CYCLE_COLOR = Color.red;
+       private static final Color  NON_EXISTANT_COLOR = Color.gray.brighter();
+       private static final Color  CONTROL_COLOR = Color.gray.darker();
+       private static final Color  BACKGROUND_COLOR = Color.white;
+       
+       private Stroke  _solidStroke;
+       private Stroke  _dashedStroke;
+       
+
+       
+       
+       private void drawConnections(Graphics2D g2d, Connection c)
+       {
+               GraphicalTemplateElement h1 = c._h1;
+               GraphicalTemplateElement.RelativePosition edge1 = c._edge1;
+               Point2D.Double p1 = h1.getEdgePosition(edge1);
+               GraphicalTemplateElement h2 = c._h2;
+               GraphicalTemplateElement.RelativePosition edge2 = c._edge2;
+               Point2D.Double p2 = h2.getEdgePosition(edge2);
+               if (isInCycle(c))
+               {
+                       g2d.setColor(CYCLE_COLOR);
+               }
+               else
+               {
+                       g2d.setColor(GraphicalTemplateElement.BACKBONE_COLOR);
+               }
+               g2d.drawLine((int)p1.x,(int)p1.y,(int)p2.x,(int)p2.y);
+       }
+       
+
+       public void paintComponent(Graphics g)
+       {
+               //rescale();
+               
+               // Debug code to show drawing area
+//             g.setColor(Color.red);
+//             g.fillRect(0,0,getWidth(),getHeight());
+//             g.setColor(Color.white);
+//             g.fillRect(10,10,getWidth()-20,getHeight()-20);
+               
+               g.setColor(Color.white);
+               g.fillRect(0,0,getWidth(),getHeight());
+               
+               Graphics2D g2d = (Graphics2D) g;
+               g2d.scale(scaleFactor, scaleFactor);
+               g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                               RenderingHints.VALUE_ANTIALIAS_ON);
+               removeAll();
+               //super.paintComponent(g2d);
+
+               buildConnectedComponents();
+               
+               if (_selected!=null)
+               {
+                       if (_relpos != GraphicalTemplateElement.RelativePosition.RP_OUTER)
+                       {       
+                       Point2D.Double p = _selected.getEdgePosition(_relpos);
+                       g2d.setStroke(_solidStroke);
+                       g2d.drawLine((int)_mousePos.x, (int)_mousePos.y, (int)p.x, (int)p.y);
+                       }
+               }
+               for (int i=0;i<_RNAConnections.size();i++)
+               {
+                       Connection c = _RNAConnections.get(i);
+                       drawConnections(g2d,c);
+               }
+               for (int i=0;i<_RNAComponents.size();i++)
+               {
+                       GraphicalTemplateElement elem = _RNAComponents.get(i);
+                       //g2d.setColor(elem.getDominantColor());
+                       //g2d.fill(elem.getArea());
+                       if (_selected == elem)
+                       {  
+                               elem.draw(g2d,true); 
+                       }
+                       else
+                       {  elem.draw(g2d,false);  }
+               }
+       }
+       
+       /**
+        * Get the bounding rectangle of the RNA components, always including the origin (0,0).
+        */
+       public Rectangle getBoundingRectange() {
+               int minX = 0;
+               int maxX = 0;
+               int minY = 0;
+               int maxY = 0;
+               for(int i=0;i<this._RNAComponents.size();i++)
+               {
+                       GraphicalTemplateElement h = _RNAComponents.get(i);
+                       Polygon p = h.getBoundingPolygon();
+                       Rectangle r = p.getBounds();
+                       minX = Math.min(minX,r.x);
+                       maxX = Math.max(maxX,r.x+r.width);
+                       minY = Math.min(minY,r.y);
+                       maxY = Math.max(maxY,r.y+r.height);
+               }
+               Rectangle res = new Rectangle();
+               res.x = minX;
+               res.y = minY;
+               res.width = maxX - minX;
+               res.height = maxY - minY;
+               return res;
+       }
+
+       public void rescale()
+       {
+               Rectangle rect = getBoundingRectange();
+               
+               if (rect.x < 0 || rect.y < 0) {
+            for(int i=0;i<this._RNAComponents.size();i++)
+            {
+                    GraphicalTemplateElement h = _RNAComponents.get(i);
+                    h.translate(rect.x<0 ? -rect.x : 0, rect.y<0 ? -rect.y : 0);
+            }
+            rect = getBoundingRectange();
+               }
+               
+               // areaW and H are width and height, in pixels, of the drawing area
+               // including the "outside" parts available with scrolling
+               int areaW = (int) ((rect.width + 100) * scaleFactor);
+               int areaH = (int) ((rect.height + 100) * scaleFactor);
+               // make it cover at least the space visible in the window
+               //areaW = Math.max(areaW, (int) (_editor.getJp().getViewport().getViewSize().width));
+               //areaH = Math.max(areaH, (int) (_editor.getJp().getViewport().getViewSize().height));
+               //System.out.println(areaW + " x " + areaH);
+               setPreferredSize(new Dimension(areaW, areaH));
+               revalidate();
+       }
+
+       public void clearTemplate() {
+               loadTemplate(new RNATemplate());
+       }
+
+       /**
+        * Load an existing RNATemplate object in this panel.
+        */
+       public void loadTemplate(RNATemplate template) {
+               _template = template;
+               _RNAComponents.clear();
+               _RNAConnections.clear();
+               _helixToConnection.clear();
+               
+               // We need a template element -> graphical template element mapping
+               Map<RNATemplateElement, GraphicalTemplateElement> map = new HashMap<RNATemplateElement, GraphicalTemplateElement>();
+               
+               // First, we load elements
+               {
+                       Iterator<RNATemplateElement> iter = template.classicIterator();
+                       while (iter.hasNext()) {
+                               RNATemplateElement templateElement = iter.next();
+                               if (templateElement instanceof RNATemplateHelix) {
+                                       RNATemplateHelix templateHelix = (RNATemplateHelix) templateElement;
+                                       Helix graphicalHelix = new Helix(templateHelix);
+                                       graphicalHelix.setDominantColor(nextBackgroundColor());
+                                       _RNAComponents.add(graphicalHelix);
+                                       map.put(templateHelix, graphicalHelix);
+                               } else if (templateElement instanceof RNATemplateUnpairedSequence) {
+                                       RNATemplateUnpairedSequence templateSequence = (RNATemplateUnpairedSequence) templateElement;
+                                       UnpairedRegion graphicalSequence = new UnpairedRegion(templateSequence);
+                                       graphicalSequence.setDominantColor(nextBackgroundColor());
+                                       _RNAComponents.add(graphicalSequence);
+                                       map.put(templateSequence, graphicalSequence);
+                               }
+                       }
+               }
+               
+               // Now, we load edges
+               {
+                       Iterator<EdgeEndPoint> iter = template.makeEdgeList().iterator();
+                       while (iter.hasNext()) {
+                               EdgeEndPoint v1 = iter.next();
+                               EdgeEndPoint v2 = v1.getOtherEndPoint();
+                               GraphicalTemplateElement gte1 = map.get(v1.getElement());
+                               GraphicalTemplateElement gte2 = map.get(v2.getElement());
+                               RelativePosition rp1 = gte1.relativePositionFromEdgeEndPointPosition(v1.getPosition());
+                               RelativePosition rp2 = gte2.relativePositionFromEdgeEndPointPosition(v2.getPosition());
+                               addGraphicalConnection(gte1, rp1, gte2, rp2);
+                       }
+               }
+               
+               zoomFit();
+               //repaint();
+       }
+
+       /**
+        * Load a template from an XML file.
+        */
+       public void loadFromXmlFile(File filename) {
+               try {
+                       RNATemplate newTemplate = RNATemplate.fromXMLFile(filename);
+                       loadTemplate(newTemplate);
+               } catch (ExceptionXmlLoading e) {
+                       e.printStackTrace();
+                       JOptionPane.showMessageDialog(this, e.getMessage(), "Template loading error", JOptionPane.ERROR_MESSAGE);
+               }
+       }
+       
+       private void zoomFinish() {
+               rescale();
+               repaint();
+       }
+       
+       public void zoomIn() {
+               scaleFactor *= 1.2;
+               zoomFinish();
+       }
+       
+       public void zoomOut() {
+               scaleFactor /= 1.2;
+               zoomFinish();
+       }
+       
+       public void zoomReset() {
+               scaleFactor = scaleFactorDefault;
+               zoomFinish();
+       }
+       
+       public void zoomFit() {
+               if (_RNAComponents.isEmpty()) {
+                       zoomReset();
+               } else {
+                       Rectangle rect = getBoundingRectange();
+                       double areaW = (rect.width + 100);
+                       double areaH = (rect.height + 100);
+                       // make it cover at least the space visible in the window
+                       scaleFactor = 1;
+                       scaleFactor = Math.min(scaleFactor, _editor.getJp().getViewport().getSize().width / areaW);
+                       scaleFactor = Math.min(scaleFactor, _editor.getJp().getViewport().getSize().height / areaH);
+                       zoomFinish();
+               }
+       }
+       
+       public void translateView(Point trans) {
+               int newX = _editor.getJp().getHorizontalScrollBar().getValue() - trans.x;
+               int newY = _editor.getJp().getVerticalScrollBar().getValue() - trans.y;
+               newX = Math.max(0, Math.min(newX, _editor.getJp().getHorizontalScrollBar().getMaximum()));
+               newY = Math.max(0, Math.min(newY, _editor.getJp().getVerticalScrollBar().getMaximum()));
+               _editor.getJp().getHorizontalScrollBar().setValue(newX);
+               _editor.getJp().getVerticalScrollBar().setValue(newY);
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/applications/templateEditor/UnpairedRegion.java b/srcjar/fr/orsay/lri/varna/applications/templateEditor/UnpairedRegion.java
new file mode 100644 (file)
index 0000000..363fe85
--- /dev/null
@@ -0,0 +1,488 @@
+package fr.orsay.lri.varna.applications.templateEditor;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.awt.Shape;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Point2D.Double;
+import java.util.ArrayList;
+
+import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement.RelativePosition;
+import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected;
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.geom.CubicBezierCurve;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+
+public class UnpairedRegion extends GraphicalTemplateElement{
+       private RNATemplateUnpairedSequence _e;
+       public static final double DEFAULT_VECTOR_LENGTH = 35;
+       public static final double DEFAULT_VECTOR_DISTANCE = 35;
+
+       private Point2D.Double[] sequenceBasesCoords = null;
+
+       public UnpairedRegion(double x, double y, RNATemplate tmp)
+       {
+               _e = tmp.new RNATemplateUnpairedSequence("");
+               _e.setVertex5(new Point2D.Double(x,y));
+               _e.setVertex3(new Point2D.Double(x+DEFAULT_VECTOR_DISTANCE,y));
+               _e.setInTangentVectorLength(DEFAULT_VECTOR_LENGTH);
+               _e.setInTangentVectorAngle(-Math.PI/2.0);
+               _e.setOutTangentVectorLength(DEFAULT_VECTOR_LENGTH);
+               _e.setOutTangentVectorAngle(-Math.PI/2.0);
+               updateLength();
+
+       }
+
+       /**
+        * Build an UnpairedRegion object from a RNATemplateUnpairedSequence
+        * object. The RNATemplateUnpairedSequence must be connected to
+        * an helix on both sides.
+        */
+       public UnpairedRegion(RNATemplateUnpairedSequence templateSequence)
+       {
+               _e = templateSequence;
+       }
+
+       public Point2D.Double getEdge5()
+       { 
+               RelativePosition r = RelativePosition.RP_CONNECT_START5;
+               Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
+               return (isAnchored5()? c.second.getEdgePosition(c.first): _e.getVertex5()); 
+       }
+
+       public Point2D.Double getEdge3()
+       { 
+               RelativePosition r = RelativePosition.RP_CONNECT_END3;
+               Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
+               return (isAnchored3()? c.second.getEdgePosition(c.first): _e.getVertex3()); 
+       }
+
+       public Point2D.Double getCenter()
+       { 
+               Point2D.Double p1 = getEdge5();
+               Point2D.Double p2 = getEdge3();
+               return new Point2D.Double((p1.x+p2.x)/2.,(p1.y+p2.y)/2.); 
+       }
+       
+       
+       public void setEdge5(Point2D.Double d)
+       {
+               _e.setVertex5(d);
+               updateLength();
+       }
+
+       public void setEdge3(Point2D.Double d)
+       {
+               _e.setVertex3(d);
+               updateLength();
+       }
+
+       public void setCenter(Point2D.Double d)
+       {
+               Point2D.Double p1 = getEdge5();
+               Point2D.Double p2 = getEdge3();
+               double dx = p1.x-p2.x;
+               double dy = p1.y-p2.y;
+               _e.setVertex3(new Point2D.Double(d.x-dx/2.,d.y-dy/2.));
+               _e.setVertex5(new Point2D.Double(d.x+dx/2.,d.y+dy/2.));
+               invalidateCoords();
+       }
+       
+       
+       public boolean isAnchored5()
+       {
+               return (_e.getIn().getOtherElement()!=null);
+       }
+
+       public boolean isAnchored3()
+       {
+               return (_e.getOut().getOtherElement()!=null);
+       }
+
+
+       public static Shape bezToShape(CubicBezierCurve c)
+       {
+               GeneralPath p = new GeneralPath();
+               int nb = 9;
+               double[] tab = new double[nb];
+               for (int i=0;i<nb;i++)
+               {
+                       tab[i] = (c.getApproxCurveLength()*(double)i)/(double)nb;
+               }
+               Point2D.Double[] points = c.uniformParam(tab);
+               System.out.println(points.length);
+               p.moveTo((float)points[0].x,(float)points[0].y);
+               for (int i=1;i<nb;i++)
+               { 
+                       Point2D.Double a = points[i];
+                       System.out.println(a);
+                       p.lineTo((float)a.x,(float)a.y);
+               }
+               p.lineTo((float)c.getP3().x,(float)c.getP3().y);
+               return p;
+       }
+
+
+
+       public Shape getCurve()
+       {
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+               return new CubicCurve2D.Double(p5.x,p5.y,t5.x,t5.y,t3.x,t3.y,p3.x,p3.y);
+               //CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
+               //return bezToShape(c);
+
+       }
+
+       private int estimateNumberOfBases()
+       {
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+               CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
+               // Extremities don't count as unpaired bases because they are part of the connected helix.
+               return Math.max((int)Math.round(c.getApproxCurveLength()/Helix.LOOP_DISTANCE)-1, 1);
+       }
+
+       private void updateLength()
+       {
+               this._e.setLength(estimateNumberOfBases());
+               invalidateCoords();
+       }
+
+       
+       /**
+        * Mark the coordinates as invalid, ie. need to be calculated again if we want to draw them.
+        */
+       private void invalidateCoords() {
+               sequenceBasesCoords = null;
+       }
+
+       /**
+        * Calculate the positions of the unpaired bases.
+        */
+       private void calculeCoords() {
+               //System.out.println("calculate coords");
+               
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+               
+               // Draw bases on curve:
+               int n = _e.getLength();
+               // We choose to approximate the Bezier curve by 10*n straight lines.
+               CubicBezierCurve bezier = new CubicBezierCurve(p5, t5, t3, p3, 10*n);
+               double curveLength = bezier.getApproxCurveLength();
+               double delta_t = curveLength / (n+1);
+               double[] t = new double[n];
+               for (int k=0; k<n; k++) {
+                       t[k] = (k+1) * delta_t;
+               }
+               sequenceBasesCoords = bezier.uniformParam(t);
+       }
+
+       public void draw(Graphics2D g2d, boolean selected) {
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+               if (selected)
+               {
+                       g2d.setStroke(_dashedStroke);
+                       g2d.setColor(BACKBONE_COLOR);
+                       g2d.draw(getBoundingPolygon());
+                       g2d.setStroke(_solidStroke);
+                       drawAnchor(g2d,t5);
+                       drawAnchor(g2d,t3);
+                       double d5x = (t5.x-p5.x)/(t5.distance(p5));
+                       double d5y = (t5.y-p5.y)/(t5.distance(p5));
+                       double d3x = (t3.x-p3.x)/(t3.distance(p3));
+                       double d3y = (t3.y-p3.y)/(t3.distance(p3));
+                       double shift = -3.5;
+                       Point2D.Double tp5 = new Point2D.Double(t5.x-shift*d5x,t5.y-shift*d5y); 
+                       Point2D.Double tp3 = new Point2D.Double(t3.x-shift*d3x,t3.y-shift*d3y);
+
+                       drawArrow(g2d, p5, tp5, UNPAIRED_ARROW_WIDTH);
+                       drawArrow(g2d, p3, tp3, UNPAIRED_ARROW_WIDTH);
+               }
+               g2d.setColor(BACKBONE_COLOR);
+               g2d.setStroke(_solidStroke);
+               g2d.draw(getCurve());
+
+               if (sequenceBasesCoords == null) {
+                       calculeCoords();
+               }
+               for (int k=0; k<sequenceBasesCoords.length; k++) {
+                       drawBase(g2d, sequenceBasesCoords[k]);
+               }
+
+               if (!isAnchored5())
+               {drawAnchor5(g2d,p5); }
+               else
+               { drawMagnet(g2d,p5);}
+               if (!isAnchored3())
+               {drawAnchor3(g2d,p3); }
+               else
+               { drawMagnet(g2d,p3);}
+               
+               if (!isAnchored5() && !isAnchored3())
+               drawMove(g2d, getCenter());
+       }
+
+
+       public Point2D.Double getControl5()
+       {
+               Point2D.Double p5 = getEdge5();
+               double angle = _e.getInTangentVectorAngle();
+               return new Point2D.Double(p5.x+Math.cos(angle)*_e.getInTangentVectorLength(),
+                               p5.y+Math.sin(angle)*_e.getInTangentVectorLength());
+       }
+
+       public Point2D.Double getControl3()
+       {
+               Point2D.Double p3 = getEdge3(); 
+               double angle = _e.getOutTangentVectorAngle();
+               return new Point2D.Double(p3.x+Math.cos(angle)*_e.getOutTangentVectorLength(),
+                               p3.y+Math.sin(angle)*_e.getOutTangentVectorLength());
+       }
+
+       public static final double MAX_UNPAIRED_CONTROL_DISTANCE = 10.0;
+       public static final double UNPAIRED_ARROW_WIDTH = 6.0;
+
+
+
+       public Polygon getBoundingPolygon() {
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+
+               double minx = Math.min(p5.x,Math.min(p3.x,Math.min(t5.x,t3.x)));
+               double maxx = Math.max(p5.x,Math.max(p3.x,Math.max(t5.x,t3.x)));
+               double miny = Math.min(p5.y,Math.min(p3.y,Math.min(t5.y,t3.y)));
+               double maxy = Math.max(p5.y,Math.max(p3.y,Math.max(t5.y,t3.y)));
+               minx -= 10;
+               maxx += 10;
+               miny -= 10;
+               maxy += 10;
+               int[] x = {(int)minx,(int)maxx,(int)maxx,(int)minx};
+               int[] y = {(int)miny,(int)miny,(int)maxy,(int)maxy};
+               return new Polygon(x,y,4);
+       }
+
+       public RelativePosition getClosestEdge(double x, double y) {
+               Point2D.Double p = new Point2D.Double(x,y);
+               Point2D.Double p5 = getEdge5(); 
+               Point2D.Double p3 = getEdge3(); 
+               Point2D.Double t5 = getControl5(); 
+               Point2D.Double t3 = getControl3();
+               Point2D.Double ct = getCenter();
+               ArrayList<Couple<java.lang.Double,RelativePosition>> v = new ArrayList<Couple<java.lang.Double,RelativePosition>>(); 
+               v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p5),RelativePosition.RP_CONNECT_START5));
+               v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p3),RelativePosition.RP_CONNECT_END3));
+               v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t5),RelativePosition.RP_EDIT_TANGENT_5));
+               v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t3),RelativePosition.RP_EDIT_TANGENT_3));
+               v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(ct),RelativePosition.RP_INNER_MOVE));
+               double dist = java.lang.Double.MAX_VALUE;
+               RelativePosition r = RelativePosition.RP_OUTER;
+               
+               for (Couple<java.lang.Double,RelativePosition> c : v)
+               {
+                       if (c.first<dist)
+                       {
+                               dist = c.first;
+                               r = c.second;
+                       }
+               }
+               return r;
+       }
+
+       public RelativePosition getConnectedEdge(RelativePosition edge) {
+               switch(edge)
+               {
+               case RP_CONNECT_START5:
+                       return RelativePosition.RP_CONNECT_END3;
+               case RP_CONNECT_END3:
+                       return RelativePosition.RP_CONNECT_START5;
+               default:
+                       return RelativePosition.RP_OUTER;
+               }
+       }
+
+       public Point2D.Double getEdgePosition(RelativePosition edge)
+       {
+               switch(edge)
+               {
+               case RP_INNER_MOVE:
+                       return getCenter();
+               case RP_CONNECT_START5:
+                       return getEdge5();
+               case RP_CONNECT_END3:
+                       return getEdge3();
+               case RP_EDIT_TANGENT_5:
+                       return getControl5();
+               case RP_EDIT_TANGENT_3:
+                       return getControl3();
+               default:
+                       return getEdge5();
+               }
+       }
+
+       double v2a(Point2D.Double p)
+       {
+               return (double)Math.atan2(p.y, p.x);
+       }
+
+       public void updateControl5(Point2D.Double p)
+       {
+               Point2D.Double p5 = getEdge5();
+               _e.setInTangentVectorLength(p5.distance(p));
+               Point2D.Double x = new Point2D.Double(p.x-p5.x,p.y-p5.y); 
+               _e.setInTangentVectorAngle(v2a(x));
+               updateLength();
+       }
+
+       public void updateControl3(Point2D.Double p)
+       {
+               Point2D.Double p3 = getEdge3();
+               _e.setOutTangentVectorLength(p3.distance(p));
+               Point2D.Double x = new Point2D.Double(p.x-p3.x,p.y-p3.y); 
+               _e.setOutTangentVectorAngle(v2a(x));
+               updateLength();
+       }
+
+       public void translate(double x, double y) {
+               _e.getVertex5().x += x;
+               _e.getVertex5().y += y;
+               _e.getVertex3().x += x;
+               _e.getVertex3().y += y;
+               invalidateCoords();
+       }
+
+       public RelativePosition getRelativePosition(double x, double y) {
+               RelativePosition rp = getClosestEdge(x, y);
+               double d = getEdgePosition(rp).distance(new Point2D.Double(x,y));
+               if (d<MAX_UNPAIRED_CONTROL_DISTANCE)
+                       return rp;
+               if (getCurve().contains(new Point2D.Double(x,y)))
+                       return RelativePosition.RP_INNER_GENERAL;
+               return RelativePosition.RP_OUTER;
+       }
+
+       public Shape getArea()
+       {
+               return getCurve();
+       }
+
+
+       public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionInvalidRNATemplate
+       {
+               super.attach(e,edgeOrig,edgeDest);
+               if (e instanceof Helix)
+               {
+                       EdgeEndPoint e1 = this.getEndPoint(edgeOrig);
+                       EdgeEndPoint e2 = e.getEndPoint(edgeDest);
+                       boolean parity1 = this.isIn(edgeOrig);
+                       boolean parity2 = e.isIn(edgeDest);
+                       if ((e1!=null)&&(e2!=null)&&(parity1!=parity2))
+                       {
+                               e1.disconnect();
+                               e2.disconnect();
+                               e1.connectTo(e2);   
+                       }
+               }
+       }
+
+
+       public EdgeEndPoint getEndPoint(RelativePosition r) {
+               switch(r)
+               {
+               case RP_CONNECT_START5:
+                       return _e.getIn();                              
+               case RP_CONNECT_END3:
+                       return _e.getOut();
+               }
+               return null;
+       }
+
+       public boolean isIn(RelativePosition r) {
+               switch(r)
+               {
+               case RP_CONNECT_START5:
+                       return true;                            
+               case RP_CONNECT_END3:
+                       return false;
+               }
+               return true;
+       }
+
+       public void detach(RelativePosition edge)
+       {
+               // If the underlying template element is still connected, disconnect it
+               if (getEndPoint(edge).isConnected())
+               {
+                       Couple<GraphicalTemplateElement.RelativePosition, GraphicalTemplateElement> c = getAttachedElement(edge);
+                       getEndPoint(edge).disconnect();
+               }
+
+               // Call the parent class detach function, which will also take care to disconnect this other endpoint of this edge
+               super.detach(edge);
+       }
+
+       public void setEdgePosition(RelativePosition edge, Point2D.Double pos) {
+               switch(edge)
+               {
+               case RP_CONNECT_START5:
+                       setEdge5(pos);
+                       break;
+               case RP_INNER_MOVE:
+                       setCenter(pos);
+                       break;
+               case RP_CONNECT_END3:
+                       setEdge3(pos);
+                       break;
+               case RP_EDIT_TANGENT_5:
+                       updateControl5(pos);
+                       break;
+               case RP_EDIT_TANGENT_3:
+                       updateControl3(pos);
+                       break;
+               }
+       }
+
+       public ArrayList<RelativePosition> getConnectedEdges() {
+               ArrayList<RelativePosition> result = new ArrayList<RelativePosition>();
+               result.add(RelativePosition.RP_CONNECT_START5);
+               result.add(RelativePosition.RP_CONNECT_END3);
+               return result;
+       }
+
+       public RNATemplateElement getTemplateElement() {
+               return _e;
+       }
+
+
+       public RelativePosition relativePositionFromEdgeEndPointPosition(
+                       EdgeEndPointPosition pos) {
+               switch (pos) {
+               case IN1:
+                       return RelativePosition.RP_CONNECT_START5;
+               case OUT1:
+                       return RelativePosition.RP_CONNECT_END3;
+               default:
+                       return null;
+               }
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/components/ActionEditor.java b/srcjar/fr/orsay/lri/varna/components/ActionEditor.java
new file mode 100644 (file)
index 0000000..204024f
--- /dev/null
@@ -0,0 +1,54 @@
+package fr.orsay.lri.varna.components;
+
+import java.awt.Component;
+import java.awt.Event;
+import java.awt.event.ActionListener;
+import java.util.EventObject;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+
+public class ActionEditor extends AbstractCellEditor implements TableCellEditor { 
+
+       JButton _btn = new JButton();
+
+       public ActionEditor (ActionListener a) {
+         // add all elments you need to your panel
+         _btn.addActionListener(a);
+       }
+
+       public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int vColIndex) { 
+          _btn.setText(value.toString());
+          _btn.setActionCommand(value.toString()+"-"+rowIndex);
+          // set all elemnts of you panel to the according values
+          // or add dynamically an action listener
+          
+          return _btn;
+       }
+       public Object getCellEditorValue() 
+       { 
+               return ""; 
+       } 
+       
+       public boolean shouldSelectCell(EventObject anEvent)
+       {
+               return super.shouldSelectCell(anEvent);
+               
+       }
+
+       public boolean isCellEditable(EventObject anEvent)
+       {
+               return super.isCellEditable(anEvent);           
+       }
+       
+       public boolean stopCellEditing()
+       {
+               return super.stopCellEditing();
+       }
+
+       
+} 
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/components/ActionRenderer.java b/srcjar/fr/orsay/lri/varna/components/ActionRenderer.java
new file mode 100644 (file)
index 0000000..ee88270
--- /dev/null
@@ -0,0 +1,30 @@
+
+package fr.orsay.lri.varna.components;
+
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.table.TableCellRenderer;
+
+public class ActionRenderer extends JButton implements TableCellRenderer {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       public ActionRenderer() {
+       }
+
+       public Component getTableCellRendererComponent(JTable table, Object button,
+                       boolean isSelected, boolean hasFocus, int row, int column) {
+               this.setText(button.toString());
+               return this;
+       }
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/components/AnnotationTableModel.java b/srcjar/fr/orsay/lri/varna/components/AnnotationTableModel.java
new file mode 100644 (file)
index 0000000..c84f72e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.components;
+
+import java.util.ArrayList;
+
+import javax.swing.table.AbstractTableModel;
+
+public class AnnotationTableModel extends AbstractTableModel {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String[] columnNames = { "Annotation" };
+       private ArrayList<ArrayList<Object>> data = new ArrayList<ArrayList<Object>>();
+
+
+       public AnnotationTableModel(ArrayList<Object> annot) {
+               ArrayList<Object> ligne;
+               for (int i = 0; i < annot.size(); i++) {
+                       ligne = new ArrayList<Object>();
+                       ligne.add(annot.get(i));
+                       data.add(ligne);
+               }
+
+       }
+
+       public int getColumnCount() {
+               return columnNames.length;
+       }
+
+       public int getRowCount() {
+               return data.size();
+       }
+
+       public String getColumnName(int col) {
+               return columnNames[col];
+       }
+
+       public Object getValueAt(int row, int col) {
+               return data.get(row).get(col);
+       }
+
+       /*
+        * JTable uses this method to determine the default renderer/ editor for
+        * each cell. If we didn't implement this method, then the last column would
+        * contain text ("true"/"false"), rather than a check box.
+        */
+       @SuppressWarnings("unchecked")
+       public Class getColumnClass(int c) {
+               return getValueAt(0, c).getClass();
+       }
+
+       public boolean isCellEditable(int row, int col) {
+               // Note that the data/cell address is constant,
+               // no matter where the cell appears onscreen.
+               if (col < 1) {
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       public void setValueAt(Object value, int row, int col) {
+               data.get(row).set(col, value);
+               fireTableCellUpdated(row, col);
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/components/BaseSpecialColorEditor.java b/srcjar/fr/orsay/lri/varna/components/BaseSpecialColorEditor.java
new file mode 100644 (file)
index 0000000..d39d74e
--- /dev/null
@@ -0,0 +1,104 @@
+
+package fr.orsay.lri.varna.components;
+
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+
+import fr.orsay.lri.varna.controlers.ControleurBaseSpecialColorEditor;
+import fr.orsay.lri.varna.views.VueBases;
+
+public class BaseSpecialColorEditor extends AbstractCellEditor implements
+               TableCellEditor {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+       private Color currentColor;
+       private JButton button;
+       private JColorChooser colorChooser;
+       private JDialog dialog;
+       protected static final String EDIT = "edit";
+       private VueBases _vueBases;
+       private ControleurBaseSpecialColorEditor _controleurSpecialColorEditor;
+
+       public BaseSpecialColorEditor(VueBases vueBases) {
+               // Set up the editor (from the table's point of view),
+               // which is a button.
+               // This button brings up the color chooser dialog,
+               // which is the editor from the user's point of view.
+               button = new JButton();
+               button.setActionCommand(EDIT);
+               _controleurSpecialColorEditor = new ControleurBaseSpecialColorEditor(this);
+               button.addActionListener(_controleurSpecialColorEditor);
+               button.setBorderPainted(false);
+               fireEditingStopped();
+               _vueBases = vueBases;
+
+               // Set up the dialog that the button brings up.
+               colorChooser = new JColorChooser();
+               dialog = JColorChooser.createDialog(button, "Pick a Color", true, // modal
+                               colorChooser, _controleurSpecialColorEditor, // OK button
+                               // handler
+                               null); // no CANCEL button handler
+       }
+
+       // Implement the one CellEditor method that AbstractCellEditor doesn't.
+       public Object getCellEditorValue() {
+               return currentColor;
+       }
+
+       // Implement the one method defined by TableCellEditor.
+       public Component getTableCellEditorComponent(JTable table, Object value,
+                       boolean isSelected, int row, int column) {
+               currentColor = (Color) value;
+               return button;
+       }
+
+       public static long getSerialVersionUID() {
+               return serialVersionUID;
+       }
+
+       public Color getCurrentColor() {
+               return currentColor;
+       }
+
+       public JButton getButton() {
+               return button;
+       }
+
+       public JColorChooser getColorChooser() {
+               return colorChooser;
+       }
+
+       public JDialog getDialog() {
+               return dialog;
+       }
+
+       public static String getEDIT() {
+               return EDIT;
+       }
+
+       public VueBases get_vueBases() {
+               return _vueBases;
+       }
+
+       public ControleurBaseSpecialColorEditor get_controleurSpecialColorEditor() {
+               return _controleurSpecialColorEditor;
+       }
+
+       public void setCurrentColor(Color currentColor) {
+               this.currentColor = currentColor;
+       }
+
+       public void callFireEditingStopped() {
+               fireEditingStopped();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/components/BaseTableModel.java b/srcjar/fr/orsay/lri/varna/components/BaseTableModel.java
new file mode 100644 (file)
index 0000000..cf04d61
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.components;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import javax.swing.table.AbstractTableModel;
+
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+
+
+public class BaseTableModel extends AbstractTableModel {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String[] columnNames = { "Numbers", "Base", "Outline Color", "Inner Color",
+                       "Name Color", "Number Color" };
+       private ArrayList<ArrayList<Object>> data = new ArrayList<ArrayList<Object>>();
+       private ArrayList<BaseList> _bases;
+       private boolean _singleBases = true;
+
+       public BaseTableModel(ArrayList<BaseList> bases) {
+               _bases = bases;
+               ArrayList<Object> ligne;
+               for (int i = 0; i < bases.size(); i++) {
+                       ligne = new ArrayList<Object>();
+                       BaseList bl  = bases.get(i);
+                       if (bl.size()!=1)
+                       {
+                               _singleBases = false;
+                       }
+                       ligne.add(bl.getNumbers());
+                       ligne.add(bl.getContents());
+                       ligne.add(bl.getAverageOutlineColor());
+                       ligne.add(bl.getAverageInnerColor());
+                       ligne.add(bl.getAverageNameColor());
+                       ligne.add(bl.getAverageNumberColor());
+                       this.data.add(ligne);   
+               }
+
+       }
+
+       public int getColumnCount() {
+               return columnNames.length;
+       }
+
+       public int getRowCount() {
+               return data.size();
+       }
+
+       public String getColumnName(int col) {
+               return columnNames[col];
+       }
+
+       public Object getValueAt(int row, int col) {
+               return data.get(row).get(col);
+       }
+
+       /*
+        * JTable uses this method to determine the default renderer/ editor for
+        * each cell. If we didn't implement this method, then the last column would
+        * contain text ("true"/"false"), rather than a check box.
+        */
+       @SuppressWarnings("unchecked")
+       public Class getColumnClass(int c) {
+               return getValueAt(0, c).getClass();
+       }
+
+       public boolean isCellEditable(int row, int col) {
+               // Note that the data/cell address is constant,
+               // no matter where the cell appears onscreen.
+               if (col < 1) {
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       public void setValueAt(Object value, int row, int col) {
+               data.get(row).set(col, value);
+               if (col == 1 && _singleBases)
+               {
+                       ModeleBase mb = _bases.get(row).getBases().get(0);
+                       mb.setContent(value.toString());
+               }
+               fireTableCellUpdated(row, col);
+
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/components/ColorRenderer.java b/srcjar/fr/orsay/lri/varna/components/ColorRenderer.java
new file mode 100644 (file)
index 0000000..fa0c285
--- /dev/null
@@ -0,0 +1,52 @@
+
+package fr.orsay.lri.varna.components;
+
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.table.TableCellRenderer;
+
+public class ColorRenderer extends JLabel implements TableCellRenderer {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+       Border unselectedBorder = null;
+       Border selectedBorder = null;
+       boolean isBordered = true;
+
+       public ColorRenderer(boolean isBordered) {
+               this.isBordered = isBordered;
+               setOpaque(true); // MUST do this for background to show up.
+       }
+
+       public Component getTableCellRendererComponent(JTable table, Object color,
+                       boolean isSelected, boolean hasFocus, int row, int column) {
+               Color newColor = (Color) color;
+               setBackground(newColor);
+               if (isBordered) {
+                       if (isSelected) {
+                               if (selectedBorder == null) {
+                                       selectedBorder = BorderFactory.createMatteBorder(2, 5, 2,
+                                                       5, table.getSelectionBackground());
+                               }
+                               setBorder(selectedBorder);
+                       } else {
+                               if (unselectedBorder == null) {
+                                       unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2,
+                                                       5, table.getBackground());
+                               }
+                               setBorder(unselectedBorder);
+                       }
+               }
+
+               setToolTipText("RGB value: " + newColor.getRed() + ", "
+                               + newColor.getGreen() + ", " + newColor.getBlue());
+               return this;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/components/GradientEditorPanel.java b/srcjar/fr/orsay/lri/varna/components/GradientEditorPanel.java
new file mode 100644 (file)
index 0000000..f6d835a
--- /dev/null
@@ -0,0 +1,344 @@
+package fr.orsay.lri.varna.components;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import javax.swing.JColorChooser;
+import javax.swing.JPanel;
+
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+
+public class GradientEditorPanel extends JPanel implements MouseListener, MouseMotionListener{
+               ModeleColorMap _mcm;
+               
+               public GradientEditorPanel (ModeleColorMap mcm)
+               { 
+                       _mcm = mcm;
+                       this.addMouseListener( this);
+                       this.addMouseMotionListener(this);
+               }
+               
+               public void setColorMap(ModeleColorMap mcm)
+               {
+                       _mcm = mcm;
+                       repaint();
+                       firePropertyChange("PaletteChanged","a","b");
+               }
+               
+               public ModeleColorMap getColorMap()
+               {
+                       return _mcm;
+               }
+
+               private final static int TRIGGERS_SEMI_WIDTH = 2;
+               private final static int PALETTE_HEIGHT = 11;
+               private final static int REMOVE_HEIGHT = 11;
+               private final static int TOLERANCE = 5;
+               private final static int GAP = 4;
+               private final Color EDGES = Color.gray.brighter();
+               private final Color BUTTONS = Color.LIGHT_GRAY.brighter();
+               
+               public int getStartChoose()
+               {
+                       return getHeight()-PALETTE_HEIGHT-REMOVE_HEIGHT-GAP-1;
+               }
+               
+               public int getEndChoose()
+               {
+                       return getStartChoose()+PALETTE_HEIGHT;
+               }
+               
+               public int getStartRemove()
+               {
+                       return getEndChoose()+GAP;
+               }
+
+               public int getEndRemove()
+               {
+                       return getStartRemove()+REMOVE_HEIGHT;
+               }
+               
+               private int getStripeHeight()
+               {
+                       return getHeight()-PALETTE_HEIGHT-REMOVE_HEIGHT-2*GAP-1;
+               }
+               
+               private Color alterColor(Color c, int inc)
+               {
+                       int nr = Math.min(Math.max(c.getRed()+inc, 0),255);
+                       int ng = Math.min(Math.max(c.getGreen()+inc, 0),255);
+                       int nb = Math.min(Math.max(c.getBlue()+inc, 0),255);
+                       return new Color(nr,ng,nb);
+               }
+
+               public void paintComponent(Graphics g)
+               {
+                       super.paintComponent(g);
+                       Graphics2D g2d = (Graphics2D)  g;
+                       g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                       RenderingHints.VALUE_ANTIALIAS_ON);
+                       int height = getStripeHeight();
+                       double v1 = _mcm.getMinValue();
+                       double v2 = _mcm.getMaxValue();
+                       for (int i=0;i<=getWidth();i++)
+                       {
+                               double ratio = (((double)i)/((double)getWidth()));
+                               double val = v1+(v2-v1)*ratio;
+                               g2d.setColor(_mcm.getColorForValue(val));
+                               g2d.drawLine(i, 0, i, height);  
+                       }
+                       for(int i=0;i<_mcm.getNumColors();i++)
+                       {
+                               double val = _mcm.getValueAt(i);
+                               Color c = _mcm.getColorAt(i);
+                               double norm = (val-_mcm.getMinValue())/(_mcm.getMaxValue()-_mcm.getMinValue());
+                               int x = (int)(norm*(getWidth()-1));
+                               // Target
+                               g2d.setColor(c);
+                               g2d.fillRect(x-TRIGGERS_SEMI_WIDTH+1, 0, 2*TRIGGERS_SEMI_WIDTH-1, getHeight()-1);                                       
+                               g2d.setColor(EDGES);
+                               g2d.drawLine(x-TRIGGERS_SEMI_WIDTH, 0, x-TRIGGERS_SEMI_WIDTH, getHeight());                                     
+                               g2d.drawLine(x+TRIGGERS_SEMI_WIDTH, 0, x+TRIGGERS_SEMI_WIDTH, getHeight());
+                               
+                               if (i==0)
+                               {
+                                       // Choose Color
+                                       g2d.setColor(EDGES);
+                                       g2d.drawRect(x,height+GAP,PALETTE_HEIGHT,2*PALETTE_HEIGHT+GAP);                                 
+                                       g2d.setColor(c);
+                                       g2d.fillRect(x+1,height+GAP+1,PALETTE_HEIGHT-1,2*PALETTE_HEIGHT+GAP-1);
+                               }
+                               else if (i==_mcm.getNumColors()-1)
+                               {
+                                       // Choose Color
+                                       g2d.setColor(EDGES);
+                                       g2d.drawRect(x-PALETTE_HEIGHT,height+GAP,PALETTE_HEIGHT,2*PALETTE_HEIGHT+GAP);                                  
+                                       g2d.setColor(c);
+                                       g2d.fillRect(x-PALETTE_HEIGHT+1,height+GAP+1,PALETTE_HEIGHT-1,2*PALETTE_HEIGHT+GAP-1);                                  
+                               }
+                               else
+                               {
+                               // Choose Color
+                               g2d.setColor(EDGES);
+                               g2d.drawRect(x-PALETTE_HEIGHT/2,height+GAP,PALETTE_HEIGHT,PALETTE_HEIGHT);                                      
+                               g2d.setColor(alterColor(c,-15));
+                               g2d.fillRect(x-PALETTE_HEIGHT/2+1,height+GAP+1,PALETTE_HEIGHT-1,PALETTE_HEIGHT-1);                                      
+                               g2d.setColor(c);
+                               g2d.fillOval(x-PALETTE_HEIGHT/2+1,height+GAP+1,PALETTE_HEIGHT-1,PALETTE_HEIGHT-1);
+                               g2d.setColor(alterColor(c,10));
+                               g2d.fillOval(x-PALETTE_HEIGHT/2+1+2,height+GAP+1+2,PALETTE_HEIGHT-1-4,PALETTE_HEIGHT-1-4);
+
+
+                               // Remove Color
+                               g2d.setColor(EDGES);
+                               g2d.drawRect(x-PALETTE_HEIGHT/2,height+2*GAP+PALETTE_HEIGHT,REMOVE_HEIGHT,REMOVE_HEIGHT);                                       
+                               g2d.setColor(BUTTONS);
+                               g2d.fillRect(x-PALETTE_HEIGHT/2+1,height+2*GAP+1+PALETTE_HEIGHT,REMOVE_HEIGHT-1,REMOVE_HEIGHT-1);                                               
+                               int xcross1 = x-PALETTE_HEIGHT/2+2;
+                               int ycross1 = height+2*GAP+PALETTE_HEIGHT +2;
+                               int xcross2 = xcross1+REMOVE_HEIGHT-4;
+                               int ycross2 = ycross1+REMOVE_HEIGHT-4;
+                               g2d.setColor(Color.red);
+                               g2d.drawLine(xcross1, ycross1, xcross2 , ycross2);                              
+                               g2d.drawLine(xcross1, ycross2, xcross2 , ycross1);
+                               }
+                       }
+
+               }
+               
+               private boolean isChooseColor(int x, int y)
+               {
+                       if (_selectedIndex != -1)
+                       {
+                               if ((_selectedIndex ==0)||(_selectedIndex == _mcm.getNumColors()-1))
+                                       return (y<=getEndRemove() && y>=getStartChoose() && Math.abs(getXPos(_selectedIndex)-x)<=PALETTE_HEIGHT);
+                               if (y<=getEndChoose() && y>=getStartChoose())
+                               {
+                                       return Math.abs(getXPos(_selectedIndex)-x)<=PALETTE_HEIGHT/2;
+                               }
+                       }
+                       return false;
+               }
+               
+               private boolean isRemove(int x, int y)
+               {
+                       if (_selectedIndex != -1)
+                       {
+                               if ((_selectedIndex ==0)||(_selectedIndex == _mcm.getNumColors()-1))
+                                       return false;
+                               if (y<=getEndRemove() && y>=getStartRemove())
+                               {
+                                       return Math.abs(getXPos(_selectedIndex)-x)<=PALETTE_HEIGHT/2;
+                               }
+                       }
+                       return false;
+               }
+               
+               private int getXPos(int i)
+               {
+                       double val = _mcm.getValueAt(i);
+                       double norm = (val-_mcm.getMinValue())/(_mcm.getMaxValue()-_mcm.getMinValue());
+                       return (int)(norm*(getWidth()-1));                      
+               }
+
+               
+               private int locateSelectedIndex(int x, int y)
+               {
+                       double dist = Double.MAX_VALUE;
+                       int index = -1;
+                       for(int i=0;i<_mcm.getNumColors();i++)
+                       {
+                               int xp = getXPos(i);
+                               double tmpDist = Math.abs(x-xp);
+                               if (tmpDist<dist)
+                               {
+                                       index = i;
+                                       dist = tmpDist; 
+                               }
+                       }       
+                       return index;
+               }
+               
+               private int _selectedIndex = -1;
+               
+               public void mouseClicked(MouseEvent arg0) {
+                       _selectedIndex = locateSelectedIndex(arg0.getX(),arg0.getY());
+                       if (_selectedIndex!=-1)
+                       {
+                               if (isRemove(arg0.getX(),arg0.getY()))
+                               {
+                                       removeEntry(_selectedIndex);
+                               }
+                               else if (Math.abs(getXPos(_selectedIndex)-arg0.getX())>TOLERANCE)
+                               {
+                                       double val = _mcm.getMinValue()+ arg0.getX()*(_mcm.getMaxValue()-_mcm.getMinValue())/(getWidth()-1);
+                                       Color nc = JColorChooser.showDialog(this, "Choose new color" ,_mcm.getColorAt(_selectedIndex));
+                                       if (nc != null)
+                                       {
+                                               _mcm.addColor(val, nc);
+                                               repaint();
+                                               firePropertyChange("PaletteChanged","a","b");
+                                       }                                       
+                               }
+                       }
+               }
+
+               public void mouseEntered(MouseEvent arg0) {
+                       // TODO Auto-generated method stub
+                       
+               }
+
+               public void mouseExited(MouseEvent arg0) {
+                       // TODO Auto-generated method stub
+                       
+               }
+
+               public void mousePressed(MouseEvent arg0) {
+                       requestFocus();
+                       _selectedIndex = locateSelectedIndex(arg0.getX(),arg0.getY());
+                       if (_selectedIndex!=-1)
+                       {
+                               if (isChooseColor(arg0.getX(),arg0.getY()))
+                               {
+                                       Color nc = JColorChooser.showDialog(this, "Choose new color" ,_mcm.getColorAt(_selectedIndex));
+                                       if (nc != null)
+                                       {
+                                               double nv = _mcm.getValueAt(_selectedIndex);
+                                               replaceEntry(_selectedIndex, nc, nv);
+                                               _selectedIndex = -1;
+                                       }
+                               }
+                       }
+               }
+
+               public void mouseReleased(MouseEvent arg0) {
+                       _selectedIndex = -1;
+               }
+
+               private void replaceEntry(int index, Color nc, double nv)
+               {
+                       ModeleColorMap cm = new ModeleColorMap();
+                       for(int i=0;i<_mcm.getNumColors();i++)
+                       {
+                               if (i!=index)
+                               {
+                                       double val = _mcm.getValueAt(i);
+                                       Color c = _mcm.getColorAt(i);
+                                       cm.addColor(val, c);
+                               }
+                               else
+                               {
+                                       cm.addColor(nv, nc);
+                               }
+                       }
+                       _mcm = cm;
+                       repaint();
+                       firePropertyChange("PaletteChanged","a","b");
+               }
+
+               private void removeEntry(int index)
+               {
+                       ModeleColorMap cm = new ModeleColorMap();
+                       for(int i=0;i<_mcm.getNumColors();i++)
+                       {
+                               if (i!=index)
+                               {
+                                       double val = _mcm.getValueAt(i);
+                                       Color c = _mcm.getColorAt(i);
+                                       cm.addColor(val, c);
+                               }
+                       }
+                       _mcm = cm;
+                       repaint();
+                       firePropertyChange("PaletteChanged","a","b");
+               }
+               
+               
+               public void mouseDragged(MouseEvent arg0) {
+                       if ((_selectedIndex!=-1)&&(_selectedIndex!=0)&&(_selectedIndex!=_mcm.getNumColors()-1))
+                       {
+                               Color c = _mcm.getColorAt(_selectedIndex);
+                               double val = _mcm.getMinValue()+ arg0.getX()*(_mcm.getMaxValue()-_mcm.getMinValue())/(getWidth()-1);
+                               replaceEntry(_selectedIndex, c, val);
+                       }
+               }
+
+               public void mouseMoved(MouseEvent arg0) {
+                       Cursor c = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+                       _selectedIndex = locateSelectedIndex(arg0.getX(),arg0.getY());
+                       if (_selectedIndex!=-1)
+                       {
+                               if (isChooseColor(arg0.getX(),arg0.getY()))
+                               {
+                                       c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+                               }
+                               else if ((_selectedIndex != 0)&&(_selectedIndex != _mcm.getNumColors()-1))
+                               {
+                                       if (isRemove(arg0.getX(),arg0.getY()))
+                                       {
+                                               c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);                                     
+                                       }
+                                       else if (Math.abs(getXPos(_selectedIndex)-arg0.getX())<=TOLERANCE)
+                                       {
+                                               c = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
+                                       }
+                                       else if (arg0.getY()<getHeight()-this.getStripeHeight())
+                                       {
+                                               c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);                                                                             
+                                       }
+                                       else
+                                       {
+                                               Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+                                       }
+                               }
+                       }
+                       setCursor(c);
+               }
+       }
+
diff --git a/srcjar/fr/orsay/lri/varna/components/ReorderableJList.java b/srcjar/fr/orsay/lri/varna/components/ReorderableJList.java
new file mode 100644 (file)
index 0000000..791c451
--- /dev/null
@@ -0,0 +1,219 @@
+package fr.orsay.lri.varna.components;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragGestureRecognizer;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.io.IOException;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+
+public class ReorderableJList extends JList 
+implements DragSourceListener, DropTargetListener, DragGestureListener {
+
+static DataFlavor localObjectFlavor;
+static {
+    try {
+        localObjectFlavor =
+            new DataFlavor (DataFlavor.javaJVMLocalObjectMimeType);
+    } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } 
+} 
+static DataFlavor[] supportedFlavors = { localObjectFlavor }; 
+DragSource dragSource; 
+DropTarget dropTarget; 
+int dropTargetIndex; 
+int draggedIndex = -1;
+
+public ReorderableJList () {
+    super();
+    setCellRenderer (new ReorderableListCellRenderer());
+    setModel (new DefaultListModel());
+    dragSource = new DragSource();
+    DragGestureRecognizer dgr =
+        dragSource.createDefaultDragGestureRecognizer (this,
+                                   DnDConstants.ACTION_MOVE,
+                                                       this);
+    dropTarget = new DropTarget (this, this);
+}
+
+// DragGestureListener
+public void dragGestureRecognized (DragGestureEvent dge) {
+    //System.out.println ("dragGestureRecognized");
+    // find object at this x,y
+    Point clickPoint = dge.getDragOrigin();
+    int index = locationToIndex(clickPoint);
+    if (index == -1)
+        return;
+    Object target = getModel().getElementAt(index);
+    Transferable trans = new RJLTransferable (target);
+    draggedIndex = index;
+    dragSource.startDrag (dge,Cursor.getDefaultCursor(),
+                          trans, this);
+}
+// DragSourceListener events
+public void dragDropEnd (DragSourceDropEvent dsde) {
+   //System.out.println ("dragDropEnd()");
+   dropTargetIndex = -1;
+   draggedIndex = -1;
+   repaint();
+}
+public void dragEnter (DragSourceDragEvent dsde) {}
+public void dragExit (DragSourceEvent dse) {}
+public void dragOver (DragSourceDragEvent dsde) {}
+public void dropActionChanged (DragSourceDragEvent dsde) {}
+// DropTargetListener events
+public void dragEnter (DropTargetDragEvent dtde) {
+    //System.out.println ("dragEnter");
+    if (dtde.getSource() != dropTarget)
+        dtde.rejectDrag();
+    else {
+        dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+        //System.out.println ("accepted dragEnter");
+    }
+}
+public void dragExit (DropTargetEvent dte) {}
+
+public void dropActionChanged (DropTargetDragEvent dtde) {}
+
+public void dragOver (DropTargetDragEvent dtde) { 
+    // figure out which cell it's over, no drag to self    
+    if (dtde.getSource() != dropTarget)
+        dtde.rejectDrag();
+    Point dragPoint = dtde.getLocation();
+    int index = locationToIndex (dragPoint);
+    dropTargetIndex = index;
+    if (dropTargetIndex != -1)
+    {
+       Rectangle r = getCellBounds(dropTargetIndex,dropTargetIndex);
+        if (dragPoint.y > r.y+r.height/2)
+        {
+               dropTargetIndex += 1;           
+        }
+    }
+    //System.out.println(dropTargetIndex);
+    repaint(); 
+}
+
+public void drop (DropTargetDropEvent dtde) {    
+    //System.out.println ("drop()!");    
+    if (dtde.getSource() != dropTarget) {
+        //System.out.println ("rejecting for bad source (" +
+        //                    dtde.getSource().getClass().getName() + ")");        
+        dtde.rejectDrop(); 
+        return;
+    }
+    Point dropPoint = dtde.getLocation();
+    int index = locationToIndex (dropPoint);
+    if (index != -1)
+    {
+       Rectangle r = getCellBounds(index,index);
+        if (dropPoint.y > r.y+r.height/2)
+        {
+               index += 1;             
+        }
+    }
+    
+    //System.out.println ("drop index is " + index);
+    boolean dropped = false;
+    try {
+        if ((index == -1) || (index == draggedIndex)|| (index == draggedIndex+1)) {
+            //System.out.println ("dropped onto self");
+            dtde.rejectDrop();
+            return;
+        }
+        dtde.acceptDrop (DnDConstants.ACTION_MOVE);
+        //System.out.println ("accepted");
+        Object dragged =
+           dtde.getTransferable().getTransferData(localObjectFlavor);
+        // move items - note that indicies for insert will 
+        // change if [removed] source was before target 
+        //System.out.println ("drop " + draggedIndex + " to " + index);
+        boolean sourceBeforeTarget = (draggedIndex < index); 
+        //System.out.println ("source is" +
+        //                    (sourceBeforeTarget ? "" : " not") +
+        //                    " before target");
+        //System.out.println ("insert at " +
+        //                    (sourceBeforeTarget ? index-1 : index));
+         DefaultListModel mod = (DefaultListModel) getModel(); 
+        mod.remove (draggedIndex); 
+        mod.add ((sourceBeforeTarget ? index-1 : index), dragged); 
+        dropped = true;
+    } catch (Exception e) {
+        e.printStackTrace();
+    }
+    dtde.dropComplete (dropped);
+}
+
+private class ReorderableListCellRenderer 
+extends DefaultListCellRenderer { 
+boolean isTargetCell; 
+boolean isLastItem;    
+public ReorderableListCellRenderer() {
+    super();
+}
+public Component getListCellRendererComponent (JList list,
+                                               Object value, 
+                                               int index, 
+                                               boolean isSelected,                                                  boolean hasFocus) {
+    isTargetCell = (index == dropTargetIndex);
+    isLastItem = (index == list.getModel().getSize()-1) && (dropTargetIndex == list.getModel().getSize());
+    boolean showSelected = isSelected;
+    return super.getListCellRendererComponent (list, value, 
+                                               index, showSelected,                                                 
+                                               hasFocus);
+}
+public void paintComponent (Graphics g) {
+    super.paintComponent(g);
+    if (isTargetCell) {
+        g.setColor(Color.black);            
+        g.drawLine (0, 0, getSize().width, 0); 
+    } 
+    if (isLastItem) {
+        g.setColor(Color.black);            
+        g.drawLine (0, getSize().height-1, getSize().width, getSize().height-1); 
+    } 
+ } 
+}
+
+private class RJLTransferable implements Transferable { 
+    Object object; 
+    public RJLTransferable (Object o) {
+        object = o;
+    }
+    public Object getTransferData(DataFlavor df)
+        throws UnsupportedFlavorException, IOException {
+        if (isDataFlavorSupported (df))
+            return object;
+        else
+           throw new UnsupportedFlavorException(df);
+    }
+    public boolean isDataFlavorSupported (DataFlavor df) {
+        return (df.equals (localObjectFlavor));
+    }
+    public DataFlavor[] getTransferDataFlavors () {
+        return supportedFlavors; } 
+}
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/components/VARNAConsole.java b/srcjar/fr/orsay/lri/varna/components/VARNAConsole.java
new file mode 100644 (file)
index 0000000..2b8b8a9
--- /dev/null
@@ -0,0 +1,119 @@
+package fr.orsay.lri.varna.components;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JEditorPane;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurScriptParser;
+import fr.orsay.lri.varna.models.VARNAConfig;
+
+public class VARNAConsole extends JFrame implements ActionListener, FocusListener, KeyListener {
+
+  private VARNAPanel _vp;
+       
+  private JButton _quitButton; 
+  private JPanel _contentPanel; 
+  private JPanel _quitPanel; 
+  private JTextField _input; 
+  private JEditorPane _output; 
+  private JScrollPane _scrolls; 
+       
+  public VARNAConsole(VARNAPanel vp)
+  {
+         _vp = vp;
+         init();
+  }
+  
+  private void init()
+  {
+         _quitButton = new JButton("Exit");
+         _quitPanel = new JPanel();
+         _contentPanel = new JPanel();
+         _input = new JTextField("Your command here...");
+         _output = new JEditorPane();
+         _scrolls = new JScrollPane(_output);
+
+         _input.addFocusListener(this);
+         _input.addKeyListener(this);
+         
+         _output.setText(VARNAConfig.getFullName()+" console\n");
+         _output.setPreferredSize(new Dimension(500,300));
+         _output.setEditable(false);
+         
+         _quitPanel.add(_quitButton);
+         
+         _quitButton.addActionListener(this);
+         
+         _contentPanel.setLayout(new BorderLayout());
+         _contentPanel.add(_scrolls,BorderLayout.CENTER);
+         _contentPanel.add(_input,BorderLayout.SOUTH);
+
+         getContentPane().setLayout(new BorderLayout());
+         getContentPane().add(_contentPanel,BorderLayout.CENTER);
+         getContentPane().add(_quitPanel,BorderLayout.SOUTH);
+         
+         pack();
+  }
+
+public void actionPerformed(ActionEvent arg0) {
+       setVisible(false);
+}
+
+private boolean _firstFocus = true;
+
+public void focusGained(FocusEvent arg0) {
+       if (_firstFocus)
+       {
+               _input.setSelectionStart(0);
+               _input.setSelectionEnd(_input.getText().length());
+               _firstFocus = false;
+       }
+}
+
+public void focusLost(FocusEvent arg0) {
+       // TODO Auto-generated method stub
+       
+}
+
+public void keyPressed(KeyEvent arg0) {
+       // TODO Auto-generated method stub
+       
+}
+
+public void keyReleased(KeyEvent arg0) {
+       // TODO Auto-generated method stub
+       
+}
+
+public void keyTyped(KeyEvent arg0) {
+       // TODO Auto-generated method stub
+       char c = arg0.getKeyChar();
+       if (c=='\n')
+       {
+               try {
+                       ControleurScriptParser.executeScript(_vp,_input.getText());
+               } catch (Exception e) {
+                       _output.setText(_output.getText()+e.getMessage()+'\n');
+                       e.printStackTrace();
+               }
+       }
+}
+  
+  
+  
+}
diff --git a/srcjar/fr/orsay/lri/varna/components/ZoomWindow.java b/srcjar/fr/orsay/lri/varna/components/ZoomWindow.java
new file mode 100644 (file)
index 0000000..4003e69
--- /dev/null
@@ -0,0 +1,185 @@
+package fr.orsay.lri.varna.components;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D.Double;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+
+import javax.swing.JPanel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.export.SwingGraphics;
+import fr.orsay.lri.varna.models.export.VueVARNAGraphics;
+
+public class ZoomWindow extends JPanel implements ImageObserver, Runnable, MouseMotionListener, MouseListener {
+
+       VARNAPanel _vp = null;
+       BufferedImage _bi = null;
+       Rectangle2D.Double rnaRect = null;
+       
+       public ZoomWindow(VARNAPanel vp)
+       {
+               _vp = vp;
+               addMouseMotionListener(this);
+               addMouseListener(this);
+       }
+       
+       
+       public synchronized void setPanel(VARNAPanel vp)
+       {
+               _vp = vp; 
+       }
+       
+       
+       
+       public synchronized void drawPanel()
+       {
+               if (getWidth()>0 && getHeight()>0)
+               {
+                       _bi= new BufferedImage(getWidth(),getHeight(),BufferedImage.TYPE_4BYTE_ABGR);
+                       Graphics2D g2 = _bi.createGraphics();
+                       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                               RenderingHints.VALUE_ANTIALIAS_ON);
+                       VueVARNAGraphics g2D = new SwingGraphics(g2);
+                       rnaRect =_vp.renderRNA(g2D,new Rectangle2D.Double(0,0,getWidth(),getHeight()),false,true);
+                       
+                       Point2D.Double p1 = _vp.panelToLogicPoint(new Point2D.Double(0.,0.));
+                       Point2D.Double p2 = _vp.panelToLogicPoint(new Point2D.Double(_vp.getWidth(),_vp.getHeight()));
+                       
+                       double w = p2.x-p1.x; 
+                       double h = p2.y-p1.y;
+                                               
+                       Rectangle2D.Double rnaBox = _vp.getRNA().getBBox();
+                       
+                       double ratiox = w/rnaBox.width;
+                       double ratioy = h/rnaBox.height;
+                       
+                       Rectangle2D.Double rvisible = new Rectangle2D.Double(rnaRect.x+rnaRect.width*(double)(p1.x-rnaBox.x)/(double)rnaBox.width,
+                                       rnaRect.y+rnaRect.height*(double)(p1.y-rnaBox.y)/(double)rnaBox.height,
+                                       ratiox*rnaRect.width,
+                                       ratioy*rnaRect.height);
+                       
+                       //g2D.drawRect(rleft.x,rleft.y,rleft.width,rleft.height);
+                       
+                       Color shade = new Color(.9f,.9f,.9f,.4f);
+                       
+                       g2.setStroke(new BasicStroke(1.0f));
+
+                       g2.setColor(shade);
+                       
+                       /*Polygon north = new Polygon(new int[]{0,getWidth(),(int)rvisible.x,
+                                       (int)(rvisible.x+rvisible.width+1),},new int[]{},1);*/
+                       g2.fillRect(0,0,getWidth(),(int)rvisible.y);
+                       g2.fillRect(0,(int)rvisible.y,(int)rvisible.x,(int)rvisible.height+1);
+                       g2.fillRect((int)(rvisible.x+rvisible.width),(int)rvisible.y,(int)(getHeight()-(rvisible.x+rvisible.width)),(int)(rvisible.height+1));
+                       g2.fillRect(0,(int)(rvisible.y+rvisible.height),getWidth(),(int)(getHeight()-(rvisible.y+rvisible.height)));
+
+                       g2.setColor(new Color(.7f,.7f,.7f,.3f));
+                       g2.draw(rvisible);
+                       g2.drawLine(0,
+                                       0,
+                                       (int)rvisible.x,
+                                       (int)rvisible.y);
+                       g2D.drawLine(getWidth(),
+                                       0,
+                                       rvisible.x+rvisible.width,
+                                       rvisible.y);
+                       g2D.drawLine(getWidth(),
+                                       getHeight(),
+                                       rvisible.x+rvisible.width,
+                                       rvisible.y+rvisible.height);
+                       g2D.drawLine(0,
+                                       getHeight(),
+                                       rvisible.x,
+                                       rvisible.y+rvisible.height);
+
+                       g2.dispose();
+               }
+       }
+       
+       public void paintComponent(Graphics g)
+       {
+               setBackground(_vp.getBackground());
+               super.paintComponent(g);
+               drawPanel();
+               if (_bi!=null)
+               {
+                       g.drawImage(_bi,0,0,this);
+               }
+       }
+
+       public void run() {
+               while(true)
+               {
+                       repaint();
+                       try {
+                               Thread.sleep(500);
+                       } catch (InterruptedException e) {
+                               e.printStackTrace();
+                       }
+                       
+               }
+       }
+
+
+       public void mouseDragged(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void mouseMoved(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void mouseClicked(MouseEvent e) {
+               /*if (rnaRect!=null)
+               {
+                       int x= e.getX();
+                       int y= e.getY();
+                       double ratioX = ((double)(x-rnaRect.getMinX())/((double)rnaRect.width));
+                       double ratioY = ((double)(y-rnaRect.getMinY())/((double)rnaRect.height));
+                       _vp.centerViewOn(ratioX,ratioY );
+               }*/
+       }
+
+
+       public void mousePressed(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void mouseReleased(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void mouseEntered(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       public void mouseExited(MouseEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurBPHeightIncrement.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurBPHeightIncrement.java
new file mode 100644 (file)
index 0000000..87866e1
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueBPHeightIncrement;
+
+
+public class ControleurBPHeightIncrement implements ChangeListener {
+
+       private VueBPHeightIncrement _vsbb;
+
+       public ControleurBPHeightIncrement(VueBPHeightIncrement vsbb) {
+               _vsbb = vsbb;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vsbb.get_vp().setBPHeightIncrement(_vsbb.getIncrement());
+               _vsbb.get_vp().drawRNA();
+               _vsbb.get_vp().repaint();
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurBaseSpecialColorEditor.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurBaseSpecialColorEditor.java
new file mode 100644 (file)
index 0000000..b0700cc
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+
+import fr.orsay.lri.varna.components.BaseSpecialColorEditor;
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+/**
+ * BH SwingJS - additions here to allow asynchronous JColorChooser  
+ */
+public class ControleurBaseSpecialColorEditor implements ActionListener, ComponentListener {  // BH SwingJS
+
+       private BaseSpecialColorEditor _specialColorEditor;
+
+       private int _selectedRow;
+       private int _selectedCol;
+       private Color _selectedColor;
+       private String _selectedColTitle;
+
+       public ControleurBaseSpecialColorEditor(BaseSpecialColorEditor specialColorEditor) {
+               _specialColorEditor = specialColorEditor;
+       }
+
+       /**
+        * Handles events from the editor button and from the dialog's OK button.
+        */
+       public void actionPerformed(ActionEvent e) {
+               if (BaseSpecialColorEditor.getEDIT().equals(e.getActionCommand())) {
+                       // The user has clicked the cell, so
+                       // bring up the dialog.
+                       _specialColorEditor.getButton().setBackground(
+                                       _specialColorEditor.getCurrentColor());
+                       _specialColorEditor.getColorChooser().setColor(
+                                       _specialColorEditor.getCurrentColor());
+                       
+                       // BH SwingJS in JavaScript, this is not modal. 
+                       // We have to set a callback to stop the editing.
+                       
+                       _specialColorEditor.getDialog().removeComponentListener(this);
+                       _specialColorEditor.getDialog().addComponentListener(this);
+                       _specialColorEditor.getDialog().setVisible(true);
+                       
+                       
+                       // Make the renderer reappear.
+                       // BH SwingJS not so fast...
+                       //_specialColorEditor.callFireEditingStopped();
+
+               } else { // User pressed dialog's "OK" button.
+                       _specialColorEditor.setCurrentColor(_specialColorEditor
+                                       .getColorChooser().getColor());
+
+                       _selectedRow = _specialColorEditor.get_vueBases().getTable()
+                                       .getSelectedRow();
+
+                       _selectedCol = _specialColorEditor.get_vueBases().getTable()
+                                       .getSelectedColumn();
+
+                       _selectedColor = _specialColorEditor.getCurrentColor();
+
+                       _selectedColTitle = _specialColorEditor.get_vueBases()
+                                       .getSpecialTableModel().getColumnName(_selectedCol);
+                       BaseList lb = _specialColorEditor.get_vueBases().getDataAt(_selectedRow);
+                       for(ModeleBase mb: lb.getBases())
+                       {
+                               applyColor(_selectedColTitle, _selectedColor,mb);
+                       }
+                       _specialColorEditor.get_vueBases().get_vp().repaint();
+               }
+       }
+
+
+       private void applyColor(String titreCol, Color couleur, ModeleBase mb) {
+               if (titreCol.equals("Inner Color")) {
+                       mb.getStyleBase()
+                                       .setBaseInnerColor(couleur);
+               } else if (titreCol.equals("Outline Color")) {
+                       mb.getStyleBase()
+                                       .setBaseOutlineColor(couleur);
+               } else if (titreCol.equals("Name Color")) {
+                       mb.getStyleBase()
+                                       .setBaseNameColor(couleur);
+               } else if (titreCol.equals("Number Color")) {
+                       mb.getStyleBase()
+                                       .setBaseNumberColor(couleur);
+               }
+
+       }
+
+
+       @Override
+       public void componentResized(ComponentEvent e) {
+       }
+
+       @Override
+       public void componentMoved(ComponentEvent e) {
+       }
+
+       @Override
+       public void componentShown(ComponentEvent e) {
+       }
+
+       @Override
+       public void componentHidden(ComponentEvent e) {
+               
+               _specialColorEditor.callFireEditingStopped(); // BH SwingJS -- need to catch this
+               
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurBlinkingThread.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurBlinkingThread.java
new file mode 100644 (file)
index 0000000..c01619d
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Timer;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+/**
+ * BH SwingJS converted to Timer mechanism for compatibility with JavaScript 
+ *
+ */
+public class ControleurBlinkingThread extends Thread implements ActionListener {
+       public static final long DEFAULT_FREQUENCY = 50;
+       private long _period;
+       private VARNAPanel _parent;
+       private double _minVal, _maxVal, _val, _incr;
+       private boolean _increasing = true;
+       private boolean _active = false;
+
+       public ControleurBlinkingThread(VARNAPanel vp) {
+               this(vp, DEFAULT_FREQUENCY, 0, 1.0, 0.0, 0.2);
+       }
+
+       public ControleurBlinkingThread(VARNAPanel vp, long period, double minVal,
+                       double maxVal, double val, double incr) {
+               _parent = vp;
+               _period = period;
+               _minVal = minVal;
+               _maxVal = maxVal;
+               _incr = incr;
+       }
+
+       public void setActive(boolean b) {
+               if (_active == b)
+               {}
+               else
+               {
+               _active = b;
+               if (_active) {
+                       interrupt();
+               }
+               }
+       }
+
+       public boolean getActive() {
+               return _active;
+       }
+       
+       
+       public double getVal() {
+               return _val;
+       }
+
+       protected final int START = 0;
+       protected final int LOOP = 1;
+       protected final int STOP = -1;
+       
+       protected int nextMode = START;
+       private Timer timer;
+       
+       
+       public void interrupt() {
+               super.interrupt();
+               stopTimer();
+               run();
+       }
+       
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               run();
+       }
+       public void run() {
+       //   same as:
+       //                      while (true) {
+       //                      try {
+       //                              if (_active) {
+       //                                      sleep(_period);
+       //                                      if (_increasing) {
+       //                                              _val = Math.min(_val + _incr, _maxVal);
+       //                                              if (_val == _maxVal) {
+       //                                                      _increasing = false;
+       //                                              }
+       //                                      } else {
+       //                                              _val = Math.max(_val - _incr, _minVal);
+       //                                              if (_val == _minVal) {
+       //                                                      _increasing = true;
+       //                                              }
+       //                                      }
+       //                                      _parent.repaint();
+       //                              } else {
+       //                                      sleep(10000);
+       //                              }
+       //                      } catch (InterruptedException e) {
+       //                      }
+       //              }
+               long delay = 0;
+               while (true) { 
+                       try {
+                               switch (nextMode) {
+                               case START:
+                                       if (_active) {
+                                               delay = _period;
+                                               nextMode = LOOP;
+                                       } else {
+                                               delay = 10000;
+                                               nextMode = START;
+                                       }
+                                       startTimer(delay);
+                                       return;
+                               case STOP:
+                                       break;
+                               case LOOP:
+                                       if (_increasing) {
+                                               _val = Math.min(_val + _incr, _maxVal);
+                                               if (_val == _maxVal) {
+                                                       _increasing = false;
+                                               }
+                                       } else {
+                                               _val = Math.max(_val - _incr, _minVal);
+                                               if (_val == _minVal) {
+                                                       _increasing = true;
+                                               }
+                                       }
+                                       _parent.repaint();
+                                       nextMode = START;
+                                       continue;
+                               }
+                               sleep(0);
+                       } catch (InterruptedException e) {
+                               // ignore??
+                       }
+                       break;
+                       }
+       }
+
+       private void startTimer(long delay) {
+               stopTimer();
+               timer = new Timer((int) delay, this);
+               timer.setRepeats(false);
+               timer.start();
+       }
+
+       private void stopTimer() {
+               if (timer != null) {
+                       timer.stop();
+                       timer = null;
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurBorder.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurBorder.java
new file mode 100644 (file)
index 0000000..dd54466
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Dimension;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueBorder;
+
+
+public class ControleurBorder implements ChangeListener {
+
+       private VueBorder _vb;
+
+       public ControleurBorder(VueBorder vb) {
+               _vb = vb;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               if (_vb.getDimension().getHeight() < _vb.get_vp().getHeight()
+                               && _vb.getDimension().getWidth() < _vb.get_vp().getWidth()) {
+                       _vb.get_vp().setBorderSize(_vb.getDimension());
+                       _vb.get_vp().setMinimumSize(
+                                       new Dimension(_vb.get_vp().getBorderSize().width * 2, _vb
+                                                       .get_vp().getBorderSize().height * 2));
+                       _vb.get_vp().repaint();
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurClicMovement.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurClicMovement.java
new file mode 100644 (file)
index 0000000..4212106
--- /dev/null
@@ -0,0 +1,688 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBasesComparison;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+/**
+ * Controller of the mouse click
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurClicMovement implements MouseListener,
+               MouseMotionListener, PopupMenuListener {
+       private VARNAPanel _vp;
+       private boolean _presenceMenuSelection;
+       private JMenu _submenuSelection;
+       public Point _spawnPoint;       
+       public Point _initialPoint;
+       public Point _prevPoint;
+       public Point _currentPoint;
+
+       public static final double MIN_SELECTION_DISTANCE = 40.0;
+       public static final double HYSTERESIS_DISTANCE = 10.0;
+       
+       private ModeleBase _selectedBase = null;
+       
+       
+       public enum MouseStates {
+               NONE,
+               MOVE_ELEMENT,
+               MOVE_OR_SELECT_ELEMENT,
+               SELECT_ELEMENT,
+               SELECT_REGION_OR_UNSELECT,
+               SELECT_REGION,
+               CREATE_BP,
+               POPUP_MENU,
+               MOVE_ANNOTATION,
+       };
+       private MouseStates _currentState = MouseStates.NONE;
+       
+
+
+       public ControleurClicMovement(VARNAPanel _vuep) {
+               _vp = _vuep;
+               _vp.getPopup().addPopupMenuListener(this);
+               _presenceMenuSelection = false;
+       }
+
+       public void mouseClicked(MouseEvent arg0) {
+       }
+
+       public void mouseEntered(MouseEvent arg0) {
+       }
+
+       public void mouseExited(MouseEvent arg0) {
+       }
+
+       public void mousePressed(MouseEvent arg0) 
+       {
+               _vp.requestFocus();
+               boolean button1 = (arg0.getButton() == MouseEvent.BUTTON1);
+               boolean button2 = (arg0.getButton() == MouseEvent.BUTTON2);
+               boolean button3 = (arg0.getButton() == MouseEvent.BUTTON3);
+               boolean shift = arg0.isShiftDown();
+               boolean ctrl = arg0.isControlDown();
+               boolean alt = arg0.isAltDown();
+               _vp.removeSelectedAnnotation();
+               if (button1 && !ctrl && !alt && !shift)
+               {
+                       if (_vp.isModifiable()) 
+                       {
+                               _currentState = MouseStates.MOVE_OR_SELECT_ELEMENT;
+                               if (_vp.getRealCoords() != null
+                                               && _vp.getRealCoords().length != 0
+                                               && _vp.getRNA().get_listeBases().size() != 0) 
+                               {
+                                       _selectedBase = _vp.getNearestBase(arg0.getX(),arg0.getY(),false,false);
+                                       TextAnnotation selectedAnnotation = _vp.getNearestAnnotation(arg0.getX(), arg0.getY());
+                                       _initialPoint = new Point(arg0.getX(),arg0.getY());
+                                       _currentPoint = new Point(_initialPoint);
+                                       _prevPoint = new Point(_initialPoint);
+                                       if (_selectedBase != null)
+                                       { 
+                                               if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) 
+                                               {
+                                                       _vp.highlightSelectedBase(_selectedBase);
+                                               } else {
+                                                       if (!_vp.getSelectionIndices().contains(_selectedBase.getIndex()))
+                                                       {
+                                                               _vp.highlightSelectedBase(_selectedBase);
+                                                       }
+                                                       else
+                                                       {
+                                                         // Otherwise, keep current selection as it is and move it
+                                                       }
+                                               }
+                                       }
+                                       else
+                                       {
+                                               if (selectedAnnotation != null)
+                                               {
+                                                       _currentState = MouseStates.MOVE_ANNOTATION;
+                                                       _vp.set_selectedAnnotation(selectedAnnotation);
+                                                       _vp.highlightSelectedAnnotation();
+                                               }
+                                               else
+                                               {
+                                                       _vp.clearSelection();
+                                                       _selectedBase = null;
+                                                       _currentState = MouseStates.SELECT_REGION_OR_UNSELECT;
+                                                       _initialPoint = new Point(arg0.getX(),arg0.getY());
+                                                       _prevPoint = new Point(_initialPoint);
+                                                       _currentPoint = new Point(_initialPoint);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               else if (button1 && ctrl && !alt && !shift)
+               {
+                       _selectedBase = _vp.getNearestBase(arg0.getX(),arg0.getY(),false,false);
+                       if (_selectedBase != null)
+                       { 
+                               _vp.clearSelection();
+                               _currentState = MouseStates.CREATE_BP;
+                               _vp.highlightSelectedBase(_selectedBase);
+                               _vp.setOriginLink(_vp.logicToPanel(_selectedBase.getCoords()));
+                               _initialPoint = new Point(arg0.getX(),arg0.getY());
+                               _currentPoint = new Point(_initialPoint);
+                       }
+               }
+               else if (button1 && !ctrl && !alt && shift)
+               {
+                       _currentState = MouseStates.SELECT_ELEMENT;
+                       _initialPoint = new Point(arg0.getX(),arg0.getY());
+                       _currentPoint = new Point(_initialPoint);
+               }
+               else if (button3) 
+               {
+                       _currentState = MouseStates.POPUP_MENU;
+                       if (_presenceMenuSelection) {
+                               _vp.getPopupMenu().removeSelectionMenu();
+                       }
+                       if ((_vp.getRealCoords() != null) && _vp.getRNA().get_listeBases().size() != 0) {
+                               updateNearestBase(arg0);
+                               // on insere dans le menu les nouvelles options
+                               addMenu(arg0);
+                               if (_vp.get_selectedAnnotation() != null)
+                                       _vp.highlightSelectedAnnotation();
+                       }
+                       // affichage du popup menu
+                       if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_LINEAR) {
+                               _vp.getPopup().get_rotation().setEnabled(false);
+                       } else {
+                               _vp.getPopup().get_rotation().setEnabled(true);
+                       }
+                       _vp.getPopup().updateDialog();
+                       _vp.getPopup().show(_vp, arg0.getX(), arg0.getY());
+               }
+               _vp.repaint();
+       }
+
+       public void mouseDragged(MouseEvent me) {
+               if ((_currentState == MouseStates.MOVE_OR_SELECT_ELEMENT)||(_currentState == MouseStates.MOVE_ELEMENT))
+               {
+                       _vp.lockScrolling();
+
+                       _currentState = MouseStates.MOVE_ELEMENT;
+                               // si on deplace la souris et qu'une base est selectionnée
+                               if (_selectedBase != null) {
+                                       if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) {
+                                               _vp.highlightSelectedStem(_selectedBase);
+                                               // dans le cas radiale on deplace une helice
+                                               _vp.getVARNAUI().UIMoveHelixAtom(_selectedBase.getIndex(), _vp.panelToLogicPoint(new Point2D.Double(me.getX(), me.getY())));
+                                       } else {
+                                               // dans le cas circulaire naview ou line on deplace une base
+                                               _currentPoint = new Point(me.getX(), me.getY());
+                                               moveSelection(_prevPoint,_currentPoint);
+                                               _prevPoint = new Point(_currentPoint);
+                                       }
+                                       _vp.repaint();
+                               }
+               }
+               else if (_currentState == MouseStates.MOVE_ANNOTATION)
+               {
+                       if (_vp.get_selectedAnnotation()!=null)
+                       {
+                               Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(me.getX(), me.getY()));
+                               _vp.get_selectedAnnotation().setAncrage(p.x,p.y);
+                               _vp.repaint();
+                       }                       
+               }
+               else if ((_currentState == MouseStates.SELECT_ELEMENT)||(_currentState == MouseStates.SELECT_REGION_OR_UNSELECT))
+               {
+                       if (_initialPoint.distance(me.getX(),me.getY())>HYSTERESIS_DISTANCE)
+                               _currentState = MouseStates.SELECT_REGION;
+               }
+               else if (_currentState == MouseStates.SELECT_REGION)
+               {
+                       _currentPoint = new Point(me.getX(),me.getY());
+                       int minx = Math.min(_currentPoint.x, _initialPoint.x);
+                       int miny = Math.min(_currentPoint.y, _initialPoint.y);
+                       int maxx = Math.max(_currentPoint.x, _initialPoint.x);
+                       int maxy = Math.max(_currentPoint.y, _initialPoint.y);
+                       _vp.setSelectionRectangle(new Rectangle(minx,miny,maxx-minx,maxy-miny));
+               }
+               else if (_currentState == MouseStates.CREATE_BP)
+               {
+                       if (_initialPoint.distance(me.getX(),me.getY())>HYSTERESIS_DISTANCE)
+                       {
+                               ModeleBase newSelectedBase = _vp.getNearestBase(me.getX(),me.getY(),false,false);
+                               _vp.setHoverBase(newSelectedBase);
+                               if (newSelectedBase==null)
+                               {
+                                       _vp.setDestinationLink(new Point2D.Double(me.getX(),me.getY()));
+                                       _vp.clearSelection();
+                                       _vp.addToSelection(_selectedBase.getIndex());
+                               }
+                               else
+                               {
+                                       ModeleBase mborig = _selectedBase;
+                                       _vp.clearSelection();
+                                       _vp.addToSelection(newSelectedBase.getIndex());
+                                       _vp.addToSelection(mborig.getIndex());
+                                       _vp.setDestinationLink(_vp.logicToPanel(newSelectedBase.getCoords()));
+                               }
+                               _vp.repaint();
+                       }
+                       
+               }
+       }
+
+
+       public void mouseReleased(MouseEvent arg0) {
+               if (arg0.getButton() == MouseEvent.BUTTON1)
+               {
+                       _vp.fireBaseClicked(_selectedBase, arg0);       
+                       //System.out.println(""+_currentState);
+                       
+                       if (_currentState == MouseStates.MOVE_ELEMENT)
+                       {
+                               _vp.clearSelection();
+                               _selectedBase = null;
+                               _vp.unlockScrolling();
+                               _vp.removeSelectedAnnotation();
+                       }
+                       else if (_currentState == MouseStates.SELECT_REGION_OR_UNSELECT)
+                       {
+                               _vp.clearSelection();
+                               _selectedBase = null;
+                               _vp.removeSelectedAnnotation();
+                       }
+                       else if (_currentState == MouseStates.SELECT_ELEMENT)
+                       {
+                               if (_vp.getRealCoords() != null
+                                               && _vp.getRealCoords().length != 0
+                                               && _vp.getRNA().get_listeBases().size() != 0) 
+                                       {
+                                               int selectedIndex = _vp.getNearestBaseIndex(arg0.getX(),arg0.getY(),false,false);
+                                               if (selectedIndex !=-1)
+                                               { 
+                                                       _vp.toggleSelection(selectedIndex);
+                                               }
+                                       }
+                               _selectedBase = null;
+                       }
+                       else if (_currentState == MouseStates.SELECT_REGION)
+                       {
+                         _vp.removeSelectionRectangle();
+                       }
+                       else if (_currentState == MouseStates.CREATE_BP)
+                       {
+                               if (_initialPoint.distance(arg0.getX(),arg0.getY())>HYSTERESIS_DISTANCE)
+                               {
+                                       int selectedIndex = _vp.getNearestBaseIndex(arg0.getX(),arg0.getY(),false,false);
+                                       if (selectedIndex>=0)
+                                       {
+                                               ModeleBase mb = _vp.getNearestBase(arg0.getX(),arg0.getY(),false,false);
+                                               ModeleBase mborig = _selectedBase;
+                                               ModeleBP msbp = new ModeleBP(mb,mborig); 
+                                               if (mb!=mborig)
+                                               {
+                                                 _vp.getVARNAUI().UIAddBP(mb.getIndex(),mborig.getIndex(),msbp);
+                                               }
+                                       }
+                               }
+                               _vp.removeLink();
+                               _vp.clearSelection();
+                               _vp.repaint();
+                       }
+                       else
+                       {
+                               _vp.clearSelection();
+                       }
+
+               }
+               _currentState = MouseStates.NONE;
+               _vp.repaint();          
+       }
+
+
+       private void addMenu(MouseEvent arg0) {
+               // creation du menu
+               _submenuSelection = new JMenu("Selection");
+               addCurrent();
+               // ajout des option sur base
+               addMenuBase();
+               // ajout des option sur paire de base
+               if (_vp.getRNA().get_listeBases().get(_vp.getNearestBase())
+                               .getElementStructure() != -1) {
+                       addMenuBasePair();
+               }
+
+               // detection renflement
+               detectBulge();
+               // detection 3'
+               detect3Prime();
+               // detection 5'
+               detect5Prime();
+               // detection boucle
+               detectLoop();
+               // detection d'helice
+               detectHelix();
+               // detection tige
+               detectStem();
+               // Ajout de toutes bases
+               addAllBase();
+               // detection d'annotation
+               detectAnnotation(arg0);
+
+               _vp.getPopup().addSelectionMenu(_submenuSelection);
+               _presenceMenuSelection = true;
+       }
+
+       private void detectAnnotation(MouseEvent arg0) {
+               if (_vp.getListeAnnotations().size() != 0) {
+                       double dist = Double.MAX_VALUE;
+                       double d2;
+                       Point2D.Double position;
+                       for (TextAnnotation textAnnot : _vp.getListeAnnotations()) {
+                               // calcul de la distance
+                               position = textAnnot.getCenterPosition();
+                               position = _vp.transformCoord(position);
+                               d2 = Math.sqrt(Math.pow((position.x - arg0.getX()), 2)
+                                               + Math.pow((position.y - arg0.getY()), 2));
+                               // si la valeur est inferieur au minimum actuel
+                               if (dist > d2) {
+                                       _vp.set_selectedAnnotation(textAnnot);
+                                       dist = d2;
+                               }
+                       }
+                       _submenuSelection.addSeparator();
+                       _vp.getPopup().addAnnotationMenu(_submenuSelection,true);
+               }
+       }
+
+       private void detectBulge() {
+               int indiceB = _vp.getNearestBase();
+               ArrayList<Integer> indices = _vp.getRNA().findBulge(indiceB);
+               if ((indices.size() > 0)
+                               && (_vp.getRNA().getHelixCountOnLoop(_vp.getNearestBase()) == 2)) {
+                       JMenu submenuBulge = new JMenu("Bulge");
+                       submenuBulge.addChangeListener(new ControleurSelectionHighlight(
+                                       new Vector<Integer>(indices), _vp, submenuBulge));
+                       submenuBulge.setActionCommand("bulge");
+                       if (!_vp.isModifiable())
+                               submenuBulge.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenuBulge);
+                       _submenuSelection.add(submenuBulge);
+               }
+       }
+
+       private void detectHelix() {
+               int indiceH = _vp.getNearestBase();
+               ArrayList<Integer> indices = _vp.getRNA().findHelix(indiceH);
+               if (indices.size() != 0) {
+                       // ajout menu helice
+                       JMenu submenuHelix = new JMenu("Helix");
+                       submenuHelix.addChangeListener(new ControleurSelectionHighlight(
+                                       new Vector<Integer>(indices), _vp, submenuHelix));
+                       submenuHelix.setActionCommand("helix");
+                       if (!_vp.isModifiable())
+                               submenuHelix.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenuHelix);
+                       submenuHelix.addSeparator();
+                       _vp.getPopupMenu().addAnnotationMenu(submenuHelix);
+                       _submenuSelection.add(submenuHelix);
+               }
+       }
+
+       private void detectStem() {
+               int indiceS = _vp.getNearestBase();
+               ArrayList<Integer> indices = _vp.getRNA().findStem(indiceS);
+               if (indices.size() > 0) {
+                       JMenu submenuStem = new JMenu("Stem");
+                       submenuStem.addChangeListener(new ControleurSelectionHighlight(
+                                       new Vector<Integer>(indices), _vp, submenuStem));
+                       submenuStem.setActionCommand("stem");
+                       if (!_vp.isModifiable())
+                               submenuStem.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenuStem);
+                       _submenuSelection.add(submenuStem);
+               }
+       }
+
+       private void detect3Prime() {
+               // detection 3'
+               int indice3 = _vp.getNearestBase();
+               ArrayList<Integer> indices = _vp.getRNA().find3Prime(indice3);
+               if (indices.size() != 0) {
+                       JMenu submenu3Prime = new JMenu("3'");
+                       submenu3Prime.addChangeListener(new ControleurSelectionHighlight(
+                                       new Vector<Integer>(indices), _vp, submenu3Prime));
+                       submenu3Prime.setActionCommand("3'");
+                       if (!_vp.isModifiable())
+                               submenu3Prime.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenu3Prime);
+                       _submenuSelection.add(submenu3Prime);
+               }
+       }
+
+       private void detect5Prime() {
+               int indice5 = _vp.getNearestBase();
+               ArrayList<Integer> indices = _vp.getRNA().find5Prime(indice5);
+               if (indices.size() != 0) {
+                       JMenu submenu5Prime = new JMenu("5'");
+                       submenu5Prime.addChangeListener(new ControleurSelectionHighlight(
+                                       new Vector<Integer>(indices), _vp, submenu5Prime));
+                       submenu5Prime.setActionCommand("5'");
+                       if (!_vp.isModifiable())
+                               submenu5Prime.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenu5Prime);
+                       _submenuSelection.add(submenu5Prime);
+               }
+       }
+
+       private void detectLoop() {
+               int indexL = _vp.getNearestBase();
+               if (_vp.getRNA().get_listeBases().get(indexL).getElementStructure() == -1) {
+                       ArrayList<Integer> listLoop = _vp.getRNA().findLoop(indexL);
+                       JMenu submenuLoop = new JMenu("Loop");
+                       submenuLoop.addChangeListener(new ControleurSelectionHighlight(
+                                       listLoop, _vp, submenuLoop));
+                       submenuLoop.setActionCommand("loop1");
+                       if (!_vp.isModifiable())
+                               submenuLoop.setEnabled(false);
+                       _vp.getPopupMenu().addColorOptions(submenuLoop);
+                       submenuLoop.addSeparator();
+                       _vp.getPopupMenu().addAnnotationMenu(submenuLoop);
+                       _submenuSelection.add(submenuLoop);
+               } else {
+                       ArrayList<Integer> listLoop1 = _vp.getRNA().findLoopForward(indexL);
+                       if (listLoop1.size() > 0) {
+                               JMenu submenuLoop1 = new JMenu("Forward loop");
+                               submenuLoop1
+                                               .addChangeListener(new ControleurSelectionHighlight(
+                                                               listLoop1, _vp, submenuLoop1));
+                               submenuLoop1.setActionCommand("loop1");
+                               if (!_vp.isModifiable())
+                                       submenuLoop1.setEnabled(false);
+                               _vp.getPopupMenu().addColorOptions(submenuLoop1);
+                               submenuLoop1.addSeparator();
+                               _vp.getPopupMenu().addAnnotationMenu(submenuLoop1);
+                               _submenuSelection.add(submenuLoop1);
+                       }
+                       ArrayList<Integer> listLoop2 = _vp.getRNA()
+                                       .findLoopBackward(indexL);
+                       if (listLoop2.size() > 0) {
+                               JMenu submenuLoop2 = new JMenu("Backward loop");
+                               submenuLoop2
+                                               .addChangeListener(new ControleurSelectionHighlight(
+                                                               listLoop2, _vp, submenuLoop2));
+                               submenuLoop2.setActionCommand("loop2");
+                               if (!_vp.isModifiable())
+                                       submenuLoop2.setEnabled(false);
+                               _vp.getPopupMenu().addColorOptions(submenuLoop2);
+                               submenuLoop2.addSeparator();
+                               _vp.getPopupMenu().addAnnotationMenu(submenuLoop2);
+                               _submenuSelection.add(submenuLoop2);
+                       }
+               }
+       }
+
+       private void addCurrent() {
+               Collection<? extends ModeleBase> mbs = _vp.getSelection().getBases();
+               if (mbs.size()>0)
+               {
+               JMenu submenuAll = new JMenu("Current");
+               submenuAll.addChangeListener(new ControleurSelectionHighlight(
+                               mbs, _vp, submenuAll));
+               submenuAll.setActionCommand("current");
+               if (!_vp.isModifiable())
+                       submenuAll.setEnabled(false);
+               _vp.getPopupMenu().addColorOptions(submenuAll);
+               _submenuSelection.add(submenuAll);
+               }
+       }
+       
+       
+       private void addMenuBase() {
+               JMenu submenuBase = new JMenu();
+               ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase());
+               if (mb instanceof ModeleBasesComparison) {
+                       submenuBase.setText("Base #" + (mb.getBaseNumber()) + ":"
+                                       + ((ModeleBasesComparison) mb).getBases());
+               } else {
+                       submenuBase.setText("Base #" + (mb.getBaseNumber()) + ":"
+                                       + ((ModeleBaseNucleotide) mb).getBase());
+               }
+               submenuBase.addChangeListener(new ControleurSelectionHighlight(mb
+                               .getIndex(), _vp, submenuBase));
+               submenuBase.setActionCommand("base");
+               // option disponible seulement en mode modifiable
+               if (!_vp.isModifiable())
+                       submenuBase.setEnabled(false);
+
+               JMenuItem baseChar = new JMenuItem("Edit base");
+               baseChar.setActionCommand("baseChar");
+               baseChar.addActionListener(_vp.getPopupMenu().get_controleurMenu());
+               submenuBase.add(baseChar);
+               _vp.getPopupMenu().addColorOptions(submenuBase);
+               submenuBase.addSeparator();
+               _vp.getPopupMenu().addAnnotationMenu(submenuBase);
+               _submenuSelection.add(submenuBase);
+       }
+
+       private void addAllBase() {
+               ArrayList<Integer> indices = _vp.getRNA().findAll();
+               JMenu submenuAll = new JMenu("All");
+               submenuAll.addChangeListener(new ControleurSelectionHighlight(
+                               new Vector<Integer>(indices), _vp, submenuAll));
+               submenuAll.setActionCommand("all");
+               if (!_vp.isModifiable())
+                       submenuAll.setEnabled(false);
+               _vp.getPopupMenu().addColorOptions(submenuAll);
+               _submenuSelection.add(submenuAll);
+       }
+
+       private void addMenuBasePair() {
+                       int indiceBP = _vp.getNearestBase();
+                       ArrayList<Integer> indices = _vp.getRNA().findPair(indiceBP);
+                       ModeleBase base = _vp.getRNA()
+                                       .get_listeBases().get(_vp.getNearestBase());
+                       if (base.getElementStructure() != -1) {
+                               JMenu submenuBasePair = new JMenu();
+                               ModeleBase partner = _vp
+                                               .getRNA().get_listeBases().get(
+                                                               base.getElementStructure());
+                               submenuBasePair
+                                               .addChangeListener(new ControleurSelectionHighlight(
+                                                               indices, _vp, submenuBasePair));
+                               submenuBasePair.setText("Base pair #("
+                                               + (Math.min(base.getBaseNumber(), partner
+                                                               .getBaseNumber()))
+                                               + ","
+                                               + (Math.max(base.getBaseNumber(), partner
+                                                               .getBaseNumber())) + ")");
+                               submenuBasePair.setActionCommand("bp");
+                               // option disponible seulement en mode modifiable
+                               if (!_vp.isModifiable())
+                                       submenuBasePair.setEnabled(false);
+
+                               JMenuItem basepair = new JMenuItem("Edit BP");
+                               basepair.setActionCommand("basepair");
+                               basepair.addActionListener(_vp.getPopupMenu()
+                                               .get_controleurMenu());
+
+                               _vp.getPopupMenu().addColorOptions(submenuBasePair);
+                               Component[] comps = submenuBasePair.getMenuComponents();
+                               int offset = -1;
+                               for (int i = 0; i < comps.length; i++) {
+                                       Component c = comps[i];
+                                       if (c instanceof JMenuItem) {
+                                               JMenuItem jmi = (JMenuItem) c;
+                                               if (jmi.getActionCommand().contains(",BPColor")) {
+                                                       offset = i;
+                                               }
+                                       }
+                               }
+                               if (offset != -1) {
+                                       submenuBasePair.insert(basepair, offset);
+                               } else {
+                                       submenuBasePair.add(basepair);
+                               }
+                               _submenuSelection.add(submenuBasePair);
+                       }
+               }
+
+       private void updateNearestBase(MouseEvent arg0) {
+               int i = _vp.getNearestBaseIndex(arg0.getX(),arg0.getY(),true,false);
+               if (i!=-1)
+                       _vp.setNearestBase(i);
+       }
+
+
+
+       
+       public void mouseMoved(MouseEvent arg0) {
+               _selectedBase = _vp.getNearestBase(arg0.getX(),arg0.getY());
+               TextAnnotation selectedAnnotation = _vp.getNearestAnnotation(arg0.getX(),arg0.getY());
+               _vp.setHoverBase(_selectedBase);
+               if (_selectedBase != null)
+               {
+               }
+               else if (selectedAnnotation!=null)
+               {
+                       _vp.set_selectedAnnotation(selectedAnnotation);
+                       _vp.highlightSelectedAnnotation();
+                       _vp.repaint();
+               }
+               _vp.setLastSelectedPosition(new Point2D.Double(arg0.getX(),arg0.getY()));
+       }
+
+       
+       
+       private void moveSelection(Point prev, Point cur) 
+       {
+               Point2D.Double p1 =  _vp.panelToLogicPoint(new Point2D.Double(prev.x,prev.y));
+               Point2D.Double p2 =  _vp.panelToLogicPoint(new Point2D.Double(cur.x,cur.y));
+               double dx = (p2.x - p1.x);
+               double dy = (p2.y - p1.y);
+               
+               if (_vp.isModifiable())
+               {  
+                       double ndx = dx;
+                       double ndy = dy;
+                       if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_LINEAR) 
+                       {
+                               ndy=0.0;
+                       }
+                       _vp.getVARNAUI().UIShiftBaseCoord(_vp.getSelectionIndices(), ndx, ndy);
+                       _vp.fireLayoutChanged();
+                       
+               }
+       }
+
+
+       public void popupMenuCanceled(PopupMenuEvent arg0) {
+       }
+
+       public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
+               _vp.resetAnnotationHighlight();
+               _selectedBase = null;
+       }
+
+       public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurDemoTextField.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurDemoTextField.java
new file mode 100644 (file)
index 0000000..fd87fc6
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Stack;
+
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultHighlighter;
+import javax.swing.text.Highlighter;
+
+import fr.orsay.lri.varna.applications.VARNAOnlineDemo;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+
+/**
+ * It controls the sequence and structure text fields and changes their color if
+ * they have different lengths or unbalanced parentheses.
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurDemoTextField implements CaretListener {
+       private VARNAOnlineDemo _vod;
+       private String _oldSeq, _oldStruct;
+       private Highlighter _hilit;
+       private Highlighter.HighlightPainter _painter;
+       private final Color COLORERROR = Color.RED;
+       private final Color COLORWARNING = Color.ORANGE;
+
+       public ControleurDemoTextField(VARNAOnlineDemo VOD) {
+               _vod = VOD;
+               _oldSeq = _vod.get_seq().getText();
+               _oldStruct = _vod.get_struct().getText();
+               _hilit = new DefaultHighlighter();
+               _painter = new DefaultHighlighter.DefaultHighlightPainter(Color.BLACK);
+               _vod.get_struct().setHighlighter(_hilit);
+       }
+
+       // if there is any change
+       public void caretUpdate(CaretEvent e) {
+               if (_oldStruct != _vod.get_struct().getText()
+                               || _oldSeq != _vod.get_seq().getText()) {
+                       ArrayList<String> infos = new ArrayList<String>();
+                       _vod.get_info().removeAll();
+                       _hilit.removeAllHighlights();
+                       _oldStruct = _vod.get_struct().getText();
+                       _oldSeq = _vod.get_seq().getText();
+                       int nbPO = 0, nbPF = 0;
+                       // compte les parentheses ouvrantes et fermantes
+                       Stack<Integer> p = new Stack<Integer>();
+                       boolean pb = false;
+                       for (int i = 0; i < _vod.get_struct().getText().length(); i++) {
+                               if (_vod.get_struct().getText().charAt(i) == '(') {
+                                       nbPO++;
+                                       p.push(i);
+                               } else if (_vod.get_struct().getText().charAt(i) == ')') {
+                                       nbPF++;
+                                       if (p.size() == 0) {
+                                               try {
+                                                       _hilit.addHighlight(i, i + 1, _painter);
+                                               } catch (BadLocationException e1) {
+                                                       _vod.get_varnaPanel().errorDialog(e1);
+                                               }
+                                               pb = true;
+                                       } else
+                                               p.pop();
+                               }
+                       }
+
+                       // si le nombre de parentheses ouvrantes/fermantes est different
+                       if (pb || p.size() > 0) {
+                               // colorie en rouge
+                               if (pb) {
+                                       infos.add("too many closing parentheses");
+                               }
+                               if (p.size() > 0) {
+                                       int indice;
+                                       while (!p.isEmpty()) {
+                                               indice = p.pop();
+                                               try {
+                                                       _hilit.addHighlight(indice, indice + 1, _painter);
+                                               } catch (BadLocationException e1) {
+                                                       _vod.get_varnaPanel().errorDialog(e1);
+                                               }
+                                       }
+                                       infos.add("too many opening parentheses");
+                               }
+                               _vod.get_info().setForeground(COLORERROR);
+                               _vod.get_seq().setForeground(COLORERROR);
+                               _vod.get_struct().setForeground(COLORERROR);
+                       } else {
+                               try {
+                                       // redraw the new RNA
+                                       _vod.get_varnaPanel().drawRNA(_vod.get_seq().getText(),
+                                                       _vod.get_struct().getText(),
+                                                       _vod.get_varnaPanel().getRNA().get_drawMode());
+                               } catch (ExceptionNonEqualLength e1) {
+                                       _vod.get_varnaPanel().errorDialog(e1);
+                               }
+                               // verifie la longueur de la structure et de la sequence
+                               if (_vod.get_seq().getText().length() != _vod.get_struct()
+                                               .getText().length()) {
+                                       // colorie en orange
+                                       infos.add("different lenghts");
+                                       _vod.get_seq().setForeground(COLORWARNING);
+                                       _vod.get_struct().setForeground(COLORWARNING);
+                               } else {
+                                       // sinon colorie en noir
+                                       _vod.get_seq().setForeground(Color.black);
+                                       _vod.get_struct().setForeground(Color.black);
+                               }
+                       }
+                       _vod.get_varnaPanel().getVARNAUI().UIReset();
+                       String info = new String();
+                       if (infos.size() != 0) {
+                               info += infos.get(0);
+                               for (int i = 1; i < infos.size(); i++) {
+                                       info += ", " + infos.get(i);
+                               }
+                               info += ".";
+                       }
+                       _vod.get_info().setText(info);
+               }
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurDraggedMolette.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurDraggedMolette.java
new file mode 100644 (file)
index 0000000..8668faa
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+
+/**
+ * Controller of the mouse for scroll wheel click and dragged events
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurDraggedMolette implements MouseListener,
+               MouseMotionListener {
+       private VARNAPanel _vp;
+       /**
+        * <code>true</code> if the right button is pressed<br>
+        * <code>false</code> if not
+        */
+       private static Boolean _rightButtonClick;
+       /**
+        * The vector which contains the direction of the mouse movement
+        */
+       private static Point _direction;
+       /**
+        * The position of the cursor before the mouse drag
+        */
+       private static Point _avant;
+       /**
+        * The position of the cursor after the mouse drag
+        */
+       private static Point _apres;
+
+       public ControleurDraggedMolette(VARNAPanel vp) {
+               _vp = vp;
+               _rightButtonClick = false;
+               _avant = _apres = _direction = new Point();
+       }
+
+       public void mouseDragged(MouseEvent e) {
+               // si le bon boutton a Ã©té pressé
+               if (_rightButtonClick) {
+                       _apres = e.getPoint();
+                       _direction = new Point(_apres.x - _avant.x, _apres.y - _avant.y);
+                       _vp.setTranslation(new Point(_vp.getTranslation().x + _direction.x,
+                                       _vp.getTranslation().y + _direction.y));
+                       _avant = _apres;
+                       _vp.checkTranslation();
+                       _vp.repaint();
+               }
+       }
+
+       public void mouseMoved(MouseEvent e) {
+       }
+
+       public void mouseClicked(MouseEvent e) {
+       }
+
+       public void mouseEntered(MouseEvent e) {
+       }
+
+       public void mouseExited(MouseEvent e) {
+       }
+
+       public void mousePressed(MouseEvent e) {
+               // lors du clic, la position du curseur est enregistrée
+               _avant = e.getPoint();
+               // si le boutton molette est pressé ou si le boutton gauche et shift
+               // sont pressés
+               if (e.getButton() == MouseEvent.BUTTON2)
+               {
+                       _rightButtonClick = true;
+               }
+               else
+               {
+                       _rightButtonClick = false;
+               }
+                       
+                       
+       }
+
+       public void mouseReleased(MouseEvent e) {
+               // si le boutton molette est relaché ou si le boutton gauche et shift
+               // sont relachés
+               if (e.getButton() == MouseEvent.BUTTON2)
+                       _rightButtonClick = false;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRescale.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRescale.java
new file mode 100644 (file)
index 0000000..7e464b6
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.VARNAEdits;
+import fr.orsay.lri.varna.views.VueGlobalRescale;
+import fr.orsay.lri.varna.views.VueGlobalRotation;
+
+
+public class ControleurGlobalRescale implements ChangeListener {
+
+       private VueGlobalRescale _vGR;
+       private double _oldScale;
+       private VARNAPanel _vp;
+
+       public ControleurGlobalRescale(VueGlobalRescale vGR, VARNAPanel vp) {
+               _vGR = vGR;
+               _oldScale = 1.0;
+               _vp = vp;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vp.getVARNAUI().UIGlobalRescale(_vGR.getScale()/_oldScale);
+               _oldScale = _vGR.getScale();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRotation.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurGlobalRotation.java
new file mode 100644 (file)
index 0000000..e3dc8a8
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.VARNAEdits;
+import fr.orsay.lri.varna.views.VueGlobalRotation;
+
+
+public class ControleurGlobalRotation implements ChangeListener {
+
+       private VueGlobalRotation _vGR;
+       private double _oldAngle;
+       private VARNAPanel _vp;
+
+       public ControleurGlobalRotation(VueGlobalRotation vGR, VARNAPanel vp) {
+               _vGR = vGR;
+               _oldAngle = 0;
+               _vp = vp;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vp.getVARNAUI().UIGlobalRotation(_vGR.getAngle() - _oldAngle);
+               _oldAngle = _vGR.getAngle();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurInterpolator.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurInterpolator.java
new file mode 100644 (file)
index 0000000..827f762
--- /dev/null
@@ -0,0 +1,660 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Vector;
+
+import javax.swing.Timer;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class ControleurInterpolator extends Thread implements ActionListener {
+
+       private static final int START = 0;
+       private static final int LOOP = 1;
+       private static final int END = 2;
+
+       VARNAPanel _vpn;
+       protected int _numSteps = 25;  // BH SwingJS
+       private long _timeDelay = /** @j2sNative 2 || */15;
+       private boolean _running = false;
+       Targets _d = new Targets();
+       protected int _step;   // BH SwingJS
+       protected boolean _firstHalf;  // BH SwingJS
+
+       public ControleurInterpolator(VARNAPanel vpn) {
+               _vpn = vpn;
+       }
+
+       public synchronized void addTarget(RNA target, Mapping mapping) {
+               addTarget(target, null, mapping);
+       }
+
+       
+       public synchronized void addTarget(RNA target, VARNAConfig conf, Mapping mapping) {
+               _d.add(new TargetsHolder(target,conf,mapping));
+       }
+
+       public synchronized void addTarget(RNA target) {
+               addTarget(target, null, Mapping.DefaultOutermostMapping(_vpn.getRNA()
+                               .get_listeBases().size(), target.get_listeBases().size()));
+       }
+       
+       public boolean isInterpolationInProgress()
+       {
+               return _running;
+       }
+
+       private Point2D.Double computeDestination(Point2D pli, Point2D pri,
+                       Point2D pi, int i, int n, Point2D plf, Point2D prf) {
+               Point2D.Double plm = new Point2D.Double(
+                               (pli.getX() + plf.getX()) / 2.0,
+                               (pli.getY() + plf.getY()) / 2.0);
+               Point2D.Double prm = new Point2D.Double(
+                               (pri.getX() + prf.getX()) / 2.0,
+                               (pri.getY() + prf.getY()) / 2.0);
+               Point2D.Double pm = new Point2D.Double(((n - i) * plm.getX() + i
+                               * prm.getX())
+                               / n, ((n - i) * plm.getY() + i * prm.getY()) / n);
+               Point2D.Double v = new Point2D.Double(pm.getX() - pi.getX(), pm.getY()
+                               - pi.getY());
+               Point2D.Double pf = new Point2D.Double(pi.getX() + 2.0 * v.getX(), pi
+                               .getY()
+                               + 2.0 * v.getY());
+               return pf;
+       }
+
+       private Vector<Vector<Integer>> clusterIndices(int numIndices,
+                       int[] mappedIndices) throws MappingException {
+               int[] indices = new int[numIndices];
+               for (int i = 0; i < numIndices; i++) {
+                       indices[i] = i;
+               }
+               return clusterIndices(indices, mappedIndices);
+       }
+
+       /**
+        * Builds and returns an interval array, alternating unmatched regions and
+        * matched indices. Namely, returns a vector of vector such that the vectors
+        * found at odd positions contain those indices that are NOT associated with
+        * any other base in the current mapping. On the other hand, vectors found
+        * at even positions contain only one mapped index. <br/>
+        * Ex: If indices=<code>[1,2,3,4,5]</code> and mappedIndices=
+        * <code>[2,5]</code> then the function will return
+        * <code>[[1],[2],[3,4],[5],[]]</code>.
+        * 
+        * @param indices
+        *            The total list of indices
+        * @param mappedIndices
+        *            Matched indices, should be a subset of <code>indices</code>
+        * @return A clustered array
+        * @throws MappingException
+        *             If one of the parameters is an empty array
+        */
+       private Vector<Vector<Integer>> clusterIndices(int[] indices,
+                       int[] mappedIndices) throws MappingException {
+               if ((mappedIndices.length == 0) || (indices.length == 0)) {
+                       throw new MappingException(
+                                       "Mapping Error: Cannot cluster indices in an empty mapping");
+               }
+               Vector<Vector<Integer>> res = new Vector<Vector<Integer>>();
+
+               Arrays.sort(indices);
+               Arrays.sort(mappedIndices);
+               int i, j = 0, k;
+               Vector<Integer> tmp = new Vector<Integer>();
+               for (i = 0; (i < indices.length) && (j < mappedIndices.length); i++) {
+                       if (indices[i] == mappedIndices[j]) {
+                               res.add(tmp);
+                               tmp = new Vector<Integer>();
+                               tmp.add(indices[i]);
+                               res.add(tmp);
+                               tmp = new Vector<Integer>();
+                               j++;
+                       } else {
+                               tmp.add(indices[i]);
+                       }
+               }
+               k = i;
+               for (i = k; (i < indices.length); i++) {
+                       tmp.add(indices[i]);
+               }
+               res.add(tmp);
+               return res;
+       }
+       
+       
+
+       public void run() {
+               while (true)
+               {
+                       TargetsHolder d = _d.get();
+                       _running = true;
+                       try{
+                       nextTarget(d.target,d.conf,d.mapping);
+                       }
+                       catch(Exception e)
+                       {
+                               System.err.println(e);
+                               e.printStackTrace();
+                       }
+                       _running = false;
+                       /**
+                        * @j2sNative
+                        * 
+                        * break;
+                        */
+               }
+               
+       }
+       
+       /**
+        * Compute the centroid of the RNA bases that have their indexes
+        * in the given array.
+        */
+       private static Point2D.Double computeCentroid(ArrayList<ModeleBase> rnaBases, int[] indexes) {          
+               double centroidX = 0, centroidY = 0;
+               for (int i=0; i<indexes.length; i++) {
+                       int index = indexes[i];
+                       Point2D coords = rnaBases.get(index).getCoords();
+                       centroidX += coords.getX();
+                       centroidY += coords.getY();
+               }
+               centroidX /= indexes.length;
+               centroidY /= indexes.length;
+               
+               return new Point2D.Double(centroidX, centroidY);
+       }
+       
+       /**
+        * The purpose of this class is to compute the rotation that minimizes the
+        * RMSD between the bases of the first RNA and the bases of the second RNA.
+        * 
+        * @author Raphael Champeimont
+        */
+       private static class MinimizeRMSD {
+               private double[] X1, X2, Y1, Y2;
+               
+               public MinimizeRMSD(double[] X1, double[] Y1, double[] X2, double[] Y2) {
+                       this.X1 = X1;
+                       this.X2 = X2;
+                       this.Y1 = Y1;
+                       this.Y2 = Y2;
+               }
+               
+               /**
+                * A function such that minimizing it is equivalent to
+                * minimize the RMSD between between the two arrays of vectors,
+                * supposing we rotate the points in [X2,Y2] by theta.
+                */
+               private double f(double theta) {
+                   double cos_theta = Math.cos(theta);
+                   double sin_theta = Math.sin(theta);
+                   int n = X1.length;
+                   double sum = 0;
+                   for (int i=0; i<n; i++) {
+                       double dsx = X2[i]*cos_theta - Y2[i]*sin_theta - X1[i];
+                       double dsy = X2[i]*sin_theta + Y2[i]*cos_theta - Y1[i];
+                       sum = sum + dsx*dsx + dsy*dsy;
+                   }
+                   return sum;
+               }
+               
+               /**
+                * d/dtheta f(theta)
+                */
+               private double fprime(double theta) {
+                   double cos_theta = Math.cos(theta);
+                   double sin_theta = Math.sin(theta);
+                   int n = X1.length;
+                   double sum = 0;
+                   for (int i=0; i<n; i++) {
+                       sum = sum
+                           + (X1[i]*X2[i] + Y1[i]*Y2[i]) * sin_theta
+                           + (X1[i]*Y2[i] - X2[i]*Y1[i]) * cos_theta;
+                   }
+                   return sum;
+               }
+               
+               /**
+                * d^2/dtheta^2 f(theta)
+                */
+               private double fsecond(double theta) {
+                   double cos_theta = Math.cos(theta);
+                   double sin_theta = Math.sin(theta);
+                   int n = X1.length;
+                   double sum = 0;
+                   for (int i=0; i<n; i++) {
+                       sum = sum
+                           + (X1[i]*X2[i] + Y1[i]*Y2[i]) * cos_theta
+                           + (X2[i]*Y1[i] - X1[i]*Y2[i]) * sin_theta;
+                   }
+                   return sum;
+               }
+
+
+               /**
+                * Find the theta that minimizes f(theta).
+                * We use Newton's method.
+                */
+               public  double computeOptimalTheta() {
+                       final double epsilon = 1E-5;
+                       double x_n = 0;
+                       int numsteps = 0;
+                       // In practice the number of steps to reach precision 1E-5 is smaller that
+                       // 10 most of the time.
+                       final int maxnumsteps = 100;
+                       double x_n_plus_1;
+                       double result;
+                       while (true) {
+                           numsteps = numsteps + 1;
+                           double d = fsecond(x_n);
+                           if (d == 0) {
+                               // if f''(x_n) is 0 we cannot divide by it,
+                               // so we move a little.
+                               x_n_plus_1 = x_n + epsilon * (Math.random() - 0.5);
+                           } else {
+                               x_n_plus_1 = x_n - fprime(x_n)/fsecond(x_n);
+                               if (Math.abs(x_n_plus_1 - x_n) < epsilon) {
+                                    result = x_n_plus_1;
+                                    break;
+                               }
+                           }
+                           if (numsteps >= maxnumsteps) {
+                               // If things go bad (for example f''(x) keeps being 0)
+                               // we need to give up after what we consider to be too many steps.
+                               // In practice this can happen only in pathological cases
+                               // like f being constant, which is very unlikely.
+                               result = x_n_plus_1;
+                               break;
+                               }
+                           x_n = x_n_plus_1;
+                       }
+
+                       // We now have either found the min or the max at x = result.
+                       // If we have the max at x we know the min is at x+pi.
+                       if (f(result + Math.PI) < f(result)) {
+                           result = result + Math.PI;
+                       }
+                       
+                       return result;
+               }
+
+
+       }
+       
+       
+       /**
+        * We suppose we have two lists of points. The coordinates of the first
+        * list of points are X1 and Y1, and X2 and Y2 for the second list.
+        * We suppose that the centroids of [X1,Y1] and [X2,Y2] are both (0,0).
+        * This function computes the rotation to apply to the second set of
+        * points such that the RMSD (Root mean square deviation) between them
+        * is minimum. The returned angle is in radians.
+        */
+       private static double minimizeRotateRMSD(double[] X1, double[] Y1, double[] X2, double[] Y2) {
+               MinimizeRMSD minimizer = new MinimizeRMSD(X1, Y1, X2, Y2);
+               return minimizer.computeOptimalTheta();
+       }
+       
+       
+       /**
+        * Move rna2 using a rotation so that it would move as little as possible
+        * (in the sense that we minimize the RMSD) when transforming
+        * it to rna1 using the given mapping.
+        */
+       public static void moveNearOtherRNA(RNA rna1, RNA rna2, Mapping mapping) {
+               int[] rna1MappedElems = mapping.getSourceElems();
+               int[] rna2MappedElems = mapping.getTargetElems();
+               ArrayList<ModeleBase> rna1Bases = rna1.get_listeBases();
+               ArrayList<ModeleBase> rna2Bases = rna2.get_listeBases();
+               int n = rna1MappedElems.length;
+               
+               // If there is less than 2 points, it is useless to rotate the RNA.
+               if (n < 2) return;
+               
+               // We can now assume that n >= 2.
+               
+               // Compute the centroids of both RNAs
+               Point2D.Double rna1MappedElemsCentroid = computeCentroid(rna1Bases, rna1MappedElems);
+               Point2D.Double rna2MappedElemsCentroid = computeCentroid(rna2Bases, rna2MappedElems);
+
+               // Compute the optimal rotation
+               // We first compute coordinates for both RNAs, changing the origins
+               // to be the centroids.
+               double[] X1 = new double[rna1MappedElems.length];
+               double[] Y1 = new double[rna1MappedElems.length];
+               double[] X2 = new double[rna2MappedElems.length];
+               double[] Y2 = new double[rna2MappedElems.length];
+               for (int i=0; i<rna1MappedElems.length; i++) {
+                       int base1Index = rna1MappedElems[i];
+                       Point2D.Double coords1 = rna1Bases.get(base1Index).getCoords();
+                       X1[i] = coords1.x - rna1MappedElemsCentroid.x;
+                       Y1[i] = coords1.y - rna1MappedElemsCentroid.y;
+                       Point2D.Double coords2 = rna2Bases.get(mapping.getPartner(base1Index)).getCoords();
+                       X2[i] = coords2.x - rna2MappedElemsCentroid.x;
+                       Y2[i] = coords2.y - rna2MappedElemsCentroid.y;
+               }
+               
+               // Compute the optimal rotation angle theta
+               double theta = minimizeRotateRMSD(X1, Y1, X2, Y2);
+               
+               // Rotate RNA2
+               rna2.globalRotation(theta * 180.0 / Math.PI);
+       }
+
+       public void nextTarget(RNA _target, VARNAConfig _conf, Mapping _mapping) {
+               nextTarget(_target, _conf, _mapping, false);
+       }
+       
+       Runnable _loop, _end;
+       
+       /**
+        * The argument moveTarget specifies whether the RNA _target should
+        * be rotated so that bases move as little as possible when switching
+        * from the current RNA to _target using the animation.
+        * Note that this will modify the _target object directly.
+        */
+       public void nextTarget(final RNA _target, final VARNAConfig _conf, Mapping _mapping, boolean moveTarget)
+       {
+               _end = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               _vpn.showRNA(_target);
+                               _vpn.repaint();
+                       }
+                       
+               };
+               
+               try {
+                       final RNA source = _vpn.getRNA();
+                       
+                       if (moveTarget) moveNearOtherRNA(source, _target, _mapping);
+
+                       if (source.getSize()!=0&&_target.getSize()!=0)
+                       {
+                       ArrayList<ModeleBase> currBases = source.get_listeBases();
+                       ArrayList<ModeleBase> destBases = _target.get_listeBases();
+                       Vector<Vector<Integer>> intArrSource = new Vector<Vector<Integer>>();
+                       Vector<Vector<Integer>> intArrTarget = new Vector<Vector<Integer>>();
+                               // Building interval arrays
+                               intArrSource = clusterIndices(currBases.size(), _mapping
+                                               .getSourceElems());
+                               intArrTarget = clusterIndices(destBases.size(), _mapping
+                                               .getTargetElems());
+
+                       // Duplicating source and target coordinates
+                       final Point2D.Double[] initPosSource = new Point2D.Double[currBases
+                                       .size()];
+                       final Point2D.Double[] finalPosTarget = new Point2D.Double[destBases
+                                       .size()];
+
+                       for (int i = 0; i < currBases.size(); i++) {
+                               Point2D tmp = currBases.get(i).getCoords();
+                               initPosSource[i] = new Point2D.Double(tmp.getX(), tmp.getY());
+                       }
+                       for (int i = 0; i < destBases.size(); i++) {
+                               Point2D tmp = destBases.get(i).getCoords();
+                               finalPosTarget[i] = new Point2D.Double(tmp.getX(), tmp.getY());
+                       }
+
+                       /**
+                        * Assigning final (Source) and initial (Target) coordinates
+                        */
+                       final Point2D.Double[] finalPosSource = new Point2D.Double[initPosSource.length];
+                       final Point2D.Double[] initPosTarget = new Point2D.Double[finalPosTarget.length];
+                       // Final position of source model
+                       for (int i = 0; i < finalPosSource.length; i++) {
+                               if (_mapping.getPartner(i) != Mapping.UNKNOWN) {
+                                       Point2D dest;
+                                       dest = finalPosTarget[_mapping.getPartner(i)];
+                                       finalPosSource[i] = new Point2D.Double(dest.getX(), dest
+                                                       .getY());
+                               }
+                       }
+
+                       for (int i = 0; i < intArrSource.size(); i += 2) {
+                               int matchedNeighborLeft, matchedNeighborRight;
+                               if (i == 0) {
+                                       matchedNeighborLeft = intArrSource.get(1).get(0);
+                                       matchedNeighborRight = intArrSource.get(1).get(0);
+                               } else if (i == intArrSource.size() - 1) {
+                                       matchedNeighborLeft = intArrSource.get(
+                                                       intArrSource.size() - 2).get(0);
+                                       matchedNeighborRight = intArrSource.get(
+                                                       intArrSource.size() - 2).get(0);
+                               } else {
+                                       matchedNeighborLeft = intArrSource.get(i - 1).get(0);
+                                       matchedNeighborRight = intArrSource.get(i + 1).get(0);
+                               }
+                               Vector<Integer> v = intArrSource.get(i);
+                               for (int j = 0; j < v.size(); j++) {
+                                       int index = v.get(j);
+                                       finalPosSource[index] = computeDestination(
+                                                       initPosSource[matchedNeighborLeft],
+                                                       initPosSource[matchedNeighborRight],
+                                                       initPosSource[index], j + 1, v.size() + 1,
+                                                       finalPosSource[matchedNeighborLeft],
+                                                       finalPosSource[matchedNeighborRight]);
+                               }
+                       }
+                       for (int i = 0; i < initPosTarget.length; i++) {
+                               if (_mapping.getAncestor(i) != Mapping.UNKNOWN) {
+                                       Point2D dest;
+                                       dest = initPosSource[_mapping.getAncestor(i)];
+                                       initPosTarget[i] = new Point2D.Double(dest.getX(), dest.getY());
+                               }
+                       }
+                       for (int i = 0; i < intArrTarget.size(); i += 2) {
+                               int matchedNeighborLeft, matchedNeighborRight;
+                               if (i == 0) {
+                                       matchedNeighborLeft = intArrTarget.get(1).get(0);
+                                       matchedNeighborRight = intArrTarget.get(1).get(0);
+                               } else if (i == intArrTarget.size() - 1) {
+                                       matchedNeighborLeft = intArrTarget.get(
+                                                       intArrTarget.size() - 2).get(0);
+                                       matchedNeighborRight = intArrTarget.get(
+                                                       intArrTarget.size() - 2).get(0);
+                               } else {
+                                       matchedNeighborLeft = intArrTarget.get(i - 1).get(0);
+                                       matchedNeighborRight = intArrTarget.get(i + 1).get(0);
+                               }
+                               Vector<Integer> v = intArrTarget.get(i);
+                               for (int j = 0; j < v.size(); j++) {
+                                       int index = v.get(j);
+                                       initPosTarget[index] = computeDestination(
+                                                       finalPosTarget[matchedNeighborLeft],
+                                                       finalPosTarget[matchedNeighborRight],
+                                                       finalPosTarget[index], j + 1, v.size() + 1,
+                                                       initPosTarget[matchedNeighborLeft],
+                                                       initPosTarget[matchedNeighborRight]);
+                               }
+                       }
+
+                       mode = START;
+                       _loop = new Runnable(){
+
+                               @Override
+                               public void run() {
+                                       int i = _step;
+                                       RNA current = (_firstHalf ? source : _target);
+                                       if (i == _numSteps / 2) {
+                                               _vpn.showRNA(_target);
+                                               current = _target;
+                                               _firstHalf = false;
+                                               if (_conf!=null)
+                                               {_vpn.setConfig(_conf);}
+                                               
+                                               for (int j = 0; j < initPosSource.length; j++) {
+                                                       source.setCoord(j, initPosSource[j]);
+                                               }
+                                       }
+                                       ArrayList<ModeleBase> currBases = current.get_listeBases();
+                                       for (int j = 0; j < currBases.size(); j++) {
+                                               ModeleBase m = currBases.get(j);
+                                               Point2D mpc, mnc;
+                                               if (_firstHalf) {
+                                                       mpc = initPosSource[j];
+                                                       mnc = finalPosSource[j];
+                                               } else {
+                                                       mpc = initPosTarget[j];
+                                                       mnc = finalPosTarget[j];
+                                               }
+                                               m.setCoords(new Point2D.Double(((_numSteps - 1 - i)
+                                                               * mpc.getX() + (i) * mnc.getX())
+                                                               / (_numSteps - 1), ((_numSteps - 1 - i)
+                                                               * mpc.getY() + i * mnc.getY())
+                                                               / (_numSteps - 1)));
+                                       }
+                                       _vpn.repaint();
+                               }
+                               
+                       };
+                       actionPerformed(null);
+                       } else {
+                               _end.run();
+                       }
+               } catch (MappingException e) {
+                       e.printStackTrace();
+                       _end.run();
+               }catch (Exception e) {
+                       e.printStackTrace();
+                       _end.run();
+               }
+               
+
+       }
+
+       private class TargetsHolder
+       {
+               public RNA target;
+               public VARNAConfig conf;
+               public Mapping mapping;
+               public TargetsHolder(RNA t, VARNAConfig c, Mapping m)
+               {
+                       target = t;
+                       conf = c;
+                       mapping = m;
+               }
+       }
+       
+       private class Targets
+       {
+               LinkedList<TargetsHolder> _d = new LinkedList<TargetsHolder>();
+               public Targets() {
+                       // BH j2s SwingJS added only to remove Eclipse warning
+               }
+               public synchronized void add(TargetsHolder d)
+               {
+                       _d.addLast(d);
+                       
+                       @SuppressWarnings("unused")
+                       Runnable interpolator = ControleurInterpolator.this;
+                       /**
+                        * BH SwingJS no notify()
+                        * @j2sNative 
+                        * 
+                        * interpolator.run();
+                        * 
+                        */
+                       {
+                       notify();
+                       }
+               }
+
+               public synchronized TargetsHolder get() {
+
+                       /**
+                        * BH SwingJS no wait()
+                        * 
+                        * @j2sNative
+                        * 
+                        * 
+                        */
+                       {
+                               while (_d.size() == 0) {
+                                       try {
+                                               wait();
+                                       } catch (InterruptedException e) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+                       TargetsHolder x = _d.getLast();
+                       _d.clear();
+                       return x;
+               }
+       }
+
+
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               runAnimation();
+       }
+
+       private int mode;
+
+       private void runAnimation() {
+               switch (mode) {
+               case START:
+                       _firstHalf = true;
+                       _step = 0;
+                       mode = LOOP;
+                       // Fall through
+               case LOOP:
+                       if (_step < _numSteps) {
+                               _loop.run();
+                               ++_step;
+                               break;
+                       }
+                       mode = END;
+                       // Fall through
+               case END:
+                       _end.run();
+                       return;
+               }
+               Timer t = new Timer((int) _timeDelay, this);
+               t.setRepeats(false);
+               t.start();
+               // try {
+               // for (int i = 0; i < _numSteps; i++) {
+               // _step = i;
+               // loop.run();
+               //
+               // sleep(_timeDelay);
+               // }
+               // end.run();
+               // } catch (InterruptedException e) {
+               // e.printStackTrace();
+               // end.run();
+               // }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurJCheckBoxMenuItem.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurJCheckBoxMenuItem.java
new file mode 100644 (file)
index 0000000..825c58c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+
+/**
+ * Controller of check box menu items to repaint if item state changes
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurJCheckBoxMenuItem implements ItemListener {
+
+       private VARNAPanel _vp;
+
+       public ControleurJCheckBoxMenuItem(VARNAPanel v) {
+               _vp = v;
+       }
+
+       public void itemStateChanged(ItemEvent e) {
+               _vp.repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurMenu.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurMenu.java
new file mode 100644 (file)
index 0000000..22c082f
--- /dev/null
@@ -0,0 +1,738 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JColorChooser;
+import javax.swing.JOptionPane;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionJPEGEncoding;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.views.VueBPThickness;
+import fr.orsay.lri.varna.views.VueMenu;
+
+/**
+ * This listener controls menu items
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurMenu implements InterfaceVARNAListener,
+               ActionListener {
+
+       private VARNAPanel _vp;
+       @SuppressWarnings("unused")
+       private VueMenu _vm;
+       private String _type;
+       private String _color;
+       private Object _source;
+
+       /**
+        * Creates the menu listener
+        * 
+        * @param _varnaPanel
+        *            The VARNAPanel
+        */
+       public ControleurMenu(VARNAPanel _varnaPanel, VueMenu _vueMenu) {
+               _vp = _varnaPanel;
+               _vm = _vueMenu;
+               _vp.getRNA().addVARNAListener(this);
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               String[] temp = e.getActionCommand().split(",");
+               _source = e.getSource();
+               _type = temp[0];
+               if (temp.length > 1)
+                       _color = temp[1];
+               else
+                       _color = "";
+               // selon l'option choisie dans le menu:
+               if (!optionRedraw())
+                       if (!optionExport())
+                               if (!optionImport())
+                                       if (!optionRNADisplay())
+                                               if (!optionTitle())
+                                                       if (!optionColorMap())
+                                                               if (!optionView())
+                                                                       if (!optionBase())
+                                                                               if (!optionBasePair())
+                                                                                       if (!optionLoop())
+                                                                                               if (!option3prime())
+                                                                                                       if (!option5prime())
+                                                                                                               if (!optionHelix())
+                                                                                                                       if (!optionStem())
+                                                                                                                               if (!optionBulge())
+                                                                                                                                       if (!optionAnnotation())
+                                                                                                                                               if (!optionEditRNA())
+                                                                                                                                                       _vp.errorDialog(new Exception("Uknown action command '"+_type+"'"));
+       }
+       
+       private boolean optionEditRNA()
+       {
+               if (_type.equals("editallbps")) {
+                       _vp.getVARNAUI().UIEditAllBasePairs();
+               }
+               else if (_type.equals("editallbases")) {
+                       _vp.getVARNAUI().UIEditAllBases();
+               }
+               else return false;
+               return true;
+       }
+
+       private boolean optionAnnotation() {
+               if (!_type.contains("annotation"))
+                       return false;
+               // a partir du menu principale (gestion des annotations)
+               if (_type.equals("annotationsaddPosition")) {
+                       _vp.getVARNAUI().UIAnnotationsAddPosition(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsaddBase")) {
+                       _vp.getVARNAUI().UIAnnotationsAddBase(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsaddLoop")) {
+                       _vp.getVARNAUI().UIAnnotationsAddLoop(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsaddChemProb")) {
+                       _vp.getVARNAUI().UIAnnotationsAddChemProb(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsaddRegion")) {
+                       _vp.getVARNAUI().UIAnnotationsAddRegion(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsaddHelix")) {
+                       _vp.getVARNAUI().UIAnnotationsAddHelix(_vp.getPopup().getSpawnPoint().x,_vp.getPopup().getSpawnPoint().y);
+               } else if (_type.equals("annotationsautohelices")) {
+                       _vp.getVARNAUI().UIAutoAnnotateHelices();
+               } else if (_type.equals("annotationsautointerior")) {
+                       _vp.getVARNAUI().UIAutoAnnotateInteriorLoops();
+               } else if (_type.equals("annotationsautoterminal")) {
+                       _vp.getVARNAUI().UIAutoAnnotateTerminalLoops();
+               } else if (_type.equals("annotationsautohelices")) {
+                       _vp.getVARNAUI().UIAutoAnnotateHelices();
+               } else if (_type.equals("annotationsremove")) {
+                       _vp.getVARNAUI().UIAnnotationsRemove();
+               } else if (_type.equals("annotationsautoextremites")) {
+                       _vp.getVARNAUI().UIAutoAnnotateStrandEnds();
+               } else if (_type.equals("annotationsedit")) {
+                       _vp.getVARNAUI().UIAnnotationsEdit();
+                       // a partir du menu selection (annotation la plus proche)
+               } else if (_type.equals("Selectionannotationremove")) {
+                       _vp.getVARNAUI().UIAnnotationRemoveFromAnnotation(_vp.get_selectedAnnotation());
+               } else if (_type.equals("Selectionannotationedit")) {
+                       _vp.getVARNAUI().UIAnnotationEditFromAnnotation(_vp.get_selectedAnnotation());
+
+                       // a partir d'une structure(base, loop, helix) dans l'arn
+                       // (annotation li� a la structure)
+               } else if (_type.endsWith("annotationadd")||_type.contains("annotationremove")||_type.contains("annotationedit")) 
+               {
+                       try {
+                               TextAnnotation.AnchorType type = trouverAncrage();
+                                ArrayList<Integer> listeIndex = new ArrayList<Integer>();
+                               switch(type)
+                               {
+                                       case BASE:
+                                               listeIndex.add(_vp.getNearestBase());
+                                       case LOOP:
+                                               if (_type.startsWith("loop1"))
+                                                       listeIndex = _vp.getRNA().findLoopForward(_vp.getNearestBase());
+                                               else if (_type.startsWith("loop2"))
+                                                       listeIndex = _vp.getRNA().findLoopBackward(_vp.getNearestBase());
+                                               else
+                                                       listeIndex = _vp.getRNA().findLoop(_vp.getNearestBase());
+                                       break;
+                                       case HELIX:
+                                               listeIndex = _vp.getRNA().findHelix(_vp.getNearestBase());
+                                       break;                          
+                               }
+                               if (_type.endsWith("annotationadd"))
+                               { _vp.getVARNAUI().UIAnnotationAddFromStructure(type,listeIndex); }
+                               else if (_type.contains("annotationremove")) 
+                               { _vp.getVARNAUI().UIAnnotationRemoveFromStructure(trouverAncrage(),listeIndex); }
+                               else if (_type.contains("annotationedit")) 
+                               { _vp.getVARNAUI().UIAnnotationEditFromStructure(trouverAncrage(),listeIndex); }
+                                       
+                       } catch (Exception e2) {
+                               e2.printStackTrace();
+                       }
+                       } else
+                       return false;
+               return true;
+       }
+
+       private TextAnnotation.AnchorType trouverAncrage() {
+               if (_type.contains("loop"))
+                       return TextAnnotation.AnchorType.LOOP;
+               if (_type.contains("helix"))
+                       return TextAnnotation.AnchorType.HELIX;
+               if (_type.contains("base"))
+                       return TextAnnotation.AnchorType.BASE;
+               errorDialog(new Exception("probleme d'identification de l'ancrage"));
+               return TextAnnotation.AnchorType.POSITION;
+       }
+
+       private boolean option5prime() {
+               return colorBases();
+       }
+
+       private boolean option3prime() {
+               return colorBases();
+       }
+
+       private boolean optionBulge() {
+               return colorBases();
+       }
+
+       private boolean optionStem() {
+               return colorBases();
+       }
+
+       private boolean optionHelix() {
+               return colorBases();
+       }
+
+       private boolean colorBases() {
+               // System.out.println(_type);
+               ArrayList<Integer> listBase = new ArrayList<Integer>();
+               String phrase = "Choose new " + _type;
+               if (_color.equals("InnerColor")) {
+                       phrase += " inner color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_INNER_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) {
+                                       _vp.getRNA().get_listeBases().get(listBase.get(i))
+                                                       .getStyleBase().setBaseInnerColor(c);
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("OutlineColor")) {
+                       phrase += " outline color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_OUTLINE_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) {
+                                       _vp.getRNA().get_listeBases().get(listBase.get(i))
+                                                       .getStyleBase().setBaseOutlineColor(c);
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("NameColor")) {
+                       phrase += " name color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_NAME_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) {
+                                       _vp.getRNA().get_listeBases().get(listBase.get(i))
+                                                       .getStyleBase().setBaseNameColor(c);
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("NumberColor")) {
+                       phrase += " number color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_NUMBER_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) {
+                                       _vp.getRNA().get_listeBases().get(listBase.get(i))
+                                                       .getStyleBase().setBaseNumberColor(c);
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("BPColor")) {
+                       phrase += " base-pair color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_NUMBER_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) 
+                               {
+                                       for (ModeleBP msbp:_vp.getRNA().getBPsAt(listBase.get(i)))
+                                       {
+                                               if (msbp!=null) {
+                                                       msbp.getStyle().setCustomColor(c);
+                                               }       
+                                       }
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("BPColor")) {
+                       phrase += " base-pair color";
+                       Color c = JColorChooser.showDialog(_vp, phrase,
+                                       VARNAConfig.BASE_NUMBER_COLOR_DEFAULT);
+                       if (c != null) {
+                               listBase = listSwitchType(_type);
+                               for (int i = 0; i < listBase.size(); i++) {
+                                       ModeleBase mb = _vp.getRNA().get_listeBases().get(
+                                                       listBase.get(i));
+                                       if (mb.getElementStructure() != -1) {
+                                               mb.getStyleBP().getStyle().setCustomColor(c);
+                                       }
+                               }
+                               _vp.repaint();
+                       }
+               } else if (_color.equals("BPThickness")) {
+                       listBase = listSwitchType(_type);
+                       // System.out.println(listBase.size());
+                       ArrayList<ModeleBP> styleBPs = new ArrayList<ModeleBP>();
+                       for (int i = 0; i < listBase.size(); i++) {
+                               ModeleBase mb = _vp.getRNA().get_listeBases().get(
+                                               listBase.get(i));
+                               if (mb.getElementStructure() != -1) {
+                                       styleBPs.add(mb.getStyleBP());
+                               }
+                       }
+                       VueBPThickness vbpt = new VueBPThickness(_vp, styleBPs);
+                       if (JOptionPane.showConfirmDialog(_vp, vbpt.getPanel(),
+                                       "Set base pair(s) thickness", JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
+                               vbpt.restoreThicknesses();
+                               _vp.repaint();
+                       }
+               } else
+                       return false;
+               return true;
+       }
+
+       private ArrayList<Integer> listSwitchType(String _type) {
+               if (_type.equals("helix"))
+                       return _vp.getRNA().findHelix(_vp.getNearestBase());
+               if (_type.equals("current")) {
+                       return _vp.getSelectionIndices();
+               }
+               if (_type.equals("allBases")) {
+                       return _vp.getRNA().findAll();
+               }
+               if (_type.equals("loop1")) {
+                       return _vp.getRNA().findLoopForward(_vp.getNearestBase());
+               }
+               if (_type.equals("loop2")) {
+                       return _vp.getRNA().findLoopBackward(_vp.getNearestBase());
+               }
+               if (_type.equals("stem"))
+                       return _vp.getRNA().findStem(_vp.getNearestBase());
+               if (_type.equals("base")) {
+                       ArrayList<Integer> list = new ArrayList<Integer>();
+                       list.add(_vp.getNearestBase());
+                       return list;
+               }
+               if (_type.equals("basepair") || _type.equals("bpcolor")
+                               || _type.equals("bp")) {
+                       ArrayList<Integer> list = new ArrayList<Integer>();
+                       int i = _vp.getNearestBase();
+                       list.add(i);
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       int j = mb.getElementStructure();
+                       if (mb.getElementStructure() != -1) {
+                               list.add(i);
+                               list.add(j);
+                       }
+                       return list;
+               }
+               if (_type.equals("5'"))
+                       return _vp.getRNA().findNonPairedBaseGroup(_vp.getNearestBase());
+               if (_type.equals("3'"))
+                       return _vp.getRNA().findNonPairedBaseGroup(_vp.getNearestBase());
+               if (_type.equals("bulge"))
+                       return _vp.getRNA().findNonPairedBaseGroup(_vp.getNearestBase());
+               if (_type.equals("all"))
+                       return _vp.getRNA().findAll();
+               return new ArrayList<Integer>();
+       }
+
+       private boolean optionLoop() {
+               return colorBases();
+       }
+
+       private boolean optionBase() {
+               if (_type.equals("baseChar")) {
+                       _vp.getVARNAUI().UISetBaseCharacter();
+                       return true;
+               } else {
+                       return colorBases();
+               }
+       }
+
+       private boolean optionBasePair() {
+               if (_type.equals("basepair")) {
+                       _vp.getVARNAUI().UIEditBasePair();
+                       return true;
+               } else if (_type.equals("bpcolor")) {
+                       _vp.getVARNAUI().UIColorBasePair();
+                       return true;
+               } else if (_type.equals("thickness")) {
+                       _vp.getVARNAUI().UIThicknessBasePair();
+                       return true;
+               }
+               return false;
+       }
+
+       
+       
+       private boolean optionView() {
+               if (_type.equals("background")) {
+                       _vp.getVARNAUI().UISetBackground();
+               } else if (_type.equals("shownc")) {
+                       _vp.getVARNAUI().UIToggleShowNCBP();
+               } else if (_type.equals("showbackbone")) {
+                       _vp.getVARNAUI().UIToggleDrawBackbone();
+               } else if (_type.equals("shownp")) {
+                       _vp.getVARNAUI().UIToggleShowNonPlanar();
+               } else if (_type.equals("spaceBetweenBases")) {
+                       _vp.getVARNAUI().UISetSpaceBetweenBases();
+               } else if (_type.equals("bpheightincrement")) {
+                       _vp.getVARNAUI().UISetBPHeightIncrement();
+               } else if (_type.equals("borderSize")) {
+                       _vp.getVARNAUI().UISetBorder();
+               } else if (_type.startsWith("zoom")) {
+                       if (_type.equals("zoom")) {
+                               _vp.getVARNAUI().UICustomZoom();
+                       } else {
+                               String factor = _type.substring("zoom".length());
+                               double pc = Integer.parseInt(factor);
+                               pc /= 100.0;
+                               _vp.setZoom(new Double(pc));
+                               _vp.repaint();
+                       }
+               } else if (_type.equals("rotation")) {
+                       _vp.getVARNAUI().UIGlobalRotation();
+               } else if (_type.equals("rescale")) {
+                       _vp.getVARNAUI().UIGlobalRescale();
+               } else
+                       return false;
+               return true;
+       }
+
+       
+       private boolean optionTitle() {
+               if (_type.equals("titleDisplay")) {
+                       _vp.getVARNAUI().UISetTitleFont();
+               } else if (_type.equals("setTitle")) {
+                       _vp.getVARNAUI().UISetTitle();
+               } else if (_type.equals("titleColor")) {
+                       _vp.getVARNAUI().UISetTitleColor();
+               } else
+                       return false;
+               return true;
+       }
+
+               
+       private boolean optionColorMap() {
+               if (_type.equals("toggleshowcolormap")) {
+                       _vp.getVARNAUI().UIToggleColorMap();
+               } else  if (_type.equals("colormapcaption")) {
+                       _vp.getVARNAUI().UISetColorMapCaption();
+               } else  if (_type.equals("colormapstyle")) {
+                       _vp.getVARNAUI().UISetColorMapStyle();
+               } else  if (_type.equals("colormaploadvalues")) {
+                       _vp.getVARNAUI().UILoadColorMapValues();
+               } else  if (_type.equals("colormapvalues")) {
+                       _vp.getVARNAUI().UISetColorMapValues();
+               } else
+                       return false;
+               return true;
+       }
+
+       private boolean optionRNADisplay() {
+               // les options d'affichages generales
+               if (_type.equals("gaspin")) {
+                       _vp.getVARNAUI().UIToggleGaspinMode();
+               } else if (_type.equals("backbone")) {
+                       _vp.getVARNAUI().UISetBackboneColor();
+               } else if (_type.equals("bonds")) {
+                       Color c = JColorChooser.showDialog(_vp, "Choose new bonds color",
+                                       _vp.getBackground());
+                       if (c != null) {
+                               _vp.setDefaultBPColor(c);
+                               _vp.repaint();
+                       }
+               } else if (_type.equals("basecolorforBP")) {
+                       if (_source != null) {
+                               if (_source instanceof JCheckBoxMenuItem) {
+                                       JCheckBoxMenuItem check = (JCheckBoxMenuItem) _source;
+                                       _vp.setUseBaseColorsForBPs(check.getState());
+                                       _vp.repaint();
+                               }
+                       }
+               } else if (_type.equals("bpstyle")) {
+                       _vp.getVARNAUI().UISetBPStyle();
+               } else if (_type.equals("specialbasecolored")) {
+                       _vp.getVARNAUI().UIToggleColorSpecialBases();
+               } else if (_type.equals("showwarnings")) {
+                       _vp.getVARNAUI().UIToggleShowWarnings();
+               } else if (_type.equals("dashbasecolored")) {
+                       _vp.getVARNAUI().UIToggleColorGapsBases();
+               } else if (_type.equals("numPeriod")) {
+                       _vp.getVARNAUI().UISetNumPeriod();
+               } else if (_type.equals("eachKind")) {
+                       if (_vp.getRNA().get_listeBases() != null) {
+                               _vp.getVARNAUI().UIBaseTypeColor();
+                       } else {
+                               _vp.emitWarning("No base");
+                       }
+               } else if (_type.equals("eachCouple")) {
+                       if (_vp.getRNA().get_listeBases() != null
+                                       && _vp.getRNA().get_listeBases().size() != 0) {
+                               _vp.getVARNAUI().UIBasePairTypeColor();
+                       } else {
+                               _vp.emitWarning("No base");
+                       }
+               } else if (_type.equals("eachBase")) {
+                       if (_vp.getRNA().get_listeBases() != null
+                                       && _vp.getRNA().get_listeBases().size() != 0) {
+                               _vp.getVARNAUI().UIBaseAllColor();
+                       } else {
+                               _vp.emitWarning("No base");
+                       }
+               } else if (_type.equals("specialBasesColor")) {
+                       _vp.getVARNAUI().UIPickSpecialBasesColor();
+               } else if (_type.equals("dashBasesColor")) {
+                       _vp.getVARNAUI().UIPickGapsBasesColor();
+               } else
+                       return colorBases();
+               return true;
+       }
+
+       private boolean optionImport() {
+               if (_type.equals("userInput")) {
+                       try {
+                               _vp.getVARNAUI().UIManualInput();
+                       } catch (ParseException e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionNonEqualLength e2) {
+                               errorDialog(e2);
+                       }
+               } else if (_type.equals("file")) {
+                       try {
+                               _vp.getVARNAUI().UIFile();
+                       } catch (ExceptionNonEqualLength e1) {
+                               errorDialog(e1);
+                       }
+               } else if (_type.equals("print")) {
+                       _vp.getVARNAUI().UIPrint();
+               } else if (_type.equals("about")) {
+                       _vp.getVARNAUI().UIAbout();
+               } else
+                       return false;
+               return true;
+       }
+
+       private boolean optionRedraw() {
+               if (_type.equals("reset")) {
+                       _vp.getVARNAUI().UIReset();
+               } else if (_type.equals("circular")) {
+                       _vp.getVARNAUI().UICircular();
+               } else if (_type.equals("radiate")) {
+                       _vp.getVARNAUI().UIRadiate();
+               } else if (_type.equals("naview")) {
+                       _vp.getVARNAUI().UINAView();
+               } else if (_type.equals("varnaview")) {
+                       _vp.getVARNAUI().UIVARNAView();
+               } else if (_type.equals("motifview")) {
+                       _vp.getVARNAUI().UIMOTIFView();
+               } else if (_type.equals("line")) {
+                       _vp.getVARNAUI().UILine();
+               } else if (_type.equals("flat")) {
+                       _vp.getVARNAUI().UIToggleFlatExteriorLoop();
+               } else
+                       return false;
+               return true;
+       }
+
+       private boolean optionExport() {
+               if (_type.equals("saveas")) {
+                       try {
+                               _vp.getVARNAUI().UISaveAs();
+                       } catch (ExceptionExportFailed e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionPermissionDenied e1) {
+                               errorDialog(e1);
+                       }
+               } else if (_type.equals("dbn")) {
+                       try {
+                               _vp.getVARNAUI().UISaveAsDBN();
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       } catch (ExceptionPermissionDenied e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("bpseq")) {
+                       try {
+                               _vp.getVARNAUI().UISaveAsBPSEQ();
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       } catch (ExceptionPermissionDenied e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("ct")) {
+                       try {
+                               _vp.getVARNAUI().UISaveAsCT();
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       } catch (ExceptionPermissionDenied e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("eps")) {
+                       try {
+                               _vp.getVARNAUI().UIExportEPS();
+                       } catch (ExceptionWritingForbidden e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("tikz")) {
+                       try {
+                               _vp.getVARNAUI().UIExportTIKZ();
+                       } catch (ExceptionWritingForbidden e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("xfig")) {
+                       try {
+                               _vp.getVARNAUI().UIExportXFIG();
+                       } catch (ExceptionWritingForbidden e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("svg")) {
+                       try {
+                               _vp.getVARNAUI().UIExportSVG();
+                       } catch (ExceptionWritingForbidden e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionExportFailed e) {
+                               errorDialog(e);
+                       }
+               } else if (_type.equals("jpeg")) {
+                       try {
+                               _vp.getVARNAUI().UIExportJPEG();
+                       } catch (ExceptionJPEGEncoding e1) {
+                               errorDialog(e1);
+                       } catch (ExceptionExportFailed e1) {
+                               errorDialog(e1);
+                       }
+               } else if (_type.equals("png")) {
+                       try {
+                               _vp.getVARNAUI().UIExportPNG();
+                       } catch (ExceptionExportFailed e1) {
+                               errorDialog(e1);
+                       }
+               } else
+                       return false;
+               return true;
+       }
+
+       /**
+        * Return the extension of a file, it means the string after the last dot of
+        * the file name
+        * 
+        * @param f
+        *            The file
+        * @return <code>null</code> if the file name have no dot<br>
+        *         <code>ext</code> if the file name contains a dot
+        */
+       public String getExtension(File f) {
+               String s = f.getName();
+               return getExtension(s);
+       }
+
+       /**
+        * Return the extension of a string, it means the string after the last dot
+        * of the path
+        * 
+        * @param s
+        *            The strnig o the path
+        * @return <code>null</code> if the path have no dot<br>
+        *         <code>ext</code> if the path contains a dot
+        */
+       public String getExtension(String s) {
+               String ext = null;
+               int i = s.lastIndexOf('.');
+
+               if (i > 0 && i < s.length() - 1) {
+                       ext = s.substring(i + 1).toLowerCase();
+               }
+               return ext;
+       }
+
+       /**
+        * Open an error message dialog with the exception message
+        * 
+        * @param e1
+        *            The <code>Exception</code>
+        */
+       public void errorDialog(Exception e1) {
+               if (_vp.isErrorsOn())
+                       JOptionPane.showMessageDialog(_vp, e1.getMessage(), "VARNA Error",
+                                       JOptionPane.ERROR_MESSAGE);
+       }
+
+       public void onStructureRedrawn() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onWarningEmitted(String s) {
+               if (_vp.isErrorsOn())
+                       JOptionPane.showMessageDialog(_vp,s, "VARNA Warning",
+                                       JOptionPane.ERROR_MESSAGE);
+       }
+
+       public void onLoad(String path) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onLoaded() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onUINewStructure(VARNAConfig v, RNA r) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onZoomLevelChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void onTranslationChanged() {
+               // TODO Auto-generated method stub
+               
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurMolette.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurMolette.java
new file mode 100644 (file)
index 0000000..5783525
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Point;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+
+/**
+ * Listener which control the zoom-in and zoom-out with mouse wheel.
+ * 
+ * @author DARTY Kevin
+ */
+public class ControleurMolette implements MouseWheelListener {
+
+       private VARNAPanel _vp;
+       public ControleurMolette(VARNAPanel vuep) {
+               _vp = vuep;
+       }
+
+       public void mouseWheelMoved(MouseWheelEvent e) {
+               if (_vp.isFocusOwner())
+               {
+                       // Roulement vers le haut => zoom in
+                       if (e.getWheelRotation() == -1) {
+                               _vp.getVARNAUI().UIZoomIn();
+                       }
+                       // Roulement vers le bas => zoom out
+                       else {
+                               _vp.getVARNAUI().UIZoomOut();
+                       }
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurNumPeriod.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurNumPeriod.java
new file mode 100644 (file)
index 0000000..5a6aca8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueNumPeriod;
+
+
+public class ControleurNumPeriod implements ChangeListener {
+
+       private VueNumPeriod _vnp;
+
+       public ControleurNumPeriod(VueNumPeriod vnp) {
+               _vnp = vnp;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vnp.get_vp().setNumPeriod(_vnp.getNumPeriod());
+               _vnp.get_vp().repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurScriptParser.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurScriptParser.java
new file mode 100644 (file)
index 0000000..4688f91
--- /dev/null
@@ -0,0 +1,551 @@
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Color;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation.ChemProbAnnotationType;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class ControleurScriptParser {
+    private static String SCRIPT_ERROR_PREFIX = "Error";       
+
+
+       private static class Command{
+               Function _f;
+               Vector<Argument> _argv; 
+               public Command(Function f, Vector<Argument> argv)
+               {
+                       _f = f;
+                       _argv = argv;
+               }
+       }; 
+
+       private static abstract class Argument{
+               ArgumentType _t;
+               
+               public Argument(ArgumentType t)
+               {
+                       _t = t;
+               }
+               
+               public ArgumentType getType()
+               {
+                       return _t;
+               }
+               
+               public abstract String toString();
+               
+       }; 
+       
+
+       private static class NumberArgument extends Argument{
+               Number _val; 
+               public NumberArgument(Number val)
+               {
+                       super(ArgumentType.NUMBER_TYPE);
+                       _val = val;
+               }
+               public Number getNumber()
+               {
+                       return _val;
+               }
+               public String toString()
+               {
+                       return _val.toString();
+               }
+       }; 
+       
+       private static class ColorArgument extends Argument{
+               Color _val; 
+               public ColorArgument(Color val)
+               {
+                       super(ArgumentType.COLOR_TYPE);
+                       _val = val;
+               }
+               public Color getColor()
+               {
+                       return _val;
+               }
+               public String toString()
+               {
+                       return _val.toString();
+               }
+       }; 
+
+       private static class BooleanArgument extends Argument{
+               boolean _val; 
+               public BooleanArgument(boolean val)
+               {
+                       super(ArgumentType.BOOLEAN_TYPE);
+                       _val = val;
+               }
+               public boolean getBoolean()
+               {
+                       return _val;
+               }
+               public String toString()
+               {
+                       return ""+_val;
+               }
+       }; 
+
+       private static class StringArgument extends Argument{
+               String _val; 
+               public StringArgument(String val)
+               {
+                       super(ArgumentType.STRING_TYPE);
+                       _val = val;
+               }
+               public String toString()
+               {
+                       return _val.toString();
+               }
+       }; 
+
+       private static class ArrayArgument extends Argument{
+               Vector<Argument> _val; 
+               public ArrayArgument(Vector<Argument> val)
+               {
+                       super(ArgumentType.ARRAY_TYPE);
+                       _val = val;
+               }
+               public int getSize()
+               {
+                       return _val.size();
+               }
+               public Argument getArgument(int i)
+               {
+                       return _val.get(i);
+               }
+               public String toString()
+               {
+                       return _val.toString();
+               }
+       }; 
+       
+       
+       private enum ArgumentType{
+               STRING_TYPE,
+               NUMBER_TYPE,
+               BOOLEAN_TYPE,
+               ARRAY_TYPE,
+               COLOR_TYPE
+       };
+    
+    
+    private enum Function{
+               ADD_CHEM_PROB("addchemprob",new ArgumentType[] {ArgumentType.NUMBER_TYPE,ArgumentType.NUMBER_TYPE,ArgumentType.STRING_TYPE,ArgumentType.NUMBER_TYPE,ArgumentType.COLOR_TYPE,ArgumentType.BOOLEAN_TYPE}),
+       ERASE_SEQ("eraseseq",new ArgumentType[] {}),
+               RESET_CHEM_PROB("resetchemprob",new ArgumentType[] {}),
+               SET_COLOR_MAP_MIN("setcolormapminvalue",new ArgumentType[]{ArgumentType.NUMBER_TYPE}),
+               SET_COLOR_MAP_MAX("setcolormapmaxvalue",new ArgumentType[]{ArgumentType.NUMBER_TYPE}),
+               SET_COLOR_MAP("setcolormap",new ArgumentType[]{ArgumentType.STRING_TYPE}),
+               SET_CUSTOM_COLOR_MAP("setcustomcolormap",new ArgumentType[]{ArgumentType.ARRAY_TYPE}),
+               SET_SEQ("setseq",new ArgumentType[]{ArgumentType.STRING_TYPE}),
+               SET_STRUCT("setstruct",new ArgumentType[]{ArgumentType.STRING_TYPE}),
+               SET_STRUCT_SMOOTH("setstructsmooth",new ArgumentType[] {ArgumentType.STRING_TYPE}),
+               SET_TITLE("settitle",new ArgumentType[] {ArgumentType.STRING_TYPE}),
+               SET_RNA("setrna",new ArgumentType[]{ArgumentType.STRING_TYPE,ArgumentType.STRING_TYPE}),
+               SET_RNA_SMOOTH("setrnasmooth",new ArgumentType[]{ArgumentType.STRING_TYPE,ArgumentType.STRING_TYPE}),
+               SET_SELECTION("setselection",new ArgumentType[]{ArgumentType.ARRAY_TYPE}),
+               SET_VALUES("setvalues",new ArgumentType[]{ArgumentType.ARRAY_TYPE}),
+               TOGGLE_SHOW_COLOR_MAP("toggleshowcolormap",new ArgumentType[]{}),
+               REDRAW("redraw",new ArgumentType[] {ArgumentType.STRING_TYPE}),
+               UNKNOWN("N/A",new ArgumentType[] {});
+               
+               String _funName;
+               ArgumentType[] _args;
+               Function(String funName, ArgumentType[] args)
+               {
+                       _funName = funName;
+                       _args = args;
+               }
+               ArgumentType[] getPrototype()
+               {
+                       return this._args;
+               }
+               String getFunName()
+               {
+                       return this._funName;
+               }
+               
+       };
+       
+       private static Hashtable<String,Function> _name2Fun = new Hashtable<String,Function>();  
+       private static Hashtable<Function,ArgumentType[]> _fun2Prot = new Hashtable<Function,ArgumentType[]>();
+       
+       
+       private static void initFunctions()
+       {
+               if (_name2Fun.size()>0)
+               { return; }
+               Function[] funs = Function.values();
+               for(int i=0;i<funs.length;i++)
+               {
+                       Function fun = funs[i];
+                       _name2Fun.put(fun.getFunName(),fun);
+                       _fun2Prot.put(fun,fun.getPrototype());
+               }                       
+       }
+       
+       private static Function getFunction(String f)
+       {
+               String s = f.trim().toLowerCase();
+               if (_name2Fun.containsKey(s))
+                       return _name2Fun.get(s);
+               return Function.UNKNOWN;
+       }
+
+       private static ArgumentType[] getPrototype(Function f)
+       {
+               if (_fun2Prot.containsKey(f))
+                       return _fun2Prot.get(f);
+               return new ArgumentType[0];
+       }       
+       
+    public static void executeScript(VARNAPanel vp, String cmdtxt) throws Exception
+    {
+       Vector<Command> cmds = parseScript(cmdtxt);
+       for(int i=0;i<cmds.size();i++)
+       {
+               Command cmd = cmds.get(i);
+               switch(cmd._f)
+               {
+                       case ADD_CHEM_PROB:
+                       {
+                               int from = (int)((NumberArgument) cmd._argv.get(0)).getNumber().intValue();
+                               int to = (int)((NumberArgument) cmd._argv.get(1)).getNumber().intValue();
+                               ChemProbAnnotationType t = ChemProbAnnotation.annotTypeFromString(((StringArgument) cmd._argv.get(2)).toString());
+                               double intensity = ((NumberArgument) cmd._argv.get(3)).getNumber().doubleValue();
+                               Color c = ((ColorArgument) cmd._argv.get(4)).getColor();
+                               boolean out = ((BooleanArgument) cmd._argv.get(5)).getBoolean();
+                               vp.getRNA().addChemProbAnnotation(new ChemProbAnnotation(
+                                               vp.getRNA().getBaseAt(from),
+                                               vp.getRNA().getBaseAt(to),
+                                               t,
+                                               intensity,
+                                               c,
+                                               out));
+                       }
+                       break;
+                       case ERASE_SEQ:
+                       {
+                               vp.eraseSequence();
+                       }
+                       break;
+                       case RESET_CHEM_PROB:
+                       {
+                               vp.getRNA().clearChemProbAnnotations();
+                               vp.repaint();
+                       }
+                       break;
+                       case SET_COLOR_MAP_MIN:
+                       {
+                               vp.setColorMapMinValue(((NumberArgument) cmd._argv.get(0)).getNumber().doubleValue());
+                       }
+                       break;
+                       case SET_COLOR_MAP_MAX:
+                       {
+                               vp.setColorMapMaxValue(((NumberArgument) cmd._argv.get(0)).getNumber().doubleValue());
+                       }
+                       break;
+                       case SET_COLOR_MAP:
+                       {
+                               vp.setColorMap(ModeleColorMap.parseColorMap(cmd._argv.get(0).toString()));
+                       }
+                       break;
+                       case SET_CUSTOM_COLOR_MAP:
+                       {
+                               ModeleColorMap cm = new ModeleColorMap();
+                               //System.out.println("a"+cmd._argv.get(0));
+                               ArrayArgument arg = (ArrayArgument) cmd._argv.get(0);
+                               for (int j=0;j<arg.getSize();j++)
+                               {
+                                       Argument a = arg.getArgument(j);
+                                       if (a._t==ArgumentType.ARRAY_TYPE)
+                                       { 
+                                       //System.out.println("%");
+                                               ArrayArgument aarg = (ArrayArgument) a; 
+                                               if (aarg.getSize()==2)
+                                               {
+                                                       Argument a1 = aarg.getArgument(0);
+                                                       Argument a2 = aarg.getArgument(1);
+                                               //System.out.println("& |"+a1+"| ["+a1.getType()+"] |"+a2+"| ["+a2.getType()+"]");
+                                                       if ((a1.getType()==ArgumentType.NUMBER_TYPE)&&(a2.getType()==ArgumentType.COLOR_TYPE))
+                                                       {
+                                               //System.out.println("+");
+                                                               cm.addColor(((NumberArgument)a1).getNumber().doubleValue(),((ColorArgument)a2).getColor());
+                                                       }
+                                               }
+                                       }
+                               }                               
+                               vp.setColorMap(cm);
+                       }
+                       break;
+                       case SET_RNA:
+                       {
+                               String seq = cmd._argv.get(0).toString();
+                               String str = cmd._argv.get(1).toString();
+                               vp.drawRNA(seq, str);
+                       }
+                       break;
+                       case SET_RNA_SMOOTH:
+                       {
+                               String seq = cmd._argv.get(0).toString();
+                               String str = cmd._argv.get(1).toString();
+                               vp.drawRNAInterpolated(seq, str);
+                               vp.repaint();
+                       }
+                       break;
+                       case SET_SELECTION:
+                       {
+                               ArrayArgument arg = (ArrayArgument) cmd._argv.get(0);
+                               ArrayList<Integer> vals = new ArrayList<Integer>();
+                               for (int j=0;j<arg.getSize();j++)
+                               {
+                                       Argument a = arg.getArgument(j);
+                                       if (a._t==ArgumentType.NUMBER_TYPE)
+                                       { 
+                                               NumberArgument narg = (NumberArgument) a; 
+                                               vals.add(narg.getNumber().intValue()); 
+                                       }
+                               }                               
+                               vp.setSelection(vals);
+                               vp.repaint();
+                       }
+                       break;  
+                       case SET_SEQ:
+                       {
+                               String seq = cmd._argv.get(0).toString();
+                               vp.setSequence(seq);
+                       }
+                       break;
+                       case SET_STRUCT:
+                       {
+                               String seq = vp.getRNA().getSeq();
+                               String str = cmd._argv.get(0).toString();
+                               vp.drawRNA(seq, str);
+                       }
+                       break;
+                       case SET_STRUCT_SMOOTH:
+                       {
+                               String seq = vp.getRNA().getSeq();
+                               String str = cmd._argv.get(0).toString();
+                               vp.drawRNAInterpolated(seq, str);
+                               vp.repaint();
+                       }
+                       break;
+                       case SET_TITLE:
+                       {
+                               vp.setTitle(cmd._argv.get(0).toString());
+                       }
+                       break;
+                       case SET_VALUES:
+                       {
+                               ArrayArgument arg = (ArrayArgument) cmd._argv.get(0);
+                               Double[] vals = new Double[arg.getSize()];
+                               for (int j=0;j<arg.getSize();j++)
+                               {
+                                       Argument a = arg.getArgument(j);
+                                       if (a._t==ArgumentType.NUMBER_TYPE)
+                                       { 
+                                               NumberArgument narg = (NumberArgument) a; 
+                                               vals[j] = narg.getNumber().doubleValue(); 
+                                       }
+                               }                               
+                               vp.setColorMapValues(vals);
+                               vp.repaint();
+                       }
+                       break;
+                       case REDRAW:
+                       {
+                               int mode = -1;
+                               String modeStr = cmd._argv.get(0).toString().toLowerCase(); 
+                               if (modeStr.equals("radiate"))
+                                       mode = RNA.DRAW_MODE_RADIATE;
+                               else if (modeStr.equals("circular"))
+                                       mode = RNA.DRAW_MODE_CIRCULAR;
+                               else if (modeStr.equals("naview"))
+                                       mode = RNA.DRAW_MODE_NAVIEW;
+                               else if (modeStr.equals("linear"))
+                                       mode = RNA.DRAW_MODE_LINEAR;
+                               if (mode != -1)
+                                 vp.drawRNA(vp.getRNA(), mode);
+                       }
+                       break;
+                       case TOGGLE_SHOW_COLOR_MAP:
+                       {
+                               vp.setColorMapVisible(!vp.getColorMapVisible());
+                       }
+                       break;
+                       default:
+                               throw new Exception(SCRIPT_ERROR_PREFIX+": Method '"+cmd._f+"' unimplemented.");
+               }
+               vp.repaint();
+       }
+    }
+
+       
+       
+       private static Color parseColor(String s)
+       {
+               Color result = null;
+               try {result = Color.decode(s); }
+               catch (Exception e) {}
+               return result;
+       }
+
+       private static Boolean parseBoolean(String s)
+       {
+               Boolean result = null;
+               if (s.toLowerCase().equals("true"))
+                       result = new Boolean(true);
+               if (s.toLowerCase().equals("false"))
+                       result = new Boolean(false);
+               return result;
+       }
+
+       
+       private static Vector<Argument> parseArguments(StreamTokenizer st, boolean parType) throws Exception
+       {
+               Vector<Argument> result = new Vector<Argument>();
+               while((st.ttype!=')' && parType) || (st.ttype!=']' && !parType))
+               {
+                       st.nextToken();
+                         //System.out.println(""+ (parType?"Par.":"Bra.")+" "+(char)st.ttype);
+                       switch(st.ttype)
+                       {
+                         case(StreamTokenizer.TT_NUMBER):
+                         {
+                                 result.add(new NumberArgument(st.nval));
+                         }
+                         break;
+                         case(StreamTokenizer.TT_WORD):
+                         {
+                                 Color c = parseColor(st.sval);
+                                 if (c!=null)
+                                 {
+                                        result.add(new ColorArgument(c));
+                                 }
+                                 else
+                                 {
+                                         Boolean b = parseBoolean(st.sval);
+                                         if (b!=null)
+                                         {
+                                                result.add(new BooleanArgument(b));
+                                         }
+                                         else
+                                         {
+                                                result.add(new StringArgument(st.sval));                                         
+                                         }
+                                 }
+                         }
+                         break;
+                         case('"'):
+                         {
+                                 result.add(new StringArgument(st.sval));
+                         }
+                         break;
+                         case('['):
+                         {
+                                 result.add(new ArrayArgument(parseArguments(st, false)));
+                         }
+                         break;
+                         case('('):
+                         {
+                                 result.add(new ArrayArgument(parseArguments(st, true)));
+                         }
+                         break;
+                         case(')'):
+                         {
+                                 if (parType)
+                                   return result;
+                                 else
+                                       throw new Exception(SCRIPT_ERROR_PREFIX+": Opening "+(parType?"parenthesis":"bracket")+" matched with a closing "+(!parType?"parenthesis":"bracket"));                                    
+                         }
+                         case(']'):
+                         {
+                                 if (!parType)
+                                   return result;
+                                 else
+                                       throw new Exception(SCRIPT_ERROR_PREFIX+": Opening "+(parType?"parenthesis":"bracket")+" matched with a closing "+(!parType?"parenthesis":"bracket"));                                    
+                         }
+                         case(','):
+                                 break;
+                         case(StreamTokenizer.TT_EOF):
+                         {
+                                 throw new Exception(SCRIPT_ERROR_PREFIX+": Unmatched opening "+(parType?"parenthesis":"bracket"));
+                         }
+                         
+                       }
+               }
+               return result;
+       }
+       
+       
+       private static Command parseCommand(String cmd) throws Exception
+       {
+               int cut = cmd.indexOf("(");
+               if (cut==-1)
+               {
+                       throw new Exception(SCRIPT_ERROR_PREFIX+": Syntax error");
+               }
+               String fun = cmd.substring(0,cut);
+               Function f  = getFunction(fun);
+               if (f==Function.UNKNOWN)
+               { throw new Exception(SCRIPT_ERROR_PREFIX+": Unknown function \""+fun+"\""); }
+               StreamTokenizer st = new StreamTokenizer(new StringReader(cmd.substring(cut+1)));
+               st.eolIsSignificant(false);
+               st.parseNumbers();
+               st.quoteChar('\"');
+               st.ordinaryChar('=');
+               st.ordinaryChar(',');
+               st.ordinaryChar('[');
+               st.ordinaryChar(']');
+               st.ordinaryChar('(');
+               st.ordinaryChar(')');
+               st.wordChars('#', '#');
+               Vector<Argument> argv = parseArguments(st,true);
+               checkArgs(f,argv);
+               Command result = new Command(f,argv); 
+               return result;
+       }
+       
+       private static boolean checkArgs(Function f, Vector<Argument> argv) throws Exception
+       {
+               ArgumentType[] argtypes = getPrototype(f);
+               if (argtypes.length!=argv.size())
+                       throw new Exception(SCRIPT_ERROR_PREFIX+": Wrong number of argument for function \""+f+"\".");
+               for (int i=0;i<argtypes.length;i++)
+               {
+                       if (argtypes[i] != argv.get(i)._t)
+                       {
+                               throw new Exception(SCRIPT_ERROR_PREFIX+": Bad type ("+argtypes[i]+"!="+argv.get(i)._t+") for argument #"+(i+1)+" in function \""+f+"\".");
+                       }
+               }
+               return true;
+       }
+       
+       private static Vector<Command> parseScript(String cmd) throws Exception
+       {
+               initFunctions();
+               Vector<Command> cmds = new Vector<Command>();
+               String[] data = cmd.split(";");
+               for (int i=0;i<data.length;i++)
+               {
+                       cmds.add(parseCommand(data[i].trim()));
+               }
+               return cmds;
+       }
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurSelectionHighlight.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurSelectionHighlight.java
new file mode 100644 (file)
index 0000000..11e0134
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.swing.JMenuItem;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+
+public class ControleurSelectionHighlight implements ChangeListener {
+       private Collection<? extends ModeleBase> _selection;
+       private VARNAPanel _target;
+       private JMenuItem _parent;
+
+       public ControleurSelectionHighlight(int elem, VARNAPanel v, JMenuItem parent) {
+               ArrayList<Integer> sel = new ArrayList<Integer>();
+               sel.add(elem);
+               _selection = v.getRNA().getBasesAt(sel);
+               _target = v;
+               _parent = parent;
+       }
+
+       public ControleurSelectionHighlight(Vector<Integer> sel, VARNAPanel v,
+                       JMenuItem parent) {
+               this(new ArrayList<Integer>(sel), v, parent);
+       }
+
+       public ControleurSelectionHighlight(ArrayList<Integer> sel, VARNAPanel v,
+                       JMenuItem parent) {
+               this(v.getRNA().getBasesAt(sel),v,parent);
+       }
+       
+       public ControleurSelectionHighlight(Collection<? extends ModeleBase> sel, VARNAPanel v,
+                       JMenuItem parent) {
+               _selection = sel;
+               _target = v;
+               _parent = parent;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               if (_parent.isSelected()) {
+                       _target.saveSelection();
+                       _target.setSelection(_selection);
+               } else {
+                       _target.restoreSelection();
+               }
+
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurSliderLabel.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurSliderLabel.java
new file mode 100644 (file)
index 0000000..c78544e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.JLabel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+public class ControleurSliderLabel implements ChangeListener {
+
+       private JLabel _l;
+       private double _factor = 1.0;
+
+       public ControleurSliderLabel(JLabel zoomAmountValueLabel) {
+               _l = zoomAmountValueLabel;
+               _factor = 1.0;
+       }
+
+       public ControleurSliderLabel(JLabel zoomAmountValueLabel, boolean percent) {
+               _l = zoomAmountValueLabel;
+               if (percent)
+                       _factor = 0.01;
+       }
+
+       public ControleurSliderLabel(JLabel zoomAmountValueLabel, double factor) {
+               _l = zoomAmountValueLabel;
+               _factor = factor;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               double value = ((JSlider) e.getSource()).getValue();
+               value *= _factor;
+               _l.setText(String.valueOf(value));
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurSpaceBetweenBases.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurSpaceBetweenBases.java
new file mode 100644 (file)
index 0000000..2ae40a1
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueSpaceBetweenBases;
+
+
+public class ControleurSpaceBetweenBases implements ChangeListener {
+
+       private VueSpaceBetweenBases _vsbb;
+
+       public ControleurSpaceBetweenBases(VueSpaceBetweenBases vsbb) {
+               _vsbb = vsbb;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vsbb.get_vp().setSpaceBetweenBases(_vsbb.getSpace());
+               _vsbb.get_vp().drawRNA();
+               _vsbb.get_vp().repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurTableAnnotations.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurTableAnnotations.java
new file mode 100644 (file)
index 0000000..5db2797
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import javax.swing.JTable;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.views.VueAnnotation;
+import fr.orsay.lri.varna.views.VueChemProbAnnotation;
+import fr.orsay.lri.varna.views.VueHighlightRegionEdit;
+
+
+/**
+ * Mouse action and motion listener for AnnotationTableModel
+ * 
+ * @author Darty@lri.fr
+ * 
+ */
+public class ControleurTableAnnotations implements MouseListener,
+               MouseMotionListener {
+       public static final int REMOVE = 0, EDIT = 1;
+       private JTable _table;
+       private VARNAPanel _vp;
+       private int _type;
+
+       /**
+        * 
+        * @param table
+        * @param vp
+        * @param type
+        *            : REMOVE = 0, EDIT = 1
+        */
+       public ControleurTableAnnotations(JTable table, VARNAPanel vp, int type) {
+               _table = table;
+               _vp = vp;
+               _type = type;
+       }
+
+       public void mouseClicked(MouseEvent arg0) {
+               switch (_type) {
+               case EDIT:
+                       edit();
+                       break;
+               case REMOVE:
+                       remove();
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       /**
+        * if remove case
+        */
+       private void remove() {
+               _vp.set_selectedAnnotation(null);
+               Object o = _table.getValueAt(_table.getSelectedRow(), 0);
+               if (o instanceof TextAnnotation) {
+                       if (!_vp.removeAnnotation((TextAnnotation) o))
+                               _vp.errorDialog(new Exception("Impossible de supprimer"));
+                       _table.setValueAt("Deleted!", _table.getSelectedRow(), 0);
+               }
+               else if (o instanceof ChemProbAnnotation) {
+                       _vp.getRNA().removeChemProbAnnotation((ChemProbAnnotation) o);
+                       _table.setValueAt("Deleted!", _table.getSelectedRow(), 0);
+               }
+               else if (o instanceof HighlightRegionAnnotation) {
+                       _vp.getRNA().removeHighlightRegion((HighlightRegionAnnotation) o);
+                       _table.setValueAt("Deleted!", _table.getSelectedRow(), 0);
+               }
+               _vp.repaint();
+       }
+
+       /**
+        * if edit case
+        */
+       private void edit() {
+               Object o = _table.getValueAt(_table
+                               .getSelectedRow(), 0);
+               if (o instanceof TextAnnotation)
+               {
+               TextAnnotation textAnnot = (TextAnnotation) o; 
+               VueAnnotation vueAnnotation;
+               vueAnnotation = new VueAnnotation(_vp, textAnnot, false);
+               vueAnnotation.show();
+               }else if (o instanceof HighlightRegionAnnotation)
+               {
+                       HighlightRegionAnnotation annot = (HighlightRegionAnnotation) o; 
+                       HighlightRegionAnnotation an = annot.clone(); 
+                       VueHighlightRegionEdit vueAnnotation = new VueHighlightRegionEdit(_vp,annot);
+                       if (!vueAnnotation.show())
+                       {
+                               annot.setBases(an.getBases());
+                               annot.setFillColor(an.getFillColor());
+                               annot.setOutlineColor(an.getOutlineColor());
+                               annot.setRadius(an.getRadius());
+                       }
+               }else if (o instanceof ChemProbAnnotation)
+               {
+                       ChemProbAnnotation annot = (ChemProbAnnotation) o; 
+                       ChemProbAnnotation an = annot.clone(); 
+                       VueChemProbAnnotation vueAnnotation = new VueChemProbAnnotation(_vp,annot);
+                       if (!vueAnnotation.show())
+                       {
+                               annot.setColor(an.getColor());
+                               annot.setIntensity(an.getIntensity());
+                               annot.setType(an.getType());
+                               annot.setOut(an.isOut());
+                       }
+               }
+
+       }
+
+       public void mouseEntered(MouseEvent arg0) {
+       }
+
+       public void mouseExited(MouseEvent arg0) {
+               _vp.set_selectedAnnotation(null);
+               _vp.repaint();
+       }
+
+       public void mousePressed(MouseEvent arg0) {
+       }
+
+       public void mouseReleased(MouseEvent arg0) {
+       }
+
+       public void mouseDragged(MouseEvent arg0) {
+       }
+
+       /**
+        * update selected annotation
+        */
+       public void mouseMoved(MouseEvent arg0) {
+               if (_table.rowAtPoint(arg0.getPoint()) < 0)
+                       return;
+               Object o = _table.getValueAt(_table.rowAtPoint(arg0.getPoint()), 0);
+               if (o.getClass().equals(TextAnnotation.class)
+                               && o != _vp.get_selectedAnnotation()) {
+                       _vp.set_selectedAnnotation((TextAnnotation) o);
+                       _vp.repaint();
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurVARNAPanelKeys.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurVARNAPanelKeys.java
new file mode 100644 (file)
index 0000000..8dc6eac
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Point;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Point2D;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.views.VueUI;
+
+
+/**
+ * VARNAPanel Shortcuts Controller
+ * 
+ * @author darty
+ * 
+ */
+public class ControleurVARNAPanelKeys implements KeyListener, FocusListener {
+
+       private VARNAPanel _vp;
+       
+
+       public ControleurVARNAPanelKeys(VARNAPanel vp) {
+               _vp = vp;
+       }
+
+       public void mouseClicked(MouseEvent e) {
+       }
+
+       public void mouseEntered(MouseEvent e) {
+               // prise du focus
+               //_vp.requestFocus();
+               
+       }
+
+       public void mouseExited(MouseEvent e) {
+       }
+
+       public void mousePressed(MouseEvent e) {
+       }
+
+       public void mouseReleased(MouseEvent e) {
+       }
+
+       public void keyPressed(KeyEvent e) {
+               boolean controlDown = (e.getModifiersEx() & (KeyEvent.CTRL_DOWN_MASK)) == KeyEvent.CTRL_DOWN_MASK;
+               boolean shiftDown = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) == KeyEvent.SHIFT_DOWN_MASK;
+               boolean altDown = (e.getModifiersEx() & (KeyEvent.ALT_DOWN_MASK)) == KeyEvent.ALT_DOWN_MASK;
+               VueUI ui = _vp.getVARNAUI();
+               try {
+                       switch (e.getKeyCode()) {
+                       case (KeyEvent.VK_A):
+                               if (controlDown) {
+                                       ui.UIAbout();
+                               }
+                               break;
+                       case (KeyEvent.VK_B):
+                               if (controlDown) {
+                                       ui.UISetBorder();
+                               }
+                               else
+                                       if (altDown) {
+                                               ui.UIToggleDrawBackbone();
+                                       }
+                               break;
+                       case (KeyEvent.VK_C):
+                               if (shiftDown && controlDown) {
+                                       ui.UISetColorMapCaption();
+                               }
+                               break;
+                       case (KeyEvent.VK_D):
+                               if (controlDown) {
+                                       if (shiftDown) {
+                                               ui.UIPickGapsBasesColor();
+                                       } else {
+                                               ui.UIToggleColorGapsBases();
+                                       }
+                               }
+                               break;
+                       case (KeyEvent.VK_E):
+                               if (controlDown) {
+                                       ui.UIToggleShowNonPlanar();
+                               }
+                               break;
+                       case (KeyEvent.VK_F):
+                               if (controlDown) {
+                                       ui.UIToggleFlatExteriorLoop();
+                               }
+                               break;
+                       case (KeyEvent.VK_G):
+                               if (controlDown) {
+                                       ui.UISetBackground();
+                               }
+                               else if (!shiftDown && altDown) {
+                                 ui.UIToggleGaspinMode();
+                               }
+                               break;
+                       case (KeyEvent.VK_H):
+                               if (controlDown && !shiftDown) {
+                                       ui.UISetBPHeightIncrement();
+                               }
+                               else if (controlDown && shiftDown)
+                               {
+                                       Point2D.Double p = _vp.getLastSelectedPosition();
+                                       ui.UIAnnotationsAddPosition((int)p.x,(int)p.y);
+                               }
+                               break;
+                       case (KeyEvent.VK_J):
+                               if (controlDown) {
+                                       if (shiftDown) {
+                                               ui.UIPickSpecialBasesColor();
+                                       } else {
+                                               ui.UIToggleColorSpecialBases();
+                                       }
+                               }
+                               break;
+                       case (KeyEvent.VK_K):
+                               if (controlDown && shiftDown) {
+                                       ui.UILoadColorMapValues();
+                               }
+                               else if (controlDown) {
+                                       ui.UISetBackboneColor();
+                               }
+                               break;
+                       case (KeyEvent.VK_L):
+                               if (shiftDown && controlDown) {
+                                       ui.UIToggleColorMap();
+                               } else if (controlDown)
+                               {
+                                       ui.UISetColorMapStyle();
+                               } else if (shiftDown)
+                               {
+                                       ui.UISetColorMapValues();
+                               } 
+                               break;
+                       case (KeyEvent.VK_M):
+                               if (controlDown) {
+                                       ui.UISetNumPeriod();
+                               } else if (shiftDown && altDown) {
+                                       ui.UIToggleModifiable();
+                               }
+                               break;
+                       case (KeyEvent.VK_N):
+                               if (controlDown) {
+                                       ui.UIManualInput();
+                               }
+                               break;
+                       case (KeyEvent.VK_O):
+                               if (controlDown) {
+                                       ui.UIFile();
+                               }
+                               break;
+                       case (KeyEvent.VK_P):
+                               if (controlDown && shiftDown) {
+                                       ui.UISetBPStyle();
+                               }
+                               else if (controlDown && !shiftDown) {
+                                       ui.UIPrint();
+                               }
+                               break;
+                       case (KeyEvent.VK_Q):
+                               if      (controlDown && !shiftDown && !altDown) {
+                                       _vp.getVARNAUI().UIAutoAnnotateHelices();
+                               }
+                               else if (controlDown && shiftDown && !altDown) {
+                                       _vp.getVARNAUI().UIAutoAnnotateTerminalLoops();                                 
+                               }
+                               else if (!controlDown && shiftDown && altDown) {
+                                       _vp.getVARNAUI().UIAutoAnnotateInteriorLoops();                                 
+                               }
+                               else if (controlDown && !shiftDown && altDown) {
+                                       _vp.getVARNAUI().UIAutoAnnotateStrandEnds();                                    
+                               }
+                               break;
+                       case (KeyEvent.VK_R):
+                               if (controlDown) {
+                                       if (shiftDown) {
+                                               ui.UIReset();
+                                       } else {
+                                               ui.UIGlobalRotation();
+                                       }
+                               }
+                               break;
+                       case (KeyEvent.VK_S):
+                               if (controlDown) {
+                                       if (shiftDown) {
+                                               ui.UISetSpaceBetweenBases();
+                                       } else {
+                                               ui.UISaveAs();
+                                       }
+                               }
+                               break;
+                       case (KeyEvent.VK_T):
+                               if (controlDown) {
+                                       if (shiftDown) {
+                                               ui.UISetTitleFont();
+                                       } else if (altDown) {
+                                               ui.UISetTitleColor();
+                                       } else {
+                                               ui.UISetTitle();
+                                       }
+                               }
+                               break;
+                       case (KeyEvent.VK_U):
+                               if (controlDown && !shiftDown && !altDown) {
+                                       _vp.getVARNAUI().UIBaseTypeColor();
+                               } else if (!controlDown && shiftDown && !altDown) {
+                                       _vp.getVARNAUI().UIBasePairTypeColor();
+                               } else if (!controlDown && !shiftDown && altDown) {
+                                       _vp.getVARNAUI().UIBaseAllColor();
+                               }
+                               break;
+                       case (KeyEvent.VK_W):
+                               if (controlDown) {
+                                       ui.UIToggleShowNCBP();
+                               }
+                               break;
+                       case (KeyEvent.VK_X):
+                               if (controlDown) {
+                                       ui.UIExport();
+                               }
+                               break;
+                       case (KeyEvent.VK_Y):
+                               if (controlDown) {
+                                       ui.UIRedo();
+                               }
+                       break;
+
+                       case (KeyEvent.VK_Z):
+                               if (controlDown && !shiftDown) {
+                                       ui.UIUndo();
+                               }
+                               else if (controlDown && shiftDown) {
+                                       ui.UIRedo();
+                               }
+                               else if (!controlDown && !shiftDown) {
+                                       ui.UICustomZoom();
+                               }
+                               break;
+                       case (KeyEvent.VK_1):
+                               if (controlDown) {
+                                       ui.UILine();
+                               }
+                               break;
+                       case (KeyEvent.VK_2):
+                               if (controlDown) {
+                                       ui.UICircular();
+                               }
+                               break;
+                       case (KeyEvent.VK_3):
+                               if (controlDown) {
+                                       ui.UIRadiate();
+                               }
+                               break;
+                       case (KeyEvent.VK_4):
+                               if (controlDown) {
+                                       ui.UINAView();
+                               }
+                               break;
+                       case (KeyEvent.VK_5):
+                               if (controlDown) {
+                                       ui.UIVARNAView();
+                               }
+                               break;
+                       case (KeyEvent.VK_6):
+                               if (controlDown) {
+                                       ui.UIMOTIFView();
+                               }
+                               break;
+
+                       // Navigation control keys (Zoom in/out, arrow keys ...)
+                       case (KeyEvent.VK_DOWN):
+                               if (_vp.getZoom() > 1) {
+                                       _vp.setTranslation(new Point(_vp.getTranslation().x,_vp.getTranslation().y-5));
+                                       _vp.checkTranslation();
+                               }
+                               break;
+                       case (KeyEvent.VK_UP):
+                               if (_vp.getZoom() > 1) {
+                                       _vp.setTranslation(new Point(_vp.getTranslation().x,_vp.getTranslation().y+5));
+                                       _vp.checkTranslation();
+                               }
+                               break;
+                       case (KeyEvent.VK_LEFT):
+                               if (_vp.getZoom() > 1) {
+                                       _vp.setTranslation(new Point(_vp.getTranslation().x+5,_vp.getTranslation().y));
+                                       _vp.checkTranslation();
+                               }
+                               break;
+                       case (KeyEvent.VK_RIGHT):
+                               if (_vp.getZoom() > 1) {
+                                       _vp.setTranslation(new Point(_vp.getTranslation().x-5,_vp.getTranslation().y));
+                                       _vp.checkTranslation();
+                               }
+                               break;
+                       case (KeyEvent.VK_EQUALS):
+                       case (KeyEvent.VK_PLUS):
+                               ui.UIZoomIn();
+                               break;
+                       case (KeyEvent.VK_MINUS):
+                               ui.UIZoomOut();
+                               break;
+                       }
+               } catch (Exception e1) {
+                       _vp.errorDialog(e1);
+               }
+               _vp.repaint();
+       }
+
+       /**
+        * if ((e.getKeyCode() == KeyEvent.VK_PLUS)||(e.getKeyChar() == '+')) {
+        * _vp.getVARNAUI().UIZoomIn(); } else if (e.getKeyCode() ==
+        * KeyEvent.VK_MINUS) { _vp.getVARNAUI().UIZoomOut(); } // 1 pour Redraw
+        * Radiate else if (e.getKeyChar() == KeyEvent.VK_1) {
+        * _vp.getVARNAUI().UIRadiate(); } // 2 pour Redraw Circular else if
+        * (e.getKeyChar() == KeyEvent.VK_2) { _vp.getVARNAUI().UICircular(); } // 3
+        * pour Redraw NAView else if (e.getKeyChar() == KeyEvent.VK_3) {
+        * _vp.getVARNAUI().UINAView(); }
+        * 
+        * // 4 for RNA on a line else if (e.getKeyChar() == KeyEvent.VK_4) {
+        * _vp.getVARNAUI().UILine(); } // 5 fun arn random coord else if
+        * (e.isControlDown() && e.getKeyChar() == KeyEvent.VK_9) { for (int i = 0;
+        * i < _vp.getRNA().get_listeBases().size(); i++) {
+        * _vp.getRNA().get_listeBases().get(i).set_coords( new
+        * Point2D.Double(_vp.getWidth() * Math.random(), _vp.getHeight() *
+        * Math.random())); _vp.getRNA().get_listeBases().get(i).set_center( new
+        * Point2D.Double(_vp.getWidth() / 2 Math.random(), _vp.getHeight() / 2
+        * Math.random())); } } // 6 fun random arn structure else if
+        * (e.isControlDown() & e.getKeyChar() == KeyEvent.VK_8) { try {
+        * _vp.drawRNA(_vp.getRNA().getListeBasesToString(), getRandomRNA(), _vp
+        * .getRNA().get_drawMode()); } catch (ExceptionNonEqualLength e1) {
+        * _vp.errorDialog(e1); } } _vp.repaint(); }
+        **/
+
+       public String getRandomRNA() {
+               int pile = 0, j, i = 0;
+               double l;
+               String fun = "";
+               while (i < 2000) {
+                       if (Math.random() > 0.5) {
+                               j = 0;
+                               l = Math.random() * 10;
+                               while (j < l) {
+                                       fun += '.';
+                                       i++;
+                                       j++;
+                               }
+                       } else {
+                               if (Math.random() > 0.5 && pile > 0) {
+                                       j = 0;
+                                       l = Math.random() * 5;
+                                       while (j < l && pile > 0) {
+                                               fun += ')';
+                                               pile--;
+                                               j++;
+                                               i++;
+                                       }
+                               } else {
+                                       j = 0;
+                                       l = Math.random() * 5;
+                                       while (j < l) {
+                                               fun += '(';
+                                               pile++;
+                                               j++;
+                                               i++;
+                                       }
+
+                               }
+                       }
+               }
+               while (pile > 0) {
+                       fun += ')';
+                       pile--;
+               }
+               return fun;
+       }
+
+       public void keyReleased(KeyEvent e) {
+       }
+
+       public void keyTyped(KeyEvent e) {
+       }
+
+       public void focusGained(FocusEvent arg0) {
+               _vp.repaint();
+       }
+
+       public void focusLost(FocusEvent arg0) {
+               _vp.repaint();
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurVueAnnotation.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurVueAnnotation.java
new file mode 100644 (file)
index 0000000..74da336
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JColorChooser;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueAnnotation;
+import fr.orsay.lri.varna.views.VueUI;
+
+/**
+ * Annotation View Controller
+ * 
+ * @author Darty@lri.fr
+ * 
+ */
+public class ControleurVueAnnotation implements CaretListener, ChangeListener,
+               ActionListener {
+
+       protected VueAnnotation _vueAnnot;
+
+       /**
+        * Creates a ControleurVueAnnotation
+        * 
+        * @param vueAnnot
+        */
+       public ControleurVueAnnotation(VueAnnotation vueAnnot) {
+               _vueAnnot = vueAnnot;
+       }
+
+       public void caretUpdate(CaretEvent arg0) {
+               _vueAnnot.update();
+       }
+
+       public void stateChanged(ChangeEvent arg0) {
+               _vueAnnot.update();
+       }
+
+       public void actionPerformed(ActionEvent arg0) {
+               if (arg0.getActionCommand().equals("setcolor")) {
+                       final VueUI vui = _vueAnnot.get_vp().getVARNAUI(); // BH SwingJS
+                       vui.showColorDialog("Pick a color", _vueAnnot.getTextAnnotation().getColor(), new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       Color c = (Color) vui.dialogReturnValue;
+                                       if (c != null)
+                                               _vueAnnot.updateColor(c);
+                                       _vueAnnot.update();
+                               }
+                               
+                       });
+                       
+//was:                 Color c = JColorChooser.showDialog(_vueAnnot.get_vp(),
+//                                     "Pick a color", _vueAnnot.getTextAnnotation().getColor());
+//                     if (c != null) {
+//                             _vueAnnot.updateColor(c);
+//                     }
+
+               }
+               _vueAnnot.update();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/controlers/ControleurZoom.java b/srcjar/fr/orsay/lri/varna/controlers/ControleurZoom.java
new file mode 100644 (file)
index 0000000..7987fd3
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.controlers;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.views.VueZoom;
+
+
+public class ControleurZoom implements ChangeListener {
+
+       private VueZoom _vz;
+
+       public ControleurZoom(VueZoom vz) {
+               _vz = vz;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vz.get_vp().setZoom(_vz.getZoom());
+               _vz.get_vp().repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionDrawingAlgorithm.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionDrawingAlgorithm.java
new file mode 100644 (file)
index 0000000..6ed626e
--- /dev/null
@@ -0,0 +1,18 @@
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exceptions of this class, or of a derived class,
+ * are thrown by the RNA drawing algorithms.
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExceptionDrawingAlgorithm extends Exception {
+       private static final long serialVersionUID = -8705033963886770829L;
+
+       public ExceptionDrawingAlgorithm(String message) {
+               super(message);
+       }
+       
+       public ExceptionDrawingAlgorithm() {
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionEdgeEndpointAlreadyConnected.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionEdgeEndpointAlreadyConnected.java
new file mode 100644 (file)
index 0000000..8c0a98c
--- /dev/null
@@ -0,0 +1,21 @@
+package fr.orsay.lri.varna.exceptions;
+
+
+/**
+ * Thrown by an EdgeEndPoint when it is already connected and you try
+ * to connect it.
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExceptionEdgeEndpointAlreadyConnected extends ExceptionInvalidRNATemplate {
+       private static final long serialVersionUID = 3978166870034913842L;
+
+       public ExceptionEdgeEndpointAlreadyConnected(String message) {
+               super(message);
+       }
+       
+       public ExceptionEdgeEndpointAlreadyConnected() {
+               super("Edge endpoint is already connected");
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionExportFailed.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionExportFailed.java
new file mode 100644 (file)
index 0000000..c04911b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for export problems
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionExportFailed extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+       private String _path;
+
+       public ExceptionExportFailed(String errorMessage, String path) {
+               _errorMessage = errorMessage;
+               _path = path;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Export failed, File " + _path
+                               + " cannot be created or overwritten !\n";
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionFileFormatOrSyntax.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionFileFormatOrSyntax.java
new file mode 100644 (file)
index 0000000..9fff0f9
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for any problem with the format or the syntax of a file loaded
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionFileFormatOrSyntax extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+       private String _path;
+       private String _errorMessage;
+
+       public ExceptionFileFormatOrSyntax(String errorMessage, String path) {
+               _path = path;
+               _errorMessage = errorMessage;
+       }
+
+       public ExceptionFileFormatOrSyntax(String path) {
+               _path = path;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Unknown format or syntax error in file ' " + _path
+                               + " '. \nLoading cancelled !";
+       }
+
+       public void setPath(String path) {
+               _path = path;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionInvalidRNATemplate.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionInvalidRNATemplate.java
new file mode 100644 (file)
index 0000000..be710bf
--- /dev/null
@@ -0,0 +1,18 @@
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * This exception is thrown when we discover that a template is invalid
+ * (it contains impossible connections between elements).
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExceptionInvalidRNATemplate extends Exception {
+       private static final long serialVersionUID = 3866618355319087333L;
+       
+       public ExceptionInvalidRNATemplate(String message) {
+               super(message);
+       }
+       
+       public ExceptionInvalidRNATemplate() {
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionJPEGEncoding.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionJPEGEncoding.java
new file mode 100644 (file)
index 0000000..fd100f6
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Specific problems with JPEG encoding
+ */
+public class ExceptionJPEGEncoding extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+
+       public ExceptionJPEGEncoding(String errorMessage) {
+               _errorMessage = errorMessage;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "JPEG encoding failed!";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionLoadingFailed.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionLoadingFailed.java
new file mode 100644 (file)
index 0000000..c18abe8
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for file load problems
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionLoadingFailed extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+       private String _path;
+
+       public ExceptionLoadingFailed(String errorMessage, String path) {
+               _errorMessage = errorMessage;
+               _path = path;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Loading failed!\n File '" + _path + "' cannot be read !";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionModeleStyleBaseSyntaxError.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionModeleStyleBaseSyntaxError.java
new file mode 100644 (file)
index 0000000..7f2c775
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for problems with the script about modele style base creation
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionModeleStyleBaseSyntaxError extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+       private String _details;
+
+       public ExceptionModeleStyleBaseSyntaxError(String details) {
+               _details = details;
+       }
+
+       public String getMessage() {
+               return _details;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionNAViewAlgorithm.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionNAViewAlgorithm.java
new file mode 100644 (file)
index 0000000..7ad306f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+public class ExceptionNAViewAlgorithm extends ExceptionDrawingAlgorithm {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+
+       public ExceptionNAViewAlgorithm(String errorMessage) {
+               _errorMessage = errorMessage;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return _errorMessage;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionNonEqualLength.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionNonEqualLength.java
new file mode 100644 (file)
index 0000000..bd9abab
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for different rna lenghts
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionNonEqualLength extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+
+       public ExceptionNonEqualLength(String errorMessage) {
+               _errorMessage = errorMessage;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Both RNA have not the same length, cannot resolve secondary structure.";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionParameterError.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionParameterError.java
new file mode 100644 (file)
index 0000000..9c5b5b7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for parameter problems
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionParameterError extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+       private String _details;
+
+       public ExceptionParameterError(String errorMessage, String details) {
+               _errorMessage = errorMessage;
+               _details = details;
+       }
+
+       public ExceptionParameterError(String details) {
+               _errorMessage = "";
+               _details = details;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return _details;
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionPermissionDenied.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionPermissionDenied.java
new file mode 100644 (file)
index 0000000..f6667f2
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Exception for permission denied problems due to security
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionPermissionDenied extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+
+       public ExceptionPermissionDenied(String errorMessage) {
+               _errorMessage = errorMessage;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Permission denied for security reason !\n"
+                               + "Consider using the VARNA panel class in a signed context.\n";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionUnmatchedClosingParentheses.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionUnmatchedClosingParentheses.java
new file mode 100644 (file)
index 0000000..195d275
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+import java.text.ParseException;
+
+/**
+ * Exception used when a rna has not the same number of opening and clothing
+ * parentheses
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionUnmatchedClosingParentheses extends ParseException {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       public ExceptionUnmatchedClosingParentheses(String s, int errorOffset) {
+               super(s, errorOffset);
+       }
+
+       public ExceptionUnmatchedClosingParentheses(int errorOffset) {
+               super("", errorOffset);
+       }
+
+       public String getMessage() {
+               return "Unbalanced parentheses expression, cannot resolve secondary structure.\n"
+                               + "Bad secondary structure (DBN format):Unmatched closing parentheses ')' at "
+                               + getErrorOffset();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionWritingForbidden.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionWritingForbidden.java
new file mode 100644 (file)
index 0000000..06f0b57
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Used when writing file problems append due to security policy
+ * 
+ * @author darty
+ * 
+ */
+public class ExceptionWritingForbidden extends Exception {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private String _errorMessage;
+
+       public ExceptionWritingForbidden(String errorMessage) {
+               _errorMessage = errorMessage;
+       }
+
+       public String getError() {
+               return _errorMessage;
+       }
+
+       public String getMessage() {
+               return "Writing is not allowed within current security policy.";
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionXMLGeneration.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionXMLGeneration.java
new file mode 100644 (file)
index 0000000..ac07e6f
--- /dev/null
@@ -0,0 +1,17 @@
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Thrown by XML-generating algorithms when they fail.
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExceptionXMLGeneration extends Exception {
+       private static final long serialVersionUID = 8867910395701431387L;
+
+       public ExceptionXMLGeneration(String message) {
+               super(message);
+       }
+       
+       public ExceptionXMLGeneration() {
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/ExceptionXmlLoading.java b/srcjar/fr/orsay/lri/varna/exceptions/ExceptionXmlLoading.java
new file mode 100644 (file)
index 0000000..1efaf50
--- /dev/null
@@ -0,0 +1,17 @@
+package fr.orsay.lri.varna.exceptions;
+
+/**
+ * Thrown by algorithms that load data from XML when they fail.
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExceptionXmlLoading extends Exception {
+       private static final long serialVersionUID = -6267373620339074008L;
+
+       public ExceptionXmlLoading(String message) {
+               super(message);
+       }
+       
+       public ExceptionXmlLoading() {
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/exceptions/MappingException.java b/srcjar/fr/orsay/lri/varna/exceptions/MappingException.java
new file mode 100644 (file)
index 0000000..bc31021
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.exceptions;
+
+public class MappingException extends Exception {
+       private static final long serialVersionUID = 1L;
+
+       public static final int MULTIPLE_PARTNERS_DEFINITION_ATTEMPT = 1;
+       public static final int BAD_ALIGNMENT_INPUT = 2;
+       public static final int CUSTOM_MESSAGE = 3;
+
+       private String _errorMessage;
+       private int _type;
+
+       public MappingException(String errorMessage) {
+               _errorMessage = errorMessage;
+               _type = CUSTOM_MESSAGE;
+       }
+
+       public MappingException(int type) {
+               _type = type;
+       }
+
+       public String getMessage() {
+               switch (_type) {
+               case MULTIPLE_PARTNERS_DEFINITION_ATTEMPT:
+                       return "Mapping error: Attempt to define multiple partners for a base-pair";
+               case BAD_ALIGNMENT_INPUT:
+                       return "Mapping error: Bad input alignment";
+
+               case CUSTOM_MESSAGE:
+                       return _errorMessage;
+               default:
+                       return "Mapping error: Type is unknown.";
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/factories/RNAAlignment.java b/srcjar/fr/orsay/lri/varna/factories/RNAAlignment.java
new file mode 100644 (file)
index 0000000..0cbf17c
--- /dev/null
@@ -0,0 +1,106 @@
+package fr.orsay.lri.varna.factories;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.Stack;
+
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+ * BH SwingJS -- must explicitly check for array out of bounds
+ */
+public class RNAAlignment {
+   private ArrayList<String> _lst = new ArrayList<String> (); 
+   private Hashtable<String, Integer> _index = new Hashtable<String, Integer> ();
+   private Hashtable<String, String> _accession = new Hashtable<String, String> ();
+   private String _secStr = "";
+   
+   public void addSequence(String id, String s)
+   {
+          if (!_index.containsKey(id))
+          {
+                  _index.put(id,_lst.size());
+                  _lst.add(s);
+          }
+          _lst.set(_index.get(id),s);
+   }
+   
+   public void setSecStr(String s)
+   {
+          _secStr = s;
+   }
+   
+  public void setAccession(String id, String AC)
+  {
+         _accession.put(id,AC);
+  }
+   
+   public ArrayList<RNA> getRNAs() throws ExceptionUnmatchedClosingParentheses
+   {
+          ArrayList<RNA> result = new ArrayList<RNA>(); 
+          int[] str = RNAFactory.parseSecStr(_secStr);
+          ArrayList<String> ids = new ArrayList<String>(_index.keySet());
+          Collections.sort(ids,new Comparator<String>(){
+               public int compare(String o1, String o2) {
+                       return o1.compareToIgnoreCase(o2);
+               }});
+          for (String id: ids )
+          {
+                  int n = _index.get(id);
+                  String seq = _lst.get(n);
+                  if (seq.length() != str.length)
+                          throw new ArrayIndexOutOfBoundsException(); // BH SwingJS -- must explicitly check for array out of bounds
+                  String nseq ="";
+                  String nstr ="";
+                  for(int i=0;i<seq.length();i++)
+                  {
+                          char c = seq.charAt(i);
+                          int j = str[i];
+                          
+                          if (!(c=='.' || c==':' || c=='-'))
+                          {
+                                  nseq += c;
+                                  if (j==-1)
+                                  {
+                                          nstr += '.';
+                                  }
+                                  else
+                                  {
+                                          int cp = seq.charAt(j);
+                                          if (cp=='.' || cp==':' || cp=='-')
+                                          {
+                                                  nstr += '.';                                            
+                                          }
+                                          else
+                                          {
+                                                  nstr += _secStr.charAt(i);
+                                          }
+                                  }
+                          }
+                  }
+                  RNA r = new RNA();
+                  try {
+                       r.setRNA(nseq, nstr);
+                       r.setName(id);
+                       if (_accession.containsKey(id))
+                       {
+                               r.setID(_accession.get(id));
+                       }
+                       else
+                       {
+                               r.setID(id);
+                       }
+                       result.add(r);
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+          }
+          return result;
+   }
+}
diff --git a/srcjar/fr/orsay/lri/varna/factories/RNAFactory.java b/srcjar/fr/orsay/lri/varna/factories/RNAFactory.java
new file mode 100644 (file)
index 0000000..c514f31
--- /dev/null
@@ -0,0 +1,805 @@
+package fr.orsay.lri.varna.factories;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EmptyStackException;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.InputSource;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement;
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement.BackboneType;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.utils.RNAMLParser;
+
+/**
+ * BH JAVA FIX: mostly here we are just removing a lot of unnecessary stack traces when doing a drag-drop of a file
+ * BH JAVA FIX: making sure the file reader is closed properly
+ *  
+ */
+public class RNAFactory
+{
+
+  public enum RNAFileType
+  {
+    FILE_TYPE_STOCKHOLM, FILE_TYPE_TCOFFEE, FILE_TYPE_BPSEQ, FILE_TYPE_CT, FILE_TYPE_DBN, FILE_TYPE_RNAML, FILE_TYPE_UNKNOWN
+  }
+
+private static boolean isQuiet;
+
+  public static ArrayList<RNA> loadSecStrRNAML(Reader r)
+      throws ExceptionPermissionDenied, ExceptionLoadingFailed,
+      ExceptionFileFormatOrSyntax
+  {
+
+    ArrayList<RNA> result = new ArrayList<RNA>();
+    try
+    {
+      // System.setProperty("javax.xml.parsers.SAXParserFactory",
+      // "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
+      SAXParserFactory saxFact = javax.xml.parsers.SAXParserFactory
+          .newInstance();
+      saxFact.setValidating(false);
+      saxFact.setXIncludeAware(false);
+      saxFact.setNamespaceAware(false);
+      SAXParser sp = saxFact.newSAXParser();
+      RNAMLParser RNAMLData = new RNAMLParser();
+      sp.parse(new InputSource(r), RNAMLData);
+
+      /*
+       * XMLReader xr = XMLReaderFactory.createXMLReader(); RNAMLParser
+       * RNAMLData = new RNAMLParser(); xr.setContentHandler(RNAMLData);
+       * xr.setErrorHandler(RNAMLData); xr.setEntityResolver(RNAMLData);
+       * xr.parse(new InputSource(r));
+       */
+      for (RNAMLParser.RNATmp rnaTmp : RNAMLData.getMolecules())
+      {
+        RNA current = new RNA();
+        // Retrieving parsed data
+        List<String> seq = rnaTmp.getSequence();
+        // Creating empty structure of suitable size
+        int[] str = new int[seq.size()];
+        for (int i = 0; i < str.length; i++)
+        {
+          str[i] = -1;
+        }
+        current.setRNA(seq, str);
+        Vector<RNAMLParser.BPTemp> allbpsTmp = rnaTmp.getStructure();
+        ArrayList<ModeleBP> allbps = new ArrayList<ModeleBP>();
+        for (int i = 0; i < allbpsTmp.size(); i++)
+        {
+          RNAMLParser.BPTemp bp = allbpsTmp.get(i);
+          // System.err.println(bp);
+          int bp5 = bp.pos5;
+          int bp3 = bp.pos3;
+          ModeleBase mb = current.getBaseAt(bp5);
+          ModeleBase part = current.getBaseAt(bp3);
+          ModeleBP newStyle = bp.createBPStyle(mb, part);
+          allbps.add(newStyle);
+        }
+
+        current.applyBPs(allbps);
+        result.add(current);
+      }
+
+    }
+    catch (IOException ioe)
+    {
+      throw new ExceptionLoadingFailed(
+          "Couldn't load file due to I/O or security policy issues.", "");
+    }
+    catch (Exception ge)
+    {
+       if (!isQuiet) // BH
+      ge.printStackTrace();
+    }
+    return result;
+  }
+
+  public static int[] parseSecStr(String _secStr)
+      throws ExceptionUnmatchedClosingParentheses
+  {
+    Hashtable<Character, Stack<Integer>> stacks = new Hashtable<Character, Stack<Integer>>();
+    int[] result = new int[_secStr.length()];
+    int i = 0;
+    try
+    {
+      for (i = 0; i < _secStr.length(); i++)
+      {
+        result[i] = -1;
+        char c = _secStr.charAt(i);
+        char c2 = Character.toUpperCase(c);
+        if (!stacks.containsKey(c2))
+        {
+          stacks.put(c2, new Stack<Integer>());
+        }
+        switch (c)
+        {
+        case '<':
+        case '{':
+        case '(':
+        case '[':
+          stacks.get(c).push(i);
+          break;
+        case '>':
+        {
+          int j = stacks.get('<').pop();
+          result[i] = j;
+          result[j] = i;
+          break;
+        }
+        case '}':
+        {
+          int j = stacks.get('{').pop();
+          result[i] = j;
+          result[j] = i;
+          break;
+        }
+        case ')':
+        {
+          int j = stacks.get('(').pop();
+          result[i] = j;
+          result[j] = i;
+          break;
+        }
+        case ']':
+        {
+          int j = stacks.get('[').pop();
+          result[i] = j;
+          result[j] = i;
+          break;
+        }
+        case '.':
+          break;
+        default:
+        {
+          if (Character.isLetter(c) && Character.isUpperCase(c))
+          {
+            stacks.get(c).push(i);
+          }
+          else if (Character.isLetter(c) && Character.isLowerCase(c))
+          {
+            int j = stacks.get(Character.toUpperCase(c)).pop();
+            result[i] = j;
+            result[j] = i;
+          }
+        }
+        }
+      }
+    }
+    catch (EmptyStackException e)
+    {
+      throw new ExceptionUnmatchedClosingParentheses(i);
+    }
+    return result;
+  }
+
+  public static ArrayList<RNA> loadSecStrDBN(Reader r)
+      throws ExceptionLoadingFailed, ExceptionPermissionDenied,
+      ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax
+  {
+    boolean loadOk = false;
+    ArrayList<RNA> result = new ArrayList<RNA>();
+    RNA current = new RNA();
+    try
+    {
+      BufferedReader fr = new BufferedReader(r);
+      String line = fr.readLine();
+      String title = "";
+      String seqTmp = "";
+      String strTmp = "";
+      while ((line != null) && (strTmp.equals("")))
+      {
+        line = line.trim();
+        if (!line.startsWith(">"))
+        {
+          if (seqTmp.equals(""))
+          {
+            seqTmp = line;
+          }
+          else
+          {
+            strTmp = line;
+          }
+        }
+        else
+        {
+          title = line.substring(1).trim();
+        }
+        line = fr.readLine();
+      }
+      if (strTmp.length() != 0)
+      {
+        current.setRNA(seqTmp, strTmp);
+        current.setName(title);
+        loadOk = true;
+      }
+    }
+    catch (IOException e)
+    {
+      throw new ExceptionLoadingFailed(e.getMessage(), "");
+    }
+    if (loadOk)
+    {
+      result.add(current);
+    }
+    return result;
+  }
+
+  public static ArrayList<RNA> loadSecStr(File f)
+             throws ExceptionFileFormatOrSyntax
+         {
+           try {
+                       return loadSecStr(new BufferedReader(new FileReader(f)), RNAFileType.FILE_TYPE_UNKNOWN);
+               } catch (FileNotFoundException e) {
+                       throw new ExceptionFileFormatOrSyntax(f.toString());
+               }
+         }
+
+  public static ArrayList<RNA> loadSecStr(Reader r)
+      throws ExceptionFileFormatOrSyntax
+  {
+    return loadSecStr(new BufferedReader(r), RNAFileType.FILE_TYPE_UNKNOWN);
+  }
+
+       public static ArrayList<RNA> loadSecStr(BufferedReader r, RNAFileType fileType) throws ExceptionFileFormatOrSyntax {
+               try {
+                       switch (fileType) {
+                       case FILE_TYPE_DBN: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrDBN(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                               }
+                       }
+                               break;
+                       case FILE_TYPE_CT: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrCT(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                                       if (!isQuiet) // BH
+                                               e.printStackTrace();
+                               }
+                       }
+                               break;
+                       case FILE_TYPE_BPSEQ: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrBPSEQ(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                                       if (!isQuiet) // BH
+                                               e.printStackTrace();
+                               }
+                       }
+                               break;
+                       case FILE_TYPE_TCOFFEE: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrTCoffee(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                                       if (!isQuiet) // BH
+                                               e.printStackTrace();
+                               }
+                       }
+                               break;
+                       case FILE_TYPE_STOCKHOLM: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrStockholm(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                                       if (!isQuiet) // BH
+                                               e.printStackTrace();
+                               }
+                       }
+                               break;
+                       case FILE_TYPE_RNAML: {
+                               try {
+                                       ArrayList<RNA> result = loadSecStrRNAML(r);
+                                       if (result.size() != 0)
+                                               return result;
+                               } catch (Exception e) {
+                                       if (!isQuiet) // BH
+                                               e.printStackTrace();
+                               }
+                       }
+                               break;
+
+                       case FILE_TYPE_UNKNOWN: {
+                               try {
+                                       r.mark(1000000);
+                                       RNAFactory.RNAFileType[] types = RNAFactory.RNAFileType.values();
+                                       isQuiet = true; // BH to not report errors when
+                                                                       // drag-dropping
+                                       ArrayList<RNA> result = null;
+                                       RNAFactory.RNAFileType t = null;
+                                       for (int i = 0; i < types.length; i++) {
+                                               r.reset();
+                                               t = types[i];
+                                               if (t != RNAFactory.RNAFileType.FILE_TYPE_UNKNOWN) {
+                                                       try {
+                                                               result = loadSecStr(r, t);
+                                                               if (result.size() != 0) {
+                                                                       break;
+                                                               }
+                                                       } catch (Exception e) {
+                                                               if (!isQuiet) // BH
+                                                                       System.err.println(e.toString());
+                                                       }
+                                               }
+                                       }
+                                       System.out.println(t); // BH
+                                       isQuiet = false; // BH
+                                       return result;
+                               } catch (IOException e2) {
+                                       e2.printStackTrace();
+                               }
+                       }
+                       }
+                       throw new ExceptionFileFormatOrSyntax("Couldn't parse this file as " + fileType + ".");
+               } finally { // BH !!
+                       try {
+                               if (!isQuiet)
+                                       r.close();
+                       } catch (IOException e) {
+                               // ignore
+                       }
+               }
+       }
+
+  public static RNAFileType guessFileTypeFromExtension(String path)
+  {
+    if (path.toLowerCase().endsWith("ml"))
+    {
+      return RNAFileType.FILE_TYPE_RNAML;
+    }
+    else if (path.toLowerCase().endsWith("dbn")
+        || path.toLowerCase().endsWith("faa"))
+    {
+      return RNAFileType.FILE_TYPE_DBN;
+    }
+    else if (path.toLowerCase().endsWith("ct"))
+    {
+      return RNAFileType.FILE_TYPE_CT;
+    }
+    else if (path.toLowerCase().endsWith("bpseq"))
+    {
+      return RNAFileType.FILE_TYPE_BPSEQ;
+    }
+    else if (path.toLowerCase().endsWith("rfold"))
+    {
+      return RNAFileType.FILE_TYPE_TCOFFEE;
+    }
+    else if (path.toLowerCase().endsWith("stockholm")
+        || path.toLowerCase().endsWith("stk"))
+    {
+      return RNAFileType.FILE_TYPE_STOCKHOLM;
+    }
+
+    return RNAFileType.FILE_TYPE_UNKNOWN;
+
+  }
+
+  public static ArrayList<RNA> loadSecStr(String path)
+      throws ExceptionExportFailed, ExceptionPermissionDenied,
+      ExceptionLoadingFailed, ExceptionFileFormatOrSyntax,
+      ExceptionUnmatchedClosingParentheses, FileNotFoundException
+  {
+    FileReader fr = null;
+    try
+    {
+      fr = new FileReader(path);
+      RNAFileType type = guessFileTypeFromExtension(path);
+      return loadSecStr(new BufferedReader(fr), type);
+    }
+    catch (ExceptionFileFormatOrSyntax e)
+    {
+      if (fr != null)
+        try
+        {
+          fr.close();
+        }
+        catch (IOException e2)
+        {
+        }
+      e.setPath(path);
+      throw e;
+    }
+  }
+
+  public static ArrayList<RNA> loadSecStrStockholm(BufferedReader r)
+      throws IOException, ExceptionUnmatchedClosingParentheses
+  {
+    RNAAlignment a = StockholmIO.readAlignement(r);
+    return a.getRNAs();
+  }
+
+  public static ArrayList<RNA> loadSecStrBPSEQ(Reader r)
+      throws ExceptionPermissionDenied, ExceptionLoadingFailed,
+      ExceptionFileFormatOrSyntax
+  {
+    boolean loadOk = false;
+    ArrayList<RNA> result = new ArrayList<RNA>();
+    RNA current = new RNA();
+    try
+    {
+      BufferedReader fr = new BufferedReader(r);
+      String line = fr.readLine();
+      ArrayList<String> seqTmp = new ArrayList<String>();
+      Hashtable<Integer, Vector<Integer>> strTmp = new Hashtable<Integer, Vector<Integer>>();
+
+      int bpFrom;
+      String base;
+      int bpTo;
+      int minIndex = -1;
+      boolean noWarningYet = true;
+      String title = "";
+      String id = "";
+      String filenameStr = "Filename:";
+      String organismStr = "Organism:";
+      String ANStr = "Accession Number:";
+      while (line != null)
+      {
+        line = line.trim();
+        String[] tokens = line.split("\\s+");
+        ArrayList<Integer> numbers  = new ArrayList<Integer>(); 
+        Hashtable<Integer,Integer> numberToIndex  = new Hashtable<Integer,Integer>(); 
+        if ((tokens.length >= 3) && !tokens[0].contains("#")
+            && !line.startsWith("Organism:") && !line.startsWith("Citation")
+            && !line.startsWith("Filename:")
+            && !line.startsWith("Accession Number:"))
+        {
+          base = tokens[1];
+          seqTmp.add(base);
+          bpFrom = (Integer.parseInt(tokens[0]));
+          numbers.add(bpFrom);
+          if (minIndex < 0)
+            minIndex = bpFrom;
+
+          if (seqTmp.size() < (bpFrom - minIndex + 1))
+          {
+            if (noWarningYet)
+            {
+              noWarningYet = false;
+              /*
+               * warningEmition( "Discontinuity detected between nucleotides " +
+               * (seqTmp.size()) + " and " + (bpFrom + 1) +
+               * "!\nFilling in missing portions with unpaired unknown 'X' nucleotides ..."
+               * );
+               */
+            }
+            while (seqTmp.size() < (bpFrom - minIndex + 1))
+            {
+              // System.err.println(".");
+              seqTmp.add("X");
+            }
+          }
+          for (int i = 2; i < tokens.length; i++)
+          {
+            bpTo = (Integer.parseInt(tokens[i]));
+            if ((bpTo != 0) || (i != tokens.length - 1))
+            {
+              if (!strTmp.containsKey(bpFrom))
+                strTmp.put(bpFrom, new Vector<Integer>());
+              strTmp.get(bpFrom).add(bpTo);
+            }
+          }
+        }
+        else if (tokens[0].startsWith("#"))
+        {
+          int occur = line.indexOf("#");
+          String tmp = line.substring(occur + 1);
+          title += tmp.trim() + " ";
+        }
+        else if (tokens[0].startsWith(filenameStr))
+        {
+          int occur = line.indexOf(filenameStr);
+          String tmp = line.substring(occur + filenameStr.length());
+          title += tmp.trim();
+        }
+        else if (tokens[0].startsWith(organismStr))
+        {
+          int occur = line.indexOf(organismStr);
+          String tmp = line.substring(occur + organismStr.length());
+          if (title.length() != 0)
+          {
+            title = "/" + title;
+          }
+          title = tmp.trim() + title;
+        }
+        else if (line.contains(ANStr))
+        {
+          int occur = line.indexOf(ANStr);
+          String tmp = line.substring(occur + ANStr.length());
+          id = tmp.trim();
+        }
+        line = fr.readLine();
+      }
+      if (strTmp.size() != 0)
+      {
+        ArrayList<String> seq = seqTmp;
+        int[] str = new int[seq.size()];
+        for (int i = 0; i < seq.size(); i++)
+        {
+          str[i] = -1;
+        }
+        current.setRNA(seq, str, minIndex);
+        ArrayList<ModeleBP> allbps = new ArrayList<ModeleBP>();
+        for (int i : strTmp.keySet())
+        {
+          for (int j : strTmp.get(i))
+          {
+                 if (i<=j)
+                 {
+                   ModeleBase mb = current.getBaseAt(i - minIndex);
+                   ModeleBase part = current.getBaseAt(j - minIndex);
+                   ModeleBP newStyle = new ModeleBP(mb, part);
+                   allbps.add(newStyle);
+                 }
+          }
+        }
+        current.applyBPs(allbps);
+        current.setName(title);
+        current.setID(id);
+        loadOk = true;
+      }
+    }
+    catch (NumberFormatException e)
+    {
+       if (!isQuiet) // BH SwingJS
+      e.printStackTrace();
+    }
+    catch (IOException e)
+    {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    catch (Exception e)
+    {
+      throw new ExceptionLoadingFailed(e.getMessage(), "");
+    }
+    if (loadOk)
+      result.add(current);
+    return result;
+  }
+
+  public static ArrayList<RNA> loadSecStrTCoffee(Reader r)
+      throws ExceptionPermissionDenied, ExceptionLoadingFailed,
+      ExceptionFileFormatOrSyntax
+  {
+    boolean loadOk = false;
+    ArrayList<RNA> result = new ArrayList<RNA>();
+    try
+    {
+      BufferedReader fr = new BufferedReader(r);
+      String line = fr.readLine();
+      ArrayList<String> seqs = new ArrayList<String>();
+      ArrayList<String> ids = new ArrayList<String>();
+      int numSeqs = -1;
+      int currSeq = -1;
+      RNA current = null;
+      while (line != null)
+      {
+        if (!line.startsWith("!"))
+        {
+          String[] tokens = line.split("\\s+");
+          // This may indicate new secondary structure
+          if (line.startsWith("#"))
+          {
+            currSeq = Integer.parseInt(tokens[0].substring(1));
+            int currSeq2 = Integer.parseInt(tokens[1]);
+            // For TCoffee, a sec str is a matching between a seq and itself
+            // => Disregard any alignment by filtering on the equality of sequence indices.
+            if (currSeq == currSeq2)
+            {
+              current = new RNA();
+              current.setName(ids.get(currSeq - 1));
+              current.setSequence(seqs.get(currSeq - 1));
+              result.add(current);
+            }
+            else
+            {
+              current = null;
+            }
+          }
+          // Beginning of the file... 
+          else if (current == null)
+          {
+            //... either this is the number of sequences...
+            if (numSeqs < 0)
+            {
+              numSeqs = Integer.parseInt(tokens[0]);
+            }
+            //... or this is a sequence definition...
+            else
+            {
+              String id = tokens[0];
+              String seq = tokens[2];
+              seqs.add(seq);
+              ids.add(id);
+            }
+          }
+          //Otherwise, this is a base-pair definition, related to the currently selected sequence
+          else if (tokens.length == 3)
+          {
+            int from = Integer.parseInt(tokens[0]) - 1;
+            int to = Integer.parseInt(tokens[1]) - 1;
+            current.addBP(from, to);
+          }
+        }
+        line = fr.readLine();
+      }
+      loadOk = true;
+    }
+    catch (NumberFormatException e)
+    {
+       if (!isQuiet) // BH SwingJS
+      e.printStackTrace();
+    }
+    catch (IOException e)
+    {
+       if (!isQuiet) // BH SwingJS
+               e.printStackTrace();
+    }
+    if (!loadOk)
+    {
+      throw new ExceptionLoadingFailed("Parse Error", "");
+    }
+    return result;
+  }
+
+  public static ArrayList<RNA> loadSecStrCT(Reader r)
+      throws ExceptionPermissionDenied, ExceptionLoadingFailed,
+      ExceptionFileFormatOrSyntax
+  {
+    boolean loadOk = false;
+    ArrayList<RNA> result = new ArrayList<RNA>();
+    RNA current = new RNA();
+    try
+    {
+      BufferedReader fr = new BufferedReader(r);
+      String line = fr.readLine();
+      ArrayList<String> seq = new ArrayList<String>();
+      ArrayList<String> lbls = new ArrayList<String>();
+      Vector<Integer> strTmp = new Vector<Integer>();
+      Vector<Integer> newStrands = new Vector<Integer>();
+      int bpFrom;
+      String base;
+      String lbl;
+      int bpTo;
+      boolean noWarningYet = true;
+      int minIndex = -1;
+      String title = "";
+      while (line != null)
+      {
+        line = line.trim();
+        String[] tokens = line.split("\\s+");
+        if (tokens.length >= 6)
+        {
+          try
+          {
+            bpFrom = (Integer.parseInt(tokens[0]));
+            bpTo = (Integer.parseInt(tokens[4]));
+            if (minIndex == -1)
+              minIndex = bpFrom;
+            bpFrom -= minIndex;
+            if (bpTo != 0)
+              bpTo -= minIndex;
+            else
+              bpTo = -1;
+            base = tokens[1];
+            lbl = tokens[5];
+            int before = Integer.parseInt(tokens[2]);
+            int after = Integer.parseInt(tokens[3]);
+            
+            if (before==0 && !seq.isEmpty())
+            {
+               newStrands.add(strTmp.size()-1);
+            }
+            if (bpFrom != seq.size())
+            {
+              if (noWarningYet)
+              {
+                noWarningYet = false;
+                /*
+                 * warningEmition( "Discontinuity detected between nucleotides "
+                 * + (seq.size()) + " and " + (bpFrom + 1) +
+                 * "!\nFilling in missing portions with unpaired unknown 'X' nucleotides ..."
+                 * );
+                 */
+              }
+              while (bpFrom > seq.size())
+              {
+                seq.add("X");
+                strTmp.add(-1);
+                lbls.add("");
+              }
+            }
+            seq.add(base);
+            strTmp.add(bpTo);
+            lbls.add(lbl);
+          }
+          catch (NumberFormatException e)
+          {
+                 if (strTmp.size()!=0)
+                         e.printStackTrace();
+          }
+        }
+        if ((line.contains("ENERGY = ")) || line.contains("dG = "))
+        {
+          String[] ntokens = line.split("\\s+");
+          if (ntokens.length >= 4)
+          {
+            String energy = ntokens[3];
+            for (int i = 4; i < ntokens.length; i++)
+            {
+              title += ntokens[i] + " ";
+            }
+            title += "(E=" + energy + " kcal/mol)";
+          }
+        }
+        line = fr.readLine();
+      }
+      if (strTmp.size() != 0)
+      {
+        int[] str = new int[strTmp.size()];
+        for (int i = 0; i < strTmp.size(); i++)
+        {
+          str[i] = strTmp.elementAt(i).intValue();
+        }
+        current.setRNA(seq, str, minIndex);
+        current.setName(title);
+        for (int i = 0; i < current.getSize(); i++)
+        {
+          current.getBaseAt(i).setLabel(lbls.get(i));
+        }
+        for (int i : newStrands)
+        {
+               current.getBackbone().addElement(new ModeleBackboneElement(i,BackboneType.DISCONTINUOUS_TYPE));
+        }
+
+        
+        loadOk = true;
+      }
+    }
+    catch (IOException e)
+    {
+       e.printStackTrace();
+      throw new ExceptionLoadingFailed(e.getMessage(), "");
+    }
+    catch (NumberFormatException e)
+    {
+       if (!isQuiet) // BH SwingJS
+       e.printStackTrace();
+      throw new ExceptionFileFormatOrSyntax(e.getMessage(), "");
+    }
+    if (loadOk)
+      result.add(current);
+    return result;
+  }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/factories/StockholmIO.java b/srcjar/fr/orsay/lri/varna/factories/StockholmIO.java
new file mode 100644 (file)
index 0000000..a237251
--- /dev/null
@@ -0,0 +1,91 @@
+package fr.orsay.lri.varna.factories;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+public class StockholmIO {
+       public static RNAAlignment readAlignementFromFile(String path) throws IOException
+       {
+               return StockholmIO.readAlignement(new BufferedReader(new FileReader(path)));
+       }
+       public static RNAAlignment readAlignementFromURL(String url) throws UnsupportedEncodingException, IOException
+       {
+               URL urlAb = new URL(url);
+               URLConnection urlConn = urlAb.openConnection(); 
+               urlConn.setUseCaches(false);
+               Reader r = new InputStreamReader(urlConn.getInputStream(),"UTF-8");
+               return readAlignement(new BufferedReader(r));
+       }
+
+       
+       /*public static Alignment readAlignement(Reader r) throws IOException
+       {
+               return readAlignement(new BufferedReader(r));
+       }*/
+       
+       public static RNAAlignment readAlignement(BufferedReader r) throws IOException
+       {
+               LinkedHashMap<String,StringBuffer> rawSeqs = new LinkedHashMap<String,StringBuffer>();
+               RNAAlignment result = new RNAAlignment();
+               String line = r.readLine(); 
+               String str = "";
+
+               while(line!=null)
+               {
+                       if (!line.startsWith("#"))
+                       {
+                               String[] data = line.split("\\s+");
+                               if (data.length>1)
+                               {
+                                       String seqName = data[0].trim();
+                                       String seq = data[1].trim();
+                                       if (!rawSeqs.containsKey(seqName))
+                                       {
+                                               rawSeqs.put(seqName,new StringBuffer());
+                                       }
+                                       StringBuffer val =  rawSeqs.get(seqName);
+                                       val.append(seq);
+                               }
+                               
+                       }
+                       else if (line.startsWith("#"))
+                       {
+                               String[] data = line.split("\\s+");
+                               if (line.startsWith("#=GC SS_cons"))
+                               {
+                                       str += data[2].trim();  
+                               }
+                               else if (line.startsWith("#=GS"))
+                               {
+                                       if (data[2].trim().equals("AC"))
+                                       {
+                                         result.setAccession(data[1].trim(),data[3].trim());
+                                         
+                                       }
+                               }
+                       }
+                               
+                       line = r.readLine(); 
+               }
+               result.setSecStr(str);
+               for(Map.Entry<String,StringBuffer> entry : rawSeqs.entrySet())
+               {
+                       String s = entry.getValue().toString();
+                       result.addSequence(entry.getKey(), s);
+               }
+               return result;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceParameterLoader.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceParameterLoader.java
new file mode 100644 (file)
index 0000000..954f145
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.interfaces;
+
+public interface InterfaceParameterLoader {
+       public String getParameterValue(String key, String def);
+}
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceParseExport.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceParseExport.java
new file mode 100644 (file)
index 0000000..417c5df
--- /dev/null
@@ -0,0 +1,8 @@
+package fr.orsay.lri.varna.interfaces;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+public interface InterfaceParseExport {
+       public void parse(String s, VARNAPanel vp);
+       public String export();
+}
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNABasesListener.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNABasesListener.java
new file mode 100644 (file)
index 0000000..d2cb290
--- /dev/null
@@ -0,0 +1,19 @@
+package fr.orsay.lri.varna.interfaces;
+
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Set;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+public interface InterfaceVARNABasesListener {
+
+       /**
+        * Reacts to click over base
+        * @param mb The base which has just been clicked
+        */
+       public void onBaseClicked(ModeleBase mb, MouseEvent e);
+}
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAListener.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAListener.java
new file mode 100644 (file)
index 0000000..aafd94d
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.interfaces;
+
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+ * Observable design pattern
+ * 
+ * @author darty
+ * 
+ */
+public abstract interface InterfaceVARNAListener {
+       public abstract void onWarningEmitted(String s);
+       public abstract void onStructureRedrawn();
+       public abstract void onUINewStructure(VARNAConfig v, RNA r);
+       public abstract void onZoomLevelChanged();
+       public abstract void onTranslationChanged();
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAObservable.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNAObservable.java
new file mode 100644 (file)
index 0000000..35b5f9d
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.interfaces;
+
+/**
+ * Observable design pattern
+ * 
+ * @author darty
+ * 
+ */
+public abstract class InterfaceVARNAObservable {
+       public abstract void addVARNAListener(InterfaceVARNAListener rl);
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNARNAListener.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNARNAListener.java
new file mode 100644 (file)
index 0000000..aeeaa5b
--- /dev/null
@@ -0,0 +1,32 @@
+package fr.orsay.lri.varna.interfaces;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Set;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public interface InterfaceVARNARNAListener {
+       /**
+        * Reacts to changes being made at the sequence level. 
+        * @param index The sequence index where a change of base content is observed
+        * @param oldseq Previous base content   
+        * @param newseq New base content
+        */
+       public void onSequenceModified(int index, String oldseq, String newseq);
+       
+       /**
+        * Reacts to modification of the structure (Base-pair addition/removal).
+        * @param current Current list of base-pairs (can be also accessed within the current RNA object).
+        * @param addedBasePairs Newly created base-pairs
+        * @param removedBasePairs Newly removed base-pairs
+        */
+       public void onStructureModified(Set<ModeleBP> current, Set<ModeleBP> addedBasePairs, Set<ModeleBP> removedBasePairs);
+       
+       /**
+        * Reacts to displacement of 
+        * @param previousPositions
+        */
+       public void onRNALayoutChanged(Hashtable<Integer,Point2D.Double> previousPositions);
+}
diff --git a/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNASelectionListener.java b/srcjar/fr/orsay/lri/varna/interfaces/InterfaceVARNASelectionListener.java
new file mode 100644 (file)
index 0000000..20bf2eb
--- /dev/null
@@ -0,0 +1,21 @@
+package fr.orsay.lri.varna.interfaces;
+
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+public interface InterfaceVARNASelectionListener {
+       /**
+        * Specifies an action that should be performed upon changing the hovered base.
+        * @param oldbase Previously hovered base (possibly null).
+        * @param newBase Newly hovered base (possibly null).
+        */
+       public void onHoverChanged(ModeleBase oldbase, ModeleBase newBase);
+       
+       /**
+        * Specifies the action to be performed upon changing the selection.
+        * @param selection The list of bases currently selected 
+        * @param addedBases The list of bases added since previous selection event
+        * @param removedBases The list of bases removed since previous selection event
+        */
+       public void onSelectionChanged(BaseList selection, BaseList addedBases, BaseList removedBases);
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/BaseList.java b/srcjar/fr/orsay/lri/varna/models/BaseList.java
new file mode 100644 (file)
index 0000000..01ac0b1
--- /dev/null
@@ -0,0 +1,200 @@
+package fr.orsay.lri.varna.models;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+public class BaseList {
+       private HashSet<ModeleBase> _bases = new HashSet<ModeleBase>(); 
+       private String _caption;
+
+       public BaseList( BaseList b)
+       {
+               _caption = b._caption;
+               _bases = new HashSet<ModeleBase>(b._bases);
+       }
+
+       
+       public BaseList( String caption)
+       {
+               _caption = caption;
+       }
+       
+       
+       public BaseList( String caption, ModeleBase mb)
+       {
+               this(caption);
+               addBase(mb);
+       }
+
+       public boolean contains(ModeleBase mb)
+       {
+               return _bases.contains(mb);
+       }
+
+       
+       public String getCaption()
+       {
+               return _caption;
+       }
+       
+       public void addBase(ModeleBase b)
+       {
+               _bases.add(b);
+       }
+
+       public void removeBase(ModeleBase b)
+       {
+               _bases.remove(b);
+       }
+
+       
+       public void addBases(Collection<? extends ModeleBase> mbs)
+       {
+               _bases.addAll(mbs);
+       }
+
+       public ArrayList<ModeleBase> getBases()
+       {
+               return new ArrayList<ModeleBase>(_bases);
+       }
+
+       public void clear()
+       {
+               _bases.clear();
+       }
+
+       public static Color getAverageColor(ArrayList<Color> cols)
+       {
+               int r=0,g=0,b=0;
+               for (Color c : cols)
+               {
+                       r += c.getRed();
+                       g += c.getGreen();
+                       b += c.getBlue();
+               }
+               if (cols.size()>0)
+               { 
+                       r /= cols.size();
+                       g /= cols.size();
+                       b /= cols.size();
+               }
+               return new Color(r,g,b);
+       }
+       
+       public Color getAverageOutlineColor()
+       {
+               ArrayList<Color> cols = new ArrayList<Color>(); 
+               for (ModeleBase mb : _bases)
+               {  cols.add(mb.getStyleBase().getBaseOutlineColor()); }
+               return getAverageColor(cols);
+       }
+
+       public Color getAverageNameColor()
+       {
+               ArrayList<Color> cols = new ArrayList<Color>(); 
+               for (ModeleBase mb : _bases)
+               {  cols.add(mb.getStyleBase().getBaseNameColor()); }
+               return getAverageColor(cols);
+       }
+
+       public Color getAverageNumberColor()
+       {
+               ArrayList<Color> cols = new ArrayList<Color>(); 
+               for (ModeleBase mb : _bases)
+               {  cols.add(mb.getStyleBase().getBaseNumberColor()); }
+               return getAverageColor(cols);
+       }
+
+       public Color getAverageInnerColor()
+       {
+               ArrayList<Color> cols = new ArrayList<Color>(); 
+               for (ModeleBase mb : _bases)
+               {  cols.add(mb.getStyleBase().getBaseInnerColor()); }
+               return getAverageColor(cols);
+       }
+
+       public String getNumbers()
+       {
+               String result = ""; 
+               boolean first = true;
+               for (ModeleBase mb:_bases)
+               {  
+                       if (!first)
+                       { result += ","; }
+                       else
+                       { first = false; }
+                       result += "" + mb.getBaseNumber(); 
+               }
+               result += "";
+               return result;
+       }
+
+       public String getContents()
+       {
+               String result = ""; 
+               boolean first = true;
+               for (ModeleBase mb:_bases)
+               {  
+                       if (!first)
+                       { result += ","; }
+                       else
+                       { first = false; }
+                       result += "" + mb.getContent(); 
+               }
+               result += "";
+               return result;
+       }
+
+       public ArrayList<Integer> getIndices()
+       {
+               ArrayList<Integer> indices = new ArrayList<Integer>();
+               for (ModeleBase mb : _bases)
+               {
+                       indices.add(mb.getIndex());
+               }
+               return indices;
+       }
+       
+       /**
+        * Returns, in a new BaseList, the intersection of the current BaseList and of the argument.
+        * @param mb The base list to be used for the intersection
+        * @return The intersection of the current base list and the argument.
+        */
+       
+       public BaseList retainAll(BaseList mb)
+       {
+               HashSet<ModeleBase> cp = new HashSet<ModeleBase>();
+               cp.addAll(_bases);
+               cp.retainAll(mb._bases);
+               BaseList result = new BaseList("TmpIntersection");
+               result.addBases(cp);
+               return result;
+       }
+
+       /**
+        * Returns, in a new BaseList, the list consisting of the current BaseList minus the list passed as argument.
+        * @param mb The base list to be subtracted from the current one
+        * @return The current base list minus the list passed as argument.
+        */
+       
+       public BaseList removeAll(BaseList mb)
+       {
+               HashSet<ModeleBase> cp = new HashSet<ModeleBase>();
+               cp.addAll(_bases);
+               cp.removeAll(mb._bases);
+               BaseList result = new BaseList("TmpMinus");
+               result.addBases(cp);
+               return result;
+       }       
+       
+       public int size()
+       {
+               return _bases.size();
+       }
+
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/FullBackup.java b/srcjar/fr/orsay/lri/varna/models/FullBackup.java
new file mode 100644 (file)
index 0000000..623b691
--- /dev/null
@@ -0,0 +1,38 @@
+package fr.orsay.lri.varna.models;
+
+import java.io.Serializable;
+
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class FullBackup implements Serializable{
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -5468893731117925140L;
+    public VARNAConfig config; 
+    public RNA rna;
+    public String name; 
+    
+    public FullBackup(VARNAConfig c, RNA r, String n){
+       config = c;
+       rna = r;
+       name = n;
+    }
+
+    public FullBackup(RNA r, String n){
+       config = null;
+       rna = r;
+       name = n;
+    }
+    
+    public String toString()
+    {
+       return name;
+    }
+    public boolean hasConfig()
+    {
+       return config!=null;
+    }
+    
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/VARNAConfig.java b/srcjar/fr/orsay/lri/varna/models/VARNAConfig.java
new file mode 100644 (file)
index 0000000..c6fb103
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBPStyle;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+public class VARNAConfig implements Serializable, Cloneable {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 2853916694420964233L;
+       /**
+        * 
+        */
+       public static final int MAJOR_VERSION = 3;
+       public static final int MINOR_VERSION = 9;
+       
+       public static String getFullName()
+       {
+               return "VARNA "+MAJOR_VERSION+"."+MINOR_VERSION;
+       }
+
+       /**
+        * Enum types and internal classes
+        */
+
+       public enum BP_STYLE implements Serializable {
+               NONE, SIMPLE, RNAVIZ, LW, LW_ALT;
+               public String toString()
+               {
+                       switch(this)
+                       {
+                               case LW: return "Leontis/Westhof (Centered)";
+                               case SIMPLE: return "Line";
+                               case RNAVIZ: return "Circles";
+                               case NONE: return "None";
+                               case LW_ALT: return "Leontis/Westhof (End)";
+                       }
+                       return "Unspecified";
+               }
+               public String getOpt()
+               {
+                       switch(this)
+                       {
+                               case NONE: return "none";
+                               case SIMPLE: return "simple";
+                               case LW: return "lw";
+                               case RNAVIZ: return "rnaviz";
+                               case LW_ALT: return "lwalt";
+                       }
+                       return "x";
+               }
+               public static BP_STYLE getStyle(String opt)
+               {
+                       for(BP_STYLE b: BP_STYLE.values())
+                       {
+                               if (opt.toLowerCase().equals(b.getOpt().toLowerCase()))
+                                       return b;
+                       }
+                       return null;
+               }
+       };
+
+       /**
+        * Default values for config options
+        */
+
+       public static final double MAX_ZOOM = 60;
+       public static final double MIN_ZOOM = 0.5;
+       public static final double DEFAULT_ZOOM = 1;
+       public static final double MAX_AMOUNT = 2;
+       public static final double MIN_AMOUNT = 1.01;
+       public static final double DEFAULT_AMOUNT = 1.2;
+       public static final double DEFAULT_BP_THICKNESS = 1.0;
+       public static final double DEFAULT_DIST_NUMBERS = 3.0;
+
+       public static final int DEFAULT_PERIOD = 10;
+
+       public static final Color DEFAULT_TITLE_COLOR = Color.black;
+       public static final Color DEFAULT_BACKBONE_COLOR = Color.DARK_GRAY.brighter();
+       public static final Color DEFAULT_BOND_COLOR = Color.blue;
+       public static final Color DEFAULT_SPECIAL_BASE_COLOR = Color.green.brighter();
+       public static final Color DEFAULT_DASH_BASE_COLOR = Color.yellow.brighter();
+       public static final double DEFAULT_BASE_OUTLINE_THICKNESS = 1.5;
+       public static final Color BASE_OUTLINE_COLOR_DEFAULT = Color.DARK_GRAY.brighter();
+       public static final Color BASE_INNER_COLOR_DEFAULT = new Color(242, 242,242);
+       public static final Color BASE_NUMBER_COLOR_DEFAULT = Color.DARK_GRAY;
+       public static final Color BASE_NAME_COLOR_DEFAULT = Color.black;
+       
+       public static final Color DEFAULT_HOVER_COLOR  =  new Color(230, 230,230);
+
+       public static final Color DEFAULT_BACKGROUND_COLOR = Color.WHITE;
+       public static final Font DEFAULT_TITLE_FONT = new Font("SansSerif", Font.BOLD,18);
+       public static final Font DEFAULT_BASE_FONT = new Font("SansSerif", Font.PLAIN, 18);
+       public static final Font DEFAULT_NUMBERS_FONT = new Font("SansSerif",
+                       Font.BOLD, 18);
+       public static final Font DEFAULT_MESSAGE_FONT = Font.decode("dialog-PLAIN-25");
+       public static final Color DEFAULT_MESSAGE_COLOR = new Color(230, 230,230);
+
+
+       public static final BP_STYLE DEFAULT_BP_STYLE = BP_STYLE.LW;
+
+       public static final ModeleColorMap DEFAULT_COLOR_MAP = ModeleColorMap.defaultColorMap();
+       public static final Color DEFAULT_COLOR_MAP_OUTLINE = Color.gray;
+       public static final double DEFAULT_BP_INCREMENT = 0.65;
+
+       public static double DEFAULT_COLOR_MAP_WIDTH = 120; 
+       public static double DEFAULT_COLOR_MAP_HEIGHT = 30; 
+       public static double DEFAULT_COLOR_MAP_X_OFFSET = 40; 
+       public static double DEFAULT_COLOR_MAP_Y_OFFSET = 0; 
+       public static int DEFAULT_COLOR_MAP_STRIPE_WIDTH = 2; 
+       public static int DEFAULT_COLOR_MAP_FONT_SIZE = 20; 
+       public static Color DEFAULT_COLOR_MAP_FONT_COLOR = Color.gray.darker(); 
+
+       public static double DEFAULT_SPACE_BETWEEN_BASES = 1.0; 
+
+       /**
+        * Various options.
+        */
+       
+       public static String XML_VAR_DRAW_OUTLINE = "drawoutline";
+       public static String XML_VAR_FILL_BASE = "fillbase";
+       public static String XML_VAR_AUTO_FIT = "autofit";
+       public static String XML_VAR_AUTO_CENTER = "autocenter";
+       public static String XML_VAR_MODIFIABLE = "modifiable";
+       public static String XML_VAR_ERRORS = "errors";
+       public static String XML_VAR_SPECIAL_BASES = "specialbases";
+       public static String XML_VAR_DASH_BASES = "dashbases";
+       public static String XML_VAR_USE_BASE_BPS = "usebasebps";
+       public static String XML_VAR_DRAW_NC = "drawnc";
+       public static String XML_VAR_DRAW_NON_PLANAR = "drawnonplanar";
+       public static String XML_VAR_SHOW_WARNINGS = "warnings";
+       public static String XML_VAR_COMPARISON_MODE = "comparison";
+       public static String XML_VAR_FLAT = "flat";
+       public static String XML_VAR_DRAW_BACKGROUND = "drawbackground";
+       public static String XML_VAR_COLOR_MAP = "drawcm";
+       public static String XML_VAR_DRAW_BACKBONE = "drawbackbone";
+       
+       public static String XML_VAR_CM_HEIGHT = "cmh";
+       public static String XML_VAR_CM_WIDTH = "cmw";
+       public static String XML_VAR_CM_X_OFFSET = "cmx";
+       public static String XML_VAR_CM_Y_OFFSET = "cmy";
+       public static String XML_VAR_DEFAULT_ZOOM = "defaultzoom";
+       public static String XML_VAR_ZOOM_AMOUNT = "zoominc";
+       public static String XML_VAR_BP_THICKNESS = "bpthick";
+       public static String XML_VAR_BASE_THICKNESS = "basethick";
+       public static String XML_VAR_DIST_NUMBERS = "distnumbers";
+       
+       public static String XML_VAR_NUM_PERIOD = "numperiod";
+       
+       public static String XML_VAR_MAIN_BP_STYLE = "bpstyle";
+
+       public static String XML_VAR_CM = "cm";
+       
+       public static String XML_VAR_BACKBONE_COLOR = "backbonecol";
+       public static String XML_VAR_HOVER_COLOR = "hovercol";
+       public static String XML_VAR_BACKGROUND_COLOR = "backgroundcol";
+       public static String XML_VAR_BOND_COLOR = "bondcol";
+       public static String XML_VAR_TITLE_COLOR = "titlecol";
+       public static String XML_VAR_SPECIAL_BASES_COLOR = "specialco";
+       public static String XML_VAR_DASH_BASES_COLOR = "dashcol";
+       public static String XML_VAR_SPACE_BETWEEN_BASES = "spacebetweenbases";
+       
+       public static String XML_VAR_TITLE_FONT = "titlefont";
+       public static String XML_VAR_NUMBERS_FONT = "numbersfont";
+       public static String XML_VAR_FONT_BASES = "basefont";
+       
+       public static String XML_VAR_CM_CAPTION = "cmcaption";
+       public static String XML_VAR_TITLE = "title";
+       
+    
+       public boolean _drawOutlineBases = true;
+       public boolean _fillBases = true;
+       public boolean _autoFit = true;
+       public boolean _autoCenter = true;
+       public boolean _modifiable = true;
+       public boolean _errorsOn = false;
+       public boolean _colorSpecialBases = false;
+       public boolean _colorDashBases = false;
+       public boolean _useBaseColorsForBPs = false;
+       public boolean _drawnNonCanonicalBP = true;
+       public boolean _drawnNonPlanarBP = true;
+       public boolean _showWarnings = false;
+       public boolean _comparisonMode = false;
+       public boolean _flatExteriorLoop = true;
+       public boolean _drawBackground = false;
+       public boolean _drawColorMap = false;
+       public boolean _drawBackbone = true;
+       
+       public double _colorMapHeight  = DEFAULT_COLOR_MAP_HEIGHT; 
+       public double _colorMapWidth   = DEFAULT_COLOR_MAP_WIDTH; 
+       public double _colorMapXOffset = DEFAULT_COLOR_MAP_X_OFFSET; 
+       public double _colorMapYOffset = DEFAULT_COLOR_MAP_Y_OFFSET; 
+       public double _zoom            = DEFAULT_ZOOM;
+       public double _zoomAmount      = DEFAULT_AMOUNT;
+       public double _bpThickness     = 1.0;
+       public double _baseThickness   = DEFAULT_BASE_OUTLINE_THICKNESS;
+       public double _distNumbers     = DEFAULT_DIST_NUMBERS;
+       public double _spaceBetweenBases = DEFAULT_SPACE_BETWEEN_BASES;
+       
+       public int _numPeriod = DEFAULT_PERIOD;
+       public BP_STYLE _mainBPStyle = DEFAULT_BP_STYLE;
+       
+       public ModeleColorMap _cm = DEFAULT_COLOR_MAP;  
+
+       public Color _backboneColor      = DEFAULT_BACKBONE_COLOR;
+       public Color _hoverColor         = DEFAULT_HOVER_COLOR;
+       public Color _backgroundColor    = DEFAULT_BACKGROUND_COLOR;
+       public Color _bondColor          = DEFAULT_BOND_COLOR;
+       public Color _titleColor         = DEFAULT_TITLE_COLOR;
+       public Color _specialBasesColor  = DEFAULT_SPECIAL_BASE_COLOR;
+       public Color _dashBasesColor     = DEFAULT_DASH_BASE_COLOR;
+       
+       public Font _titleFont           = DEFAULT_TITLE_FONT;
+       public Font _numbersFont         = DEFAULT_NUMBERS_FONT;
+       public Font _fontBasesGeneral    = DEFAULT_BASE_FONT;
+       
+       public String _colorMapCaption = "";
+       //public String _title = "";
+
+       
+       public static String XML_ELEMENT_NAME = "config";
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_DRAW_OUTLINE,"CDATA",   ""+_drawOutlineBases);
+               atts.addAttribute("","",XML_VAR_FILL_BASE,"CDATA",      ""+_fillBases);
+               atts.addAttribute("","",XML_VAR_AUTO_FIT,"CDATA",       ""+_autoFit);
+               atts.addAttribute("","",XML_VAR_AUTO_CENTER,"CDATA",    ""+_autoCenter);
+               atts.addAttribute("","",XML_VAR_MODIFIABLE,"CDATA",     ""+_modifiable);
+               atts.addAttribute("","",XML_VAR_ERRORS,"CDATA",         ""+_errorsOn);
+               atts.addAttribute("","",XML_VAR_SPECIAL_BASES,"CDATA",  ""+_colorSpecialBases);
+               atts.addAttribute("","",XML_VAR_DASH_BASES,"CDATA",     ""+_colorDashBases);
+               atts.addAttribute("","",XML_VAR_USE_BASE_BPS,"CDATA",   ""+_useBaseColorsForBPs);
+               atts.addAttribute("","",XML_VAR_DRAW_NC,"CDATA",        ""+_drawnNonCanonicalBP);
+               atts.addAttribute("","",XML_VAR_DRAW_NON_PLANAR,"CDATA",""+_drawnNonPlanarBP);
+               atts.addAttribute("","",XML_VAR_SHOW_WARNINGS,"CDATA",  ""+_showWarnings);
+               atts.addAttribute("","",XML_VAR_COMPARISON_MODE,"CDATA",""+_comparisonMode);
+               atts.addAttribute("","",XML_VAR_FLAT,"CDATA",           ""+_flatExteriorLoop);
+               atts.addAttribute("","",XML_VAR_DRAW_BACKGROUND,"CDATA",""+_drawBackground);
+               atts.addAttribute("","",XML_VAR_COLOR_MAP,"CDATA",      ""+_drawColorMap);
+               atts.addAttribute("","",XML_VAR_DRAW_BACKBONE,"CDATA",  ""+_drawBackbone);
+
+               atts.addAttribute("","",XML_VAR_CM_HEIGHT,"CDATA",      ""+_colorMapHeight);
+               atts.addAttribute("","",XML_VAR_CM_WIDTH,"CDATA",       ""+_colorMapWidth);
+               atts.addAttribute("","",XML_VAR_CM_X_OFFSET,"CDATA",    ""+_colorMapXOffset);
+               atts.addAttribute("","",XML_VAR_CM_Y_OFFSET,"CDATA",    ""+_colorMapYOffset);
+               atts.addAttribute("","",XML_VAR_DEFAULT_ZOOM,"CDATA",   ""+_zoom);
+               atts.addAttribute("","",XML_VAR_ZOOM_AMOUNT,"CDATA",    ""+_zoomAmount);
+               atts.addAttribute("","",XML_VAR_BP_THICKNESS,"CDATA",   ""+_bpThickness);
+               atts.addAttribute("","",XML_VAR_BASE_THICKNESS,"CDATA", ""+_baseThickness);
+               atts.addAttribute("","",XML_VAR_DIST_NUMBERS,"CDATA",   ""+_distNumbers);
+               atts.addAttribute("","",XML_VAR_SPACE_BETWEEN_BASES,"CDATA",   ""+_spaceBetweenBases);
+               
+                       
+               atts.addAttribute("","",XML_VAR_NUM_PERIOD,"CDATA",     ""+_numPeriod);
+                       
+               atts.addAttribute("","",XML_VAR_MAIN_BP_STYLE,"CDATA",  ""+_mainBPStyle.getOpt());
+                       
+               atts.addAttribute("","",XML_VAR_BACKBONE_COLOR,"CDATA",     XMLUtils.toHTMLNotation(_backboneColor));
+               atts.addAttribute("","",XML_VAR_HOVER_COLOR,"CDATA",        XMLUtils.toHTMLNotation(_hoverColor));
+               atts.addAttribute("","",XML_VAR_BACKGROUND_COLOR,"CDATA",   XMLUtils.toHTMLNotation(_backgroundColor));
+               atts.addAttribute("","",XML_VAR_BOND_COLOR,"CDATA",         XMLUtils.toHTMLNotation(_bondColor));
+               atts.addAttribute("","",XML_VAR_TITLE_COLOR,"CDATA",        XMLUtils.toHTMLNotation(_titleColor));
+               atts.addAttribute("","",XML_VAR_SPECIAL_BASES_COLOR,"CDATA",XMLUtils.toHTMLNotation(_specialBasesColor));
+               atts.addAttribute("","",XML_VAR_DASH_BASES_COLOR,"CDATA",   XMLUtils.toHTMLNotation(_dashBasesColor)); 
+
+               atts.addAttribute("","",XML_VAR_CM,"CDATA",                             _cm.getParamEncoding()); 
+               
+               
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               XMLUtils.toXML(hd, _titleFont,XML_VAR_TITLE_FONT);
+               XMLUtils.toXML(hd, _numbersFont,XML_VAR_NUMBERS_FONT);
+               XMLUtils.toXML(hd, _fontBasesGeneral,XML_VAR_FONT_BASES);
+               
+               XMLUtils.exportCDATAElem(hd,XML_VAR_CM_CAPTION, _colorMapCaption);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+       
+       
+       
+
+       public void loadFromXMLAttributes(Attributes attributes)
+       {
+               _drawOutlineBases      = Boolean.parseBoolean(attributes.getValue(XML_VAR_DRAW_OUTLINE));                             
+               _fillBases             = Boolean.parseBoolean(attributes.getValue(XML_VAR_FILL_BASE));                                
+               _autoFit              = Boolean.parseBoolean(attributes.getValue(XML_VAR_AUTO_FIT));                                 
+               _autoCenter           = Boolean.parseBoolean(attributes.getValue(XML_VAR_AUTO_CENTER));                              
+               _modifiable           = Boolean.parseBoolean(attributes.getValue(XML_VAR_MODIFIABLE));                               
+               _errorsOn             = Boolean.parseBoolean(attributes.getValue(XML_VAR_ERRORS));                                   
+               _colorSpecialBases    = Boolean.parseBoolean(attributes.getValue(XML_VAR_SPECIAL_BASES));                            
+               _colorDashBases       = Boolean.parseBoolean(attributes.getValue(XML_VAR_DASH_BASES));                               
+               _useBaseColorsForBPs  = Boolean.parseBoolean(attributes.getValue(XML_VAR_USE_BASE_BPS));                             
+               _drawnNonCanonicalBP  = Boolean.parseBoolean(attributes.getValue(XML_VAR_DRAW_NC));                                  
+               _drawnNonPlanarBP     = Boolean.parseBoolean(attributes.getValue(XML_VAR_DRAW_NON_PLANAR));                          
+               _showWarnings         = Boolean.parseBoolean(attributes.getValue(XML_VAR_SHOW_WARNINGS));                            
+               _comparisonMode       = Boolean.parseBoolean(attributes.getValue(XML_VAR_COMPARISON_MODE));                          
+               _flatExteriorLoop     = Boolean.parseBoolean(attributes.getValue(XML_VAR_FLAT));                                     
+               _drawBackground       = Boolean.parseBoolean(attributes.getValue(XML_VAR_DRAW_BACKGROUND));                          
+               _drawColorMap         = Boolean.parseBoolean(attributes.getValue(XML_VAR_COLOR_MAP));                                
+               _drawBackbone         = Boolean.parseBoolean(attributes.getValue(XML_VAR_DRAW_BACKBONE));     
+               
+               _colorMapHeight       = Double.parseDouble(attributes.getValue(XML_VAR_CM_HEIGHT));
+               _colorMapWidth        = Double.parseDouble(attributes.getValue(XML_VAR_CM_WIDTH));
+               _colorMapXOffset      = Double.parseDouble(attributes.getValue(XML_VAR_CM_X_OFFSET));
+               _colorMapYOffset      = Double.parseDouble(attributes.getValue(XML_VAR_CM_Y_OFFSET));
+               _zoom                             = Double.parseDouble(attributes.getValue(XML_VAR_DEFAULT_ZOOM));
+               _zoomAmount                   = Double.parseDouble(attributes.getValue(XML_VAR_ZOOM_AMOUNT));
+               _bpThickness          = Double.parseDouble(attributes.getValue(XML_VAR_BP_THICKNESS));
+               _baseThickness        = Double.parseDouble(attributes.getValue(XML_VAR_BASE_THICKNESS));
+               _distNumbers          = Double.parseDouble(attributes.getValue(XML_VAR_DIST_NUMBERS));
+               _spaceBetweenBases    = XMLUtils.getDouble(attributes, XML_VAR_SPACE_BETWEEN_BASES, DEFAULT_SPACE_BETWEEN_BASES);
+                                                                       
+               _numPeriod            = Integer.parseInt(attributes.getValue(XML_VAR_NUM_PERIOD));
+                                                                       
+               _mainBPStyle          = BP_STYLE.getStyle(attributes.getValue(XML_VAR_MAIN_BP_STYLE));           
+                                                                       
+               _backboneColor        = Color.decode(attributes.getValue(XML_VAR_BACKBONE_COLOR));                     
+               _hoverColor           = Color.decode(attributes.getValue(XML_VAR_HOVER_COLOR));                        
+               _backgroundColor      = Color.decode(attributes.getValue(XML_VAR_BACKGROUND_COLOR));                   
+               _bondColor            = Color.decode(attributes.getValue(XML_VAR_BOND_COLOR));                         
+               _titleColor           = Color.decode(attributes.getValue(XML_VAR_TITLE_COLOR));                        
+               _specialBasesColor    = Color.decode(attributes.getValue(XML_VAR_SPECIAL_BASES_COLOR));                
+               _dashBasesColor       = Color.decode(attributes.getValue(XML_VAR_DASH_BASES_COLOR));                   
+                                                                             
+               _cm                   = ModeleColorMap.parseColorMap(attributes.getValue(XML_VAR_CM)); 
+       }
+       
+       
+       
+    public VARNAConfig clone ()
+    {
+      
+      /**
+       * @j2sNative
+       * 
+       * return this;
+       * 
+       */
+        try
+        {
+            ByteArrayOutputStream out = new ByteArrayOutputStream ();
+            ObjectOutputStream oout = new ObjectOutputStream (out);
+            oout.writeObject (this);
+            
+            ObjectInputStream in = new ObjectInputStream (
+                new ByteArrayInputStream (out.toByteArray ()));
+            return (VARNAConfig)in.readObject ();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException ("cannot clone class [" +
+                this.getClass ().getName () + "] via serialization: " +
+                e.toString ());
+        }
+    }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/VARNAConfigLoader.java b/srcjar/fr/orsay/lri/varna/models/VARNAConfigLoader.java
new file mode 100644 (file)
index 0000000..f62b187
--- /dev/null
@@ -0,0 +1,1357 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models;
+
+/*
+ * VARNA is a Java library for quick automated drawings RNA secondary structure
+ * Copyright (C) 2007 Yann Ponty
+ * 
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.ExceptionDrawingAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceParameterLoader;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+ * An RNA 2d Panel demo applet
+ * 
+ * @author Yann Ponty
+ * 
+ */
+
+public class VARNAConfigLoader {
+
+       private static final int MAXSTYLE = 50;
+
+       // Applet Options
+
+       public static String algoOpt = "algorithm";
+       public static String annotationsOpt = "annotations";
+       public static String applyBasesStyleOpt = "applyBasesStyle";
+       public static String auxBPsOpt = "auxBPs";
+       public static String autoHelicesOpt = "autoHelices";
+       public static String autoInteriorLoopsOpt = "autoInteriorLoops";
+       public static String autoTerminalLoopsOpt = "autoTerminalLoops";
+
+       public static String backboneColorOpt = "backbone";
+       public static String backgroundColorOpt = "background";
+       public static String baseInnerColorOpt = "baseInner";
+       public static String baseNameColorOpt = "baseName";
+       public static String baseNumbersColorOpt = "baseNum";
+       public static String baseOutlineColorOpt = "baseOutline";
+       public static String basesStyleOpt = "basesStyle";
+       public static String borderOpt = "border";
+       public static String bondColorOpt = "bp";
+       public static String bpIncrementOpt = "bpIncrement";
+       public static String bpStyleOpt = "bpStyle";
+
+       public static String colorMapOpt = "colorMap";
+       public static String colorMapCaptionOpt = "colorMapCaption";
+       public static String colorMapDefOpt = "colorMapStyle";
+       public static String colorMapMinOpt = "colorMapMin";
+       public static String colorMapMaxOpt = "colorMapMax";
+       public static String comparisonModeOpt = "comparisonMode";
+       public static String chemProbOpt = "chemProb";
+       public static String customBasesOpt = "customBases";
+       public static String customBPsOpt = "customBPs";
+
+       public static String drawNCOpt = "drawNC";
+       public static String drawBasesOpt = "drawBases";
+       public static String drawTertiaryOpt = "drawTertiary";
+       public static String drawColorMapOpt = "drawColorMap";
+       public static String drawBackboneOpt = "drawBackbone";
+
+       public static String errorOpt = "error";
+
+       public static String fillBasesOpt = "fillBases";
+       public static String firstSequenceForComparisonOpt = "firstSequence";
+       public static String firstStructureForComparisonOpt = "firstStructure";
+       public static String flatExteriorLoopOpt = "flat";
+       public static String flipOpt = "flip";
+
+       public static String gapsBaseColorOpt = "gapsColor";
+
+       public static String highlightRegionOpt = "highlightRegion";
+
+       public static String nonStandardColorOpt = "nsBasesColor";
+       public static String numColumnsOpt = "rows";
+       public static String numRowsOpt = "columns";
+
+       public static String orientationOpt = "orientation";
+
+       public static String modifiableOpt = "modifiable";
+
+       public static String periodNumOpt = "periodNum";
+
+       public static String rotationOpt = "rotation";
+
+       public static String secondSequenceForComparisonOpt = "secondSequence";
+       public static String secondStructureForComparisonOpt = "secondStructure";
+       public static String sequenceOpt = "sequenceDBN";
+       public static String spaceBetweenBasesOpt = "spaceBetweenBases";
+       public static String structureOpt = "structureDBN";
+
+       public static String titleOpt = "title";
+       public static String titleColorOpt = "titleColor";
+       public static String titleSizeOpt = "titleSize";
+
+       public static String URLOpt = "url";
+
+       public static String warningOpt = "warning";
+
+       public static String zoomOpt = "zoom";
+       public static String zoomAmountOpt = "zoomAmount";
+
+       // Applet assignable parameters
+       private String _algo;
+       public String _annotations;
+       public String _chemProbs;
+       private double _rotation;
+
+       private String _sseq;
+       private String _sstruct;
+
+       private int _numRows;
+       private int _numColumns;
+
+       private String _title;
+       private int _titleSize;
+       private Color _titleColor;
+
+       private String _auxBPs;
+       private String _highlightRegion;
+
+       private boolean _autoHelices;
+       private boolean _autoInteriorLoops;
+       private boolean _autoTerminalLoops;
+
+       private boolean _drawBackbone;
+       private Color _backboneColor;
+       private Color _bondColor;
+       private VARNAConfig.BP_STYLE _bpStyle;
+       private Color _baseOutlineColor;
+       private Color _baseInnerColor;
+       private Color _baseNumColor;
+       private Color _baseNameColor;
+       private Color _gapsColor;
+       private Color _nonStandardColor;
+
+       private boolean _flatExteriorLoop;
+       private String _flip;
+
+       private String _customBases;
+       private String _customBPs;
+
+       private String _colorMapStyle;
+       private String _colorMapCaption;
+       private String _colorMapValues;
+       private double _colorMapMin = Double.MIN_VALUE;
+       private double _colorMapMax = Double.MAX_VALUE;
+
+       private double _spaceBetweenBases = Double.MIN_VALUE;
+
+       private boolean _drawNC;
+       private boolean _drawBases;
+       private boolean _drawTertiary;
+       private boolean _drawColorMap;
+
+       private boolean _fillBases;
+
+       private int _periodResNum;
+       private Dimension _border;
+
+       private Color _backgroundColor;
+
+       private String _orientation;
+
+       private boolean _warning, _error;
+
+       private boolean _modifiable;
+
+       private double _zoom, _zoomAmount;
+
+       private ArrayList<ModelBaseStyle> _basesStyleList;
+
+       private boolean _comparisonMode;
+
+       private String _firstSequence;
+       private String _secondSequence;
+       private String _firstStructure;
+       private String _secondStructure;
+
+       private VARNAPanel _mainSurface;
+
+       private boolean _useNonStandardColor;
+       private boolean _useGapsColor;
+       private double _bpIncrement;
+
+       private boolean _useInnerBaseColor;
+       private boolean _useBaseNameColor;
+       private boolean _useBaseNumbersColor;
+       private boolean _useBaseOutlineColor;
+
+       private String _URL;
+
+       protected ArrayList<VARNAPanel> _VARNAPanelList = new ArrayList<VARNAPanel>();
+
+       InterfaceParameterLoader _optionProducer;
+
+       public VARNAConfigLoader(InterfaceParameterLoader il) {
+               _optionProducer = il;
+       }
+
+       public ArrayList<VARNAPanel> createVARNAPanels()
+                       throws ExceptionParameterError,
+                       ExceptionModeleStyleBaseSyntaxError, ExceptionNonEqualLength,
+                       IOException, ExceptionFileFormatOrSyntax, ExceptionLoadingFailed {
+               _VARNAPanelList.clear();
+               retrieveParametersValues();
+               return _VARNAPanelList;
+       }
+
+       public int getNbRows() {
+               return this._numRows;
+       }
+
+       public int getNbColumns() {
+               return this._numColumns;
+       }
+
+       private void initValues() {
+
+               // Applet assignable parameters
+               _algo = "radiate";
+               _auxBPs = "";
+               _autoHelices = false;
+               _autoInteriorLoops = false;
+               _autoTerminalLoops = false;
+               _annotations = "";
+               _backgroundColor = VARNAConfig.DEFAULT_BACKGROUND_COLOR;
+               _customBases = "";
+               _customBPs = "";
+               _chemProbs = "";
+
+               _colorMapStyle = "";
+               _colorMapValues = "";
+               _colorMapCaption = "";
+               _drawColorMap = false;
+
+               _drawBases = true;
+               _fillBases = true;
+               _drawNC = true;
+               _drawTertiary = true;
+               _border = new Dimension(0, 0);
+               _sseq = "";// =
+               // "CAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIACAGCACGACACUAGCAGUCAGUGUCAGACUGCAIA";
+               _sstruct = "";// =
+               // "..(((((...(((((...(((((...(((((.....)))))...))))).....(((((...(((((.....)))))...))))).....)))))...)))))..";
+               _periodResNum = VARNAConfig.DEFAULT_PERIOD;
+               _rotation = 0.0;
+               _title = "";
+               _titleSize = VARNAConfig.DEFAULT_TITLE_FONT.getSize();
+
+               _backboneColor = VARNAConfig.DEFAULT_BACKBONE_COLOR;
+               _drawBackbone = true;
+
+               _bondColor = VARNAConfig.DEFAULT_BOND_COLOR;
+               _bpStyle = VARNAConfig.DEFAULT_BP_STYLE;
+
+               _highlightRegion = "";
+
+               _baseOutlineColor = VARNAConfig.BASE_OUTLINE_COLOR_DEFAULT;
+               _baseInnerColor = VARNAConfig.BASE_INNER_COLOR_DEFAULT;
+               _baseNumColor = VARNAConfig.BASE_NUMBER_COLOR_DEFAULT;
+               _baseNameColor = VARNAConfig.BASE_NAME_COLOR_DEFAULT;
+
+               _titleColor = VARNAConfig.DEFAULT_TITLE_COLOR;
+               _warning = false;
+               _error = true;
+               _modifiable = true;
+               _zoom = VARNAConfig.DEFAULT_ZOOM;
+               _zoomAmount = VARNAConfig.DEFAULT_AMOUNT;
+
+               _comparisonMode = false;
+               _firstSequence = "";
+               _firstStructure = "";
+               _secondSequence = "";
+               _secondStructure = "";
+
+               _gapsColor = VARNAConfig.DEFAULT_DASH_BASE_COLOR;
+               _useGapsColor = false;
+               _nonStandardColor = VARNAConfig.DEFAULT_SPECIAL_BASE_COLOR;
+               _useNonStandardColor = false;
+
+               _useInnerBaseColor = false;
+               _useBaseNameColor = false;
+               _useBaseNumbersColor = false;
+               _useBaseOutlineColor = false;
+
+               _bpIncrement = VARNAConfig.DEFAULT_BP_INCREMENT;
+
+               _URL = "";
+               _flatExteriorLoop = true;
+               _flip = "";
+               _orientation = "";
+               _spaceBetweenBases = VARNAConfig.DEFAULT_SPACE_BETWEEN_BASES;
+       }
+
+       public static Color getSafeColor(String col, Color def) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+
+               } catch (NumberFormatException e) {
+                       try {
+                               result = Color.getColor(col, def);
+                       } catch (Exception e2) {
+                               // Not a valid color
+                               return def;
+                       }
+               }
+               return result;
+       }
+
+       public static final String LEONTIS_WESTHOF_BP_STYLE = "lw";
+       public static final String LEONTIS_WESTHOF_BP_STYLE_ALT = "lwalt";
+       public static final String SIMPLE_BP_STYLE = "simple";
+       public static final String RNAVIZ_BP_STYLE = "rnaviz";
+       public static final String NONE_BP_STYLE = "none";
+
+       private VARNAConfig.BP_STYLE getSafeBPStyle(String opt,
+                       VARNAConfig.BP_STYLE def) {
+               VARNAConfig.BP_STYLE b = VARNAConfig.BP_STYLE.getStyle(opt);
+               if (b!= null)
+               {
+                       return b;
+               } else {
+                       return def;
+               }
+       }
+
+       public static String[][] getParameterInfo() {
+               String[][] info = {
+                               // Parameter Name Kind of Value Description
+                               {
+                                               algoOpt,
+                                               "String",
+                                               "Drawing algorithm, choosen from ["
+                                                               + VARNAConfigLoader.ALGORITHM_NAVIEW + ","
+                                                               + VARNAConfigLoader.ALGORITHM_LINE + ","
+                                                               + VARNAConfigLoader.ALGORITHM_RADIATE + ","
+                                                               + VARNAConfigLoader.ALGORITHM_CIRCULAR + "]" },
+                               { annotationsOpt, "string", "A set of textual annotations" },
+                               { applyBasesStyleOpt, "String", "Base style application" },
+                               {
+                                               auxBPsOpt,
+                                               "String",
+                                               "Adds a list of (possibly non-canonical) base-pairs to those already defined by the main secondary structure (Ex: \"(1,10);(2,11);(3,12)\"). Custom BP styles can be specified (Ex: \"(2,11):thickness=4;(3,12):color=#FF0000\")." },
+                               { autoHelicesOpt, "", "" },
+                               { autoInteriorLoopsOpt, "", "" },
+                               { autoTerminalLoopsOpt, "", "" },
+                               { backboneColorOpt, "Color", "Backbone color (Ex: #334455)" },
+                               { backgroundColorOpt, "Color", "Background color (Ex: #334455)" },
+                               { baseInnerColorOpt, "Color",
+                                               "Default value for inner base color (Ex: #334455)" },
+                               { baseNameColorOpt, "Color",
+                                               "Residues font color (Ex: #334455)" },
+                               { baseNumbersColorOpt, "Color",
+                                               "Base numbers font color (Ex: #334455)" },
+                               { baseOutlineColorOpt, "Color",
+                                               "Base outline color (Ex: #334455)" },
+                               { basesStyleOpt, "String", "Base style declaration" },
+                               { borderOpt, "String",
+                                               "Border width and height in pixels (Ex: \"20x40\")" },
+                               { bondColorOpt, "Color", "Base pair color (Ex: #334455)" },
+                               { bpIncrementOpt, "float",
+                                               "Distance between nested base-pairs (i.e. arcs) in linear representation" },
+                               {
+                                               bpStyleOpt,
+                                               "String",
+                                               "Look and feel for base pairs drawings, choosen from ["
+                                                               + VARNAConfigLoader.LEONTIS_WESTHOF_BP_STYLE+ "," 
+                                                               + VARNAConfigLoader.LEONTIS_WESTHOF_BP_STYLE_ALT+ "," 
+                                                               + VARNAConfigLoader.NONE_BP_STYLE + ","
+                                                               + VARNAConfigLoader.SIMPLE_BP_STYLE + ","
+                                                               + VARNAConfigLoader.RNAVIZ_BP_STYLE + "]" },
+                               { chemProbOpt, "", "" },
+                               {
+                                               colorMapOpt,
+                                               "String",
+                                               "Associates a list of numerical values (eg '0.2,0.4,0.6,0.8') with the RNA bases with respect to their natural order, and modifies the color used to fill these bases according to current color map style." },
+                               { colorMapCaptionOpt, "String",
+                                               "Sets current color map caption." },
+                               {
+                                               colorMapDefOpt,
+                                               "String",
+                                               "Selects a specific color map style. It can be either one of the predefined styles (eg 'red', 'green', 'blue', 'bw', 'heat', 'energy') or a new one (eg '0:#FFFF00;1:#ffFFFF;6:#FF0000')." },
+                               { colorMapMinOpt, "", "" },
+                               { colorMapMaxOpt, "", "" },
+                               { comparisonModeOpt, "boolean", "Activates comparison mode" },
+                               { customBasesOpt, "", "" },
+                               { customBPsOpt, "", "" },
+                               { drawBackboneOpt, "boolean",
+                                               "True if the backbone must be drawn, false otherwise" },
+                               { drawColorMapOpt, "", "" },
+                               { drawNCOpt, "boolean",
+                                               "Toggles on/off display of non-canonical base-pairs" },
+                               { drawBasesOpt, "boolean", "Shows/hide the outline of bases" },
+                               { drawTertiaryOpt, "boolean",
+                                               "Toggles on/off display of tertiary interaction, ie pseudoknots" },
+                               { errorOpt, "boolean", "Show errors" },
+                               { fillBasesOpt, "boolean",
+                                               "Fills or leaves empty the inner portions of bases" },
+                               { firstSequenceForComparisonOpt, "String",
+                                               "In comparison mode, sequence of first RNA" },
+                               { firstStructureForComparisonOpt, "String",
+                                               "In comparison mode, structure of first RNA" },
+                               { flatExteriorLoopOpt, "boolean",
+                                               "Toggles on/off (true/false) drawing exterior bases on a straight line" },
+                               { flipOpt, "String",
+                                               "Draws a set of exterior helices, identified by the argument string, in clockwise order (default drawing is counter-clockwise). The argument is a semicolon-separated list of helices, each identified by a base or a base-pair (eg. \"2;20-34\")." },
+                               { gapsBaseColorOpt, "Color",
+                                               "Define and use custom color for gaps bases in comparison mode" },
+                               { highlightRegionOpt, "string", "Highlight a set of contiguous regions" },
+                               { modifiableOpt, "boolean", "Allows/prohibits modifications" },
+                               { nonStandardColorOpt, "Color",
+                                               "Define and use custom color for non-standard bases in comparison mode" },
+                               { numColumnsOpt, "int", "Sets number of columns" },
+                               { numRowsOpt, "int", "Sets number of rows" },
+                               {
+                                               orientationOpt,
+                                               "float",
+                                               "Sets the general orientation of an RNA, i.e. the deviation of the longest axis (defined by the most distant couple of bases) from the horizontal axis." },
+                               { periodNumOpt, "int", "Periodicity of base-numbering" },
+                               { secondSequenceForComparisonOpt, "String",
+                                               "In comparison mode, sequence of second RNA" },
+                               { secondStructureForComparisonOpt, "String",
+                                               "In comparison mode, structure of second RNA" },
+                               { sequenceOpt, "String", "Raw RNA sequence" },
+                               { structureOpt, "String",
+                                               "RNA structure given in dot bracket notation (DBN)" },
+                               {
+                                               rotationOpt,
+                                               "float",
+                                               "Rotates RNA after initial drawing (Ex: '20' for a 20 degree counter-clockwise rotation)" },
+                               { titleOpt, "String", "RNA drawing title" },
+                               { titleColorOpt, "Color", "Title color (Ex: #334455)" },
+                               { titleSizeOpt, "int", "Title font size" },
+                               { spaceBetweenBasesOpt, "float",
+                                               "Sets the space between consecutive bases" },
+                               { warningOpt, "boolean", "Show warnings" },
+                               { zoomOpt, "int", "Zoom coefficient" },
+                               { zoomAmountOpt, "int", "Zoom increment on user interaction" } };
+               return info;
+       }
+
+       private void retrieveParametersValues() throws ExceptionParameterError {
+
+               _numRows = 1;
+               _numColumns = 1;
+               _basesStyleList = new ArrayList<ModelBaseStyle>();
+
+               try {
+                       _numRows = Integer.parseInt(_optionProducer.getParameterValue(
+                                       numRowsOpt, "" + _numRows));
+               } catch (NumberFormatException e) {
+                       throw new ExceptionParameterError(e.getMessage(), "'"
+                                       + _optionProducer.getParameterValue(numRowsOpt, ""
+                                                       + _numRows)
+                                       + "' is not a integer value for the number of rows !");
+               }
+               try {
+                       _numColumns = Integer.parseInt(_optionProducer.getParameterValue(
+                                       numColumnsOpt, "" + _numColumns));
+               } catch (NumberFormatException e) {
+                       throw new ExceptionParameterError(e.getMessage(), "'"
+                                       + _optionProducer.getParameterValue(numColumnsOpt, ""
+                                                       + _numColumns)
+                                       + "' is not a integer value for the number of columns !");
+               }
+
+               String tmp = null;
+               for (int i = 0; i < MAXSTYLE; i++) {
+                       String opt = basesStyleOpt + i;
+                       tmp = _optionProducer.getParameterValue(opt, null);
+                       // System.out.println(opt+"->"+tmp);
+                       if (tmp != null) {
+                               ModelBaseStyle msb = new ModelBaseStyle();
+                               try {
+                                       msb.assignParameters(tmp);
+                               } catch (ExceptionModeleStyleBaseSyntaxError e) {
+                                       VARNAPanel.emitWarningStatic(e, null);
+                               }
+                               _basesStyleList.add(msb);
+                       } else {
+                               _basesStyleList.add(null);
+                       }
+               }
+
+               // _containerApplet.getLayout().
+               int x;
+               String n;
+               initValues();
+               for (int i = 0; i < _numColumns; i++) {
+                       for (int j = 0; j < _numRows; j++) {
+                               try {
+                                       // initValues();
+                                       x = 1 + j + i * _numRows;
+                                       n = "" + x;
+                                       if ((_numColumns == 1) && (_numRows == 1)) {
+                                               n = "";
+                                       }
+                                       _useGapsColor = false;
+                                       _useNonStandardColor = false;
+
+                                       tmp = _optionProducer.getParameterValue(baseNameColorOpt
+                                                       + n, "");
+                                       if (!tmp.equals("")) {
+                                               _useBaseNameColor = true;
+                                               _baseNameColor = getSafeColor(tmp, _baseNameColor);
+                                       }
+                                       tmp = _optionProducer.getParameterValue(baseNumbersColorOpt
+                                                       + n, "");
+                                       if (!tmp.equals("")) {
+                                               _useBaseNumbersColor = true;
+                                               _baseNumColor = getSafeColor(tmp, _baseNumColor);
+                                       }
+                                       tmp = _optionProducer.getParameterValue(baseOutlineColorOpt
+                                                       + n, "");
+                                       if (!tmp.equals("")) {
+                                               _useBaseOutlineColor = true;
+                                               _baseOutlineColor = getSafeColor(tmp, _baseOutlineColor);
+                                       }
+                                       tmp = _optionProducer.getParameterValue(baseInnerColorOpt
+                                                       + n, "");
+                                       if (!tmp.equals("")) {
+                                               _useInnerBaseColor = true;
+                                               _baseInnerColor = getSafeColor(tmp, _baseInnerColor);
+                                       }
+
+                                       tmp = _optionProducer.getParameterValue(nonStandardColorOpt
+                                                       + n, "");
+                                       if (!tmp.equals("")) {
+                                               _nonStandardColor = getSafeColor(tmp, _nonStandardColor);
+                                               _useNonStandardColor = true;
+                                       }
+                                       tmp = _optionProducer.getParameterValue(gapsBaseColorOpt
+                                                       + n, _gapsColor.toString());
+                                       if (!tmp.equals("")) {
+                                               _gapsColor = getSafeColor(tmp, _gapsColor);
+                                               _useGapsColor = true;
+                                       }
+                                       try {
+                                               _rotation = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(rotationOpt + n,
+                                                                               Double.toString(_rotation)));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(e.getMessage(), "'"
+                                                               + _optionProducer.getParameterValue(rotationOpt
+                                                                               + n, "" + _rotation)
+                                                               + "' is not a valid float value for rotation!");
+                                       }
+
+                                       try {
+                                               _colorMapMin = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(colorMapMinOpt + n,
+                                                                               Double.toString(this._colorMapMin)));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               colorMapMinOpt + n, ""
+                                                                                                               + _colorMapMin)
+                                                                               + "' is not a valid double value for min color map values range!");
+                                       }
+
+                                       try {
+                                               _colorMapMax = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(colorMapMaxOpt + n,
+                                                                               Double.toString(this._colorMapMax)));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               colorMapMaxOpt + n, ""
+                                                                                                               + _colorMapMax)
+                                                                               + "' is not a valid double value for max color map values range!");
+                                       }
+
+                                       try {
+                                               _bpIncrement = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(bpIncrementOpt + n,
+                                                                               Double.toString(_bpIncrement)));
+                                       } catch (NumberFormatException e) {
+                                       }
+
+                                       try {
+                                               _periodResNum = Integer.parseInt(_optionProducer
+                                                               .getParameterValue(periodNumOpt + n, ""
+                                                                               + _periodResNum));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               periodNumOpt + n, ""
+                                                                                                               + _periodResNum)
+                                                                               + "' is not a valid integer value for the period of residue numbers!");
+                                       }
+                                       try {
+                                               _titleSize = Integer.parseInt(_optionProducer
+                                                               .getParameterValue(titleSizeOpt + n, ""
+                                                                               + _titleSize));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               titleSizeOpt + n, ""
+                                                                                                               + _titleSize)
+                                                                               + "' is not a valid integer value for the number of rows !");
+                                       }
+
+                                       try {
+                                               _zoom = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(zoomOpt + n, "" + _zoom));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               zoomOpt + n, "" + _zoom)
+                                                                               + "' is not a valid integer value for the zoom !");
+                                       }
+
+                                       try {
+                                               _zoomAmount = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(zoomAmountOpt + n, ""
+                                                                               + _zoomAmount));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               zoomAmountOpt + n, ""
+                                                                                                               + _zoomAmount)
+                                                                               + "' is not a valid integer value for the zoom amount!");
+                                       }
+
+                                       try {
+                                               _spaceBetweenBases = Double.parseDouble(_optionProducer
+                                                               .getParameterValue(spaceBetweenBasesOpt + n, ""
+                                                                               + _spaceBetweenBases));
+                                       } catch (NumberFormatException e) {
+                                               throw new ExceptionParameterError(
+                                                               e.getMessage(),
+                                                               "'"
+                                                                               + _optionProducer.getParameterValue(
+                                                                                               spaceBetweenBasesOpt + n, ""
+                                                                                                               + _spaceBetweenBases)
+                                                                               + "' is not a valid integer value for the base spacing!");
+                                       }
+
+                                       _drawBases = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(drawBasesOpt + n, ""
+                                                                       + _drawBases));
+                                       _fillBases = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(fillBasesOpt + n, ""
+                                                                       + _fillBases));
+                                       _autoHelices = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(autoHelicesOpt + n, ""
+                                                                       + _autoHelices));
+                                       _drawColorMap = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(drawColorMapOpt + n, ""
+                                                                       + _drawColorMap));
+                                       _drawBackbone = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(drawBackboneOpt + n, ""
+                                                                       + _drawBackbone));
+                                       _colorMapValues = _optionProducer.getParameterValue(
+                                                       colorMapOpt + n, _colorMapValues);
+                                       _autoTerminalLoops = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(autoTerminalLoopsOpt + n, ""
+                                                                       + _autoTerminalLoops));
+                                       _autoInteriorLoops = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(autoInteriorLoopsOpt + n, ""
+                                                                       + _autoInteriorLoops));
+                                       _drawNC = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(drawNCOpt + n, "" + _drawNC));
+                                       _flatExteriorLoop = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(flatExteriorLoopOpt + n, ""
+                                                                       + _flatExteriorLoop));
+                                       _drawTertiary = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(drawTertiaryOpt + n, ""
+                                                                       + _drawTertiary));
+                                       _warning = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(warningOpt + n, "false"));
+                                       _error = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(errorOpt + n, "true"));
+                                       _border = parseDimension(_optionProducer.getParameterValue(
+                                                       borderOpt + n, "0X0"));
+                                       _comparisonMode = Boolean.parseBoolean(_optionProducer
+                                                       .getParameterValue(comparisonModeOpt + n, "false"));
+                                       _firstSequence = _optionProducer.getParameterValue(
+                                                       firstSequenceForComparisonOpt + n, _firstSequence);
+                                       _firstStructure = _optionProducer
+                                                       .getParameterValue(firstStructureForComparisonOpt
+                                                                       + n, _firstStructure);
+                                       _secondSequence = _optionProducer
+                                                       .getParameterValue(secondSequenceForComparisonOpt
+                                                                       + n, _secondSequence);
+                                       _secondStructure = _optionProducer.getParameterValue(
+                                                       secondStructureForComparisonOpt + n,
+                                                       _secondStructure);
+                                       _annotations = _optionProducer.getParameterValue(
+                                                       annotationsOpt + n, _annotations);
+                                       _URL = _optionProducer.getParameterValue(URLOpt + n, _URL);
+                                       _algo = _optionProducer.getParameterValue(algoOpt + n,
+                                                       _algo);
+                                       _customBases = _optionProducer.getParameterValue(
+                                                       customBasesOpt + n, _customBases);
+                                       _auxBPs = _optionProducer.getParameterValue(auxBPsOpt + n,
+                                                       _auxBPs);
+                                       _highlightRegion = _optionProducer.getParameterValue(
+                                                       highlightRegionOpt + n, _highlightRegion);
+                                       _chemProbs = _optionProducer.getParameterValue(chemProbOpt
+                                                       + n, _chemProbs);
+                                       _customBPs = _optionProducer.getParameterValue(customBPsOpt
+                                                       + n, _customBPs);
+                                       _colorMapStyle = _optionProducer.getParameterValue(
+                                                       colorMapDefOpt + n, _colorMapStyle);
+                                       _colorMapCaption = _optionProducer.getParameterValue(
+                                                       colorMapCaptionOpt + n, _colorMapCaption);
+                                       _backboneColor = getSafeColor(
+                                                       _optionProducer.getParameterValue(backboneColorOpt
+                                                                       + n, _backboneColor.toString()),
+                                                       _backboneColor);
+                                       _backgroundColor = getSafeColor(
+                                                       _optionProducer.getParameterValue(
+                                                                       backgroundColorOpt + n,
+                                                                       _backgroundColor.toString()),
+                                                       _backgroundColor);
+                                       _bondColor = getSafeColor(
+                                                       _optionProducer.getParameterValue(bondColorOpt + n,
+                                                                       _bondColor.toString()), _bondColor);
+                                       _bpStyle = getSafeBPStyle(
+                                                       _optionProducer.getParameterValue(bpStyleOpt + n,
+                                                                       ""), _bpStyle);
+                                       _flip = _optionProducer.getParameterValue(
+                                                       flipOpt + n, _flip);
+                                       _orientation = _optionProducer.getParameterValue(
+                                                       orientationOpt + n, _orientation);
+                                       _titleColor = getSafeColor(
+                                                       _optionProducer.getParameterValue(
+                                                                       titleColorOpt + n, _titleColor.toString()),
+                                                       _titleColor);
+
+                                       
+                                       
+                                       if (!_URL.equals("")) {
+                                               _sstruct = "";
+                                               _sseq = "";
+                                               _title = "";
+                                       }
+                                       _title = _optionProducer.getParameterValue(titleOpt + n, _title);
+
+                                       if (_comparisonMode && _firstSequence != null
+                                                       && _firstStructure != null
+                                                       && _secondSequence != null
+                                                       && _secondStructure != null) {
+                                       } else {
+                                               _sseq = _optionProducer.getParameterValue(sequenceOpt
+                                                               + n, _sseq);
+                                               _sstruct = _optionProducer.getParameterValue(
+                                                               structureOpt + n, _sstruct);
+                                               if (!_sseq.equals("") && !_sstruct.equals("")) {
+                                                       _URL = "";
+                                               }
+                                               _comparisonMode = false;
+                                       }
+
+                                       // applique les valeurs des parametres recuperees
+                                       applyValues(n);
+                               } catch (ExceptionParameterError e) {
+                                       VARNAPanel.errorDialogStatic(e, _mainSurface);
+                               } catch (ExceptionNonEqualLength e) {
+                                       VARNAPanel.errorDialogStatic(e, _mainSurface);
+                               } catch (IOException e) {
+                                       VARNAPanel.errorDialogStatic(e, _mainSurface);
+                               } catch (ExceptionFileFormatOrSyntax e) {
+                                       VARNAPanel.errorDialogStatic(e, _mainSurface);
+                               } catch (ExceptionLoadingFailed e) {
+                                       VARNAPanel.errorDialogStatic(e, _mainSurface);
+                               }
+                       }// fin de boucle sur les lignes
+               }// fin de boucle sur les colonnes
+       }
+
+       private RNA _defaultRNA = new RNA();
+
+       public void setRNA(RNA r) {
+               _defaultRNA = r;
+       }
+
+       public static final String ALGORITHM_CIRCULAR = "circular";
+       public static final String ALGORITHM_NAVIEW = "naview";
+       public static final String ALGORITHM_LINE = "line";
+       public static final String ALGORITHM_RADIATE = "radiate";
+       public static final String ALGORITHM_VARNA_VIEW = "varnaview";
+       public static final String ALGORITHM_MOTIF_VIEW = "motifview";
+
+       private void applyValues(String n) throws ExceptionParameterError,
+                       ExceptionNonEqualLength, IOException, ExceptionFileFormatOrSyntax,
+                       ExceptionLoadingFailed {
+               boolean applyOptions = true;
+               int algoCode;
+               if (_algo.equals(ALGORITHM_CIRCULAR))
+                       algoCode = RNA.DRAW_MODE_CIRCULAR;
+               else if (_algo.equals(ALGORITHM_NAVIEW))
+                       algoCode = RNA.DRAW_MODE_NAVIEW;
+               else if (_algo.equals(ALGORITHM_LINE))
+                       algoCode = RNA.DRAW_MODE_LINEAR;
+               else if (_algo.equals(ALGORITHM_RADIATE))
+                       algoCode = RNA.DRAW_MODE_RADIATE;
+               else if (_algo.equals(ALGORITHM_VARNA_VIEW))
+                       algoCode = RNA.DRAW_MODE_VARNA_VIEW;
+               else if (_algo.equals(ALGORITHM_MOTIF_VIEW))
+                       algoCode = RNA.DRAW_MODE_MOTIFVIEW;
+               else
+                       algoCode = RNA.DRAW_MODE_RADIATE;
+
+               if (_comparisonMode) {
+                       _mainSurface = new VARNAPanel(_firstSequence, _firstStructure,
+                                       _secondSequence, _secondStructure, algoCode, "");
+               } else {
+                       _mainSurface = new VARNAPanel();
+               }
+
+               _VARNAPanelList.add(_mainSurface);
+               _mainSurface.setSpaceBetweenBases(_spaceBetweenBases);
+               _mainSurface.setTitle(_title);
+
+               if (!_URL.equals("")) {
+                       URL url = null;
+                       try {
+
+                               _mainSurface.setSpaceBetweenBases(_spaceBetweenBases);
+
+                               url = new URL(_URL);
+                               URLConnection connexion = url.openConnection();
+                               connexion.setUseCaches(false);
+                               InputStream r = connexion.getInputStream();
+                               InputStreamReader inr = new InputStreamReader(r);
+
+                               if (_URL.toLowerCase().endsWith(
+                                               VARNAPanel.VARNA_SESSION_EXTENSION)) {
+                                       FullBackup f;
+                                       f = VARNAPanel.importSession(r, _URL);
+                                       _mainSurface.setConfig(f.config);
+                                       _mainSurface.showRNA(f.rna);
+                                       applyOptions = false;
+                               } else {
+                                       Collection<RNA> rnas = RNAFactory.loadSecStr(
+                                                       new BufferedReader(inr),
+                                                       RNAFactory.guessFileTypeFromExtension(_URL));
+                                       if (rnas.isEmpty()) {
+                                               throw new ExceptionFileFormatOrSyntax(
+                                                               "No RNA in file '" + _URL + "'.");
+                                       }
+                                       RNA rna = rnas.iterator().next();
+                                       rna.drawRNA(algoCode, _mainSurface.getConfig());
+                                       _mainSurface.drawRNA(rna, algoCode);
+                               }
+                               if (!_title.isEmpty())
+                               {
+                                       _mainSurface.setTitle(_title);
+                               }
+                       } catch (ExceptionFileFormatOrSyntax e) {
+                               if (url != null)
+                                       e.setPath(url.getPath());
+                       } catch (ExceptionDrawingAlgorithm e) {
+                               _mainSurface.emitWarning(e.getMessage());
+                       }
+
+               } else {
+                       if (!_comparisonMode) {
+                               if (!_sstruct.equals("")) {
+                                       _mainSurface.drawRNA(_sseq, _sstruct, algoCode);
+                               } else {
+                                       try {
+                                               System.err.println("Printing default RNA "+_defaultRNA);
+                                               _defaultRNA.drawRNA(algoCode, _mainSurface.getConfig());
+                                       } catch (ExceptionDrawingAlgorithm e) {
+                                               e.printStackTrace();
+                                       }
+                                       _mainSurface.drawRNA(_defaultRNA);
+                               }
+                       }
+               }
+               if (applyOptions)
+               {
+                       if (_useInnerBaseColor) {
+                               _mainSurface.setBaseInnerColor(_baseInnerColor);
+                       }
+                       if (_useBaseOutlineColor) {
+                               _mainSurface.setBaseOutlineColor(_baseOutlineColor);
+                       }
+                       if (_useBaseNameColor) {
+                               _mainSurface.setBaseNameColor(_baseNameColor);
+                       }
+                       if (_useBaseNumbersColor) {
+                               _mainSurface.setBaseNumbersColor(_baseNumColor);
+                       }
+
+                       _mainSurface.setBackground(_backgroundColor);
+                       _mainSurface.setNumPeriod(_periodResNum);
+                       _mainSurface.setBackboneColor(_backboneColor);
+                       _mainSurface.setDefaultBPColor(_bondColor);
+                       _mainSurface.setBPHeightIncrement(_bpIncrement);
+                       _mainSurface.setBPStyle(_bpStyle);
+                       _mainSurface.setDrawBackbone(_drawBackbone);
+
+                       _mainSurface.setTitleFontColor(_titleColor);
+                       _mainSurface.setTitleFontSize(_titleSize);
+
+                       _mainSurface.getPopupMenu().get_itemShowWarnings()
+                                       .setState(_warning);
+                       _mainSurface.setErrorsOn(_error);
+                       _mainSurface.setFlatExteriorLoop(_flatExteriorLoop);
+                       _mainSurface.setZoom(_zoom);
+                       _mainSurface.setZoomIncrement(_zoomAmount);
+                       _mainSurface.setBorderSize(_border);
+
+                       if (_useGapsColor) {
+                               _mainSurface.setGapsBasesColor(this._gapsColor);
+                               _mainSurface.setColorGapsBases(true);
+                       }
+
+                       if (_useNonStandardColor) {
+                               _mainSurface.setNonStandardBasesColor(_nonStandardColor);
+                               _mainSurface.setColorNonStandardBases(true);
+                       }
+
+                       _mainSurface.setShowNonPlanarBP(_drawTertiary);
+                       _mainSurface.setShowNonCanonicalBP(_drawNC);
+
+                       applyBasesStyle(n);
+
+                       if (!_customBases.equals(""))
+                               applyBasesCustomStyles(_mainSurface);
+
+                       if (!_highlightRegion.equals(""))
+                               applyHighlightRegion(_mainSurface);
+
+                       if (!_auxBPs.equals(""))
+                               applyAuxBPs(_mainSurface);
+
+                       if (!_chemProbs.equals(""))
+                               applyChemProbs(_mainSurface);
+
+                       if (!_customBPs.equals(""))
+                               applyBPsCustomStyles(_mainSurface);
+
+                       _mainSurface.setDrawOutlineBases(_drawBases);
+                       _mainSurface.setFillBases(_fillBases);
+                       _mainSurface.drawRNA();
+
+                       if (!_annotations.equals(""))
+                               applyAnnotations(_mainSurface);
+                       if (_autoHelices)
+                               _mainSurface.getVARNAUI().UIAutoAnnotateHelices();
+                       if (_autoTerminalLoops)
+                               _mainSurface.getVARNAUI().UIAutoAnnotateTerminalLoops();
+                       if (_autoInteriorLoops)
+                               _mainSurface.getVARNAUI().UIAutoAnnotateInteriorLoops();
+
+                       if (!_orientation.equals("")) {
+                               try {
+                                       double d = 360 * _mainSurface.getOrientation()
+                                                       / (2. * Math.PI);
+                                       _rotation = Double.parseDouble(_orientation) - d;
+                               } catch (NumberFormatException e) {
+                                       // TODO : Add some code here...
+                               }
+
+                       }
+                       _mainSurface.globalRotation(_rotation);
+
+                       _mainSurface.setModifiable(_modifiable);
+
+                       _mainSurface.setColorMapCaption(_colorMapCaption);
+                       applyColorMapStyle(_mainSurface);
+                       applyFlips(_mainSurface);
+                       applyColorMapValues(_mainSurface);
+                       
+                       // if (!_drawColorMap)
+                       // _mainSurface.drawColorMap(_drawColorMap);
+               }
+               // ajoute le VARNAPanel au conteneur
+       }
+
+       private void applyBasesStyle(String n) throws ExceptionParameterError {
+               String tmp = null;
+               for (int numStyle = 0; numStyle < _basesStyleList.size(); numStyle++) {
+                       if (_basesStyleList.get(numStyle) != null) {
+                               tmp = _optionProducer.getParameterValue(applyBasesStyleOpt
+                                               + (numStyle) + "on" + n, null);
+
+                               ArrayList<Integer> indicesList = new ArrayList<Integer>();
+                               if (tmp != null) {
+                                       String[] basesList = tmp.split(",");
+                                       for (int k = 0; k < basesList.length; k++) {
+                                               String cand = basesList[k].trim();
+                                               try {
+                                                       String[] args = cand.split("-");
+                                                       if (args.length == 1) {
+                                                               int baseNum = Integer.parseInt(cand);
+                                                               int index = _mainSurface.getRNA()
+                                                                               .getIndexFromBaseNumber(baseNum);
+                                                               if (index != -1) {
+                                                                       indicesList.add(index);
+                                                               }
+                                                       } else if (args.length == 2) {
+                                                               int baseNumFrom = Integer.parseInt(args[0]
+                                                                               .trim());
+                                                               int indexFrom = _mainSurface.getRNA()
+                                                                               .getIndexFromBaseNumber(baseNumFrom);
+                                                               int baseNumTo = Integer
+                                                                               .parseInt(args[1].trim());
+                                                               int indexTo = _mainSurface.getRNA()
+                                                                               .getIndexFromBaseNumber(baseNumTo);
+                                                               if ((indexFrom != -1) && (indexTo != -1)) {
+                                                                       for (int l = indexFrom; l <= indexTo; l++)
+                                                                               indicesList.add(l);
+                                                               }
+                                                       }
+                                               } catch (NumberFormatException e) {
+                                                       throw new ExceptionParameterError(e.getMessage(),
+                                                                       "Bad Base Index: " + basesList[k]);
+                                               }
+                                       }
+                                       for (int k = 0; k < indicesList.size(); k++) {
+                                               int index = indicesList.get(k);
+                                               if ((index >= 0)
+                                                               && (index < _mainSurface.getRNA()
+                                                                               .get_listeBases().size())) {
+                                                       _mainSurface
+                                                                       .getRNA()
+                                                                       .get_listeBases()
+                                                                       .get(index)
+                                                                       .setStyleBase(_basesStyleList.get(numStyle));
+                                               }
+                                       }
+                               }
+                       }
+               }// fin de boucle sur les styles
+
+       }
+
+       private void applyColorMapStyle(VARNAPanel vp) {
+               if (_colorMapStyle.length() != 0) {
+                       vp.setColorMap(ModeleColorMap.parseColorMap(_colorMapStyle));
+               }
+       }
+
+       private void applyColorMapValues(VARNAPanel vp) {
+               if (!_colorMapValues.equals("")) {
+                       File f = new File(_colorMapValues);
+                       if(f.exists() && !f.isDirectory()) { 
+                               try {
+                                       vp.readValues(new FileReader(f));
+                                       vp.drawColorMap(true);
+                                       System.err.println("Loaded "+_colorMapValues);
+                               } catch (FileNotFoundException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+                       else
+                       {
+                               String[] values = _colorMapValues.split("[;,]");
+                               ArrayList<Double> vals = new ArrayList<Double>();
+                               for (int i = 0; i < values.length; i++) {
+                                       try {
+                                               vals.add(Double.parseDouble(values[i]));
+                                       } catch (Exception e) {
+       
+                                       }
+                               }
+                               Double[] result = new Double[vals.size()];
+                               vals.toArray(result);
+                               vp.setColorMapValues(result);
+                       }
+                       ModeleColorMap cm = vp.getColorMap();
+                       if (_colorMapMin != Double.MIN_VALUE) {
+                               // System.out.println("[A]"+_colorMapMin);
+                               cm.setMinValue(_colorMapMin);
+                       }
+                       if (_colorMapMax != Double.MAX_VALUE) {
+                               cm.setMaxValue(_colorMapMax);
+                       }
+                       _drawColorMap = true;
+               }
+       }
+
+       private void applyBasesCustomStyles(VARNAPanel vp) {
+               String[] baseStyles = _customBases.split(";");
+               for (int i = 0; i < baseStyles.length; i++) {
+                       String thisStyle = baseStyles[i];
+                       String[] data = thisStyle.split(":");
+                       try {
+                               if (data.length == 2) {
+                                       int baseNum = Integer.parseInt(data[0]);
+                                       int index = _mainSurface.getRNA().getIndexFromBaseNumber(
+                                                       baseNum);
+                                       if (index != -1) {
+                                               String style = data[1];
+                                               ModelBaseStyle msb = vp.getRNA().get_listeBases()
+                                                               .get(index).getStyleBase().clone();
+                                               msb.assignParameters(style);
+                                               vp.getRNA().get_listeBases().get(index)
+                                                               .setStyleBase(msb);
+                                       }
+                               }
+                       } catch (Exception e) {
+                               System.err.println("ApplyBasesCustomStyle: " + e.toString());
+                       }
+               }
+       }
+
+       private void applyHighlightRegion(VARNAPanel vp) {
+               String[] regions = _highlightRegion.split(";");
+               for (int i = 0; i < regions.length; i++) {
+                       String region = regions[i];
+                       try {
+                               HighlightRegionAnnotation nt = HighlightRegionAnnotation
+                                               .parseHighlightRegionAnnotation(region, vp);
+                               if (nt != null) {
+                                       vp.addHighlightRegion(nt);
+                               }
+                       } catch (Exception e) {
+                               System.err.println("Error in applyHighlightRegion: " + e.toString());
+                       }
+               }
+       }
+
+       private Dimension parseDimension(String s) {
+               Dimension d = new Dimension(0, 0);
+               try {
+                       s = s.toLowerCase();
+                       int i = s.indexOf('x');
+                       String w = s.substring(0, i);
+                       String h = s.substring(i + 1);
+                       d.width = Integer.parseInt(w);
+                       d.height = Integer.parseInt(h);
+               } catch (NumberFormatException e) {
+               }
+               return d;
+       }
+
+       private void applyBPsCustomStyles(VARNAPanel vp) {
+               String[] baseStyles = _customBPs.split(";");
+               for (int i = 0; i < baseStyles.length; i++) {
+                       String thisStyle = baseStyles[i];
+                       String[] data = thisStyle.split(":");
+                       try {
+                               if (data.length == 2) {
+                                       String indices = data[0];
+                                       String style = data[1];
+                                       String[] data2 = indices.split(",");
+                                       if (data2.length == 2) {
+                                               String s1 = data2[0];
+                                               String s2 = data2[1];
+                                               if (s1.startsWith("(") && s2.endsWith(")")) {
+                                                       int a = Integer.parseInt(s1.substring(1));
+                                                       int b = Integer.parseInt(s2.substring(0,
+                                                                       s2.length() - 1));
+                                                       ModeleBP msbp = vp.getRNA().getBPStyle(a, b);
+                                                       if (msbp != null) {
+                                                               msbp.assignParameters(style);
+                                                       }
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               System.err.println("ApplyBPsCustomStyle: " + e.toString());
+                       }
+               }
+       }
+
+       private void applyChemProbs(VARNAPanel vp) {
+               String[] chemProbs = _chemProbs.split(";");
+               for (int i = 0; i < chemProbs.length; i++) {
+                       String thisAnn = chemProbs[i];
+                       String[] data = thisAnn.split(":");
+                       try {
+                               if (data.length == 2) {
+                                       String indices = data[0];
+                                       String style = data[1];
+                                       String[] data2 = indices.split("-");
+                                       if (data2.length == 2) {
+                                               int a = Integer.parseInt(data2[0]);
+                                               int b = Integer.parseInt(data2[1]);
+                                               int c = vp.getRNA().getIndexFromBaseNumber(a);
+                                               int d = vp.getRNA().getIndexFromBaseNumber(b);
+                                               ArrayList<ModeleBase> mbl = vp.getRNA()
+                                                               .get_listeBases();
+                                               ChemProbAnnotation cpa = new ChemProbAnnotation(
+                                                               mbl.get(c), mbl.get(d), style);
+                                               vp.getRNA().addChemProbAnnotation(cpa);
+                                       }
+                               }
+                       } catch (Exception e) {
+                               System.err.println("ChempProbs: " + e.toString());
+                       }
+               }
+       }
+
+       private void applyAuxBPs(VARNAPanel vp) {
+               String[] baseStyles = _auxBPs.split(";");
+
+               for (int i = 0; i < baseStyles.length; i++) {
+                       String thisStyle = baseStyles[i];
+                       String[] data = thisStyle.split(":");
+                       try {
+                               if (data.length >= 1) {
+                                       String indices = data[0];
+                                       String[] data2 = indices.split(",");
+                                       if (data2.length == 2) {
+                                               String s1 = data2[0];
+                                               String s2 = data2[1];
+                                               if (s1.startsWith("(") && s2.endsWith(")")) {
+                                                       int a = Integer.parseInt(s1.substring(1));
+                                                       int b = Integer.parseInt(s2.substring(0,
+                                                                       s2.length() - 1));
+                                                       int c = vp.getRNA().getIndexFromBaseNumber(a);
+                                                       int d = vp.getRNA().getIndexFromBaseNumber(b);
+
+                                                       ModeleBP msbp = new ModeleBP(vp.getRNA()
+                                                                       .get_listeBases().get(c), vp.getRNA()
+                                                                       .get_listeBases().get(d));
+                                                       if (data.length >= 2) {
+                                                               String style = data[1];
+                                                               msbp.assignParameters(style);
+                                                       }
+                                                       vp.getRNA()
+                                                                       .addBPToStructureUsingNumbers(a, b, msbp);
+                                               }
+                                       }
+                               }
+                       } catch (ExceptionModeleStyleBaseSyntaxError e1) {
+                               System.err.println("AuxApplyBPs: " + e1.toString());
+                       } catch (ExceptionParameterError e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       private void applyFlips(VARNAPanel vp) {
+               String[] flips = _flip.split(";");
+               for (String s: flips)
+               {
+                       if (!s.isEmpty())
+                       {
+                               try{
+                                       String[] data = s.split("-");
+                                       int number = -1;
+                                       if (data.length==1)
+                                       {
+                                               number = Integer.parseInt(data[0]);                                     
+                                       }
+                                       else if (data.length==2)
+                                       {
+                                               number = Integer.parseInt(data[1]);
+                                       }
+                                       if (number!=-1)
+                                       {
+                                               int i = vp.getRNA().getIndexFromBaseNumber(number);
+                                               Point h = vp.getRNA().getExteriorHelix(i);
+                                               vp.getRNA().flipHelix(h);
+                                       }
+                               } catch (Exception e) {
+                                       System.err.println("Flip Helices: " + e.toString());
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Format:
+        * string:[type=[H|B|L|P]|x=double|y=double|anchor=int|size=int|color
+        * =Color];
+        * 
+        * @param vp
+        */
+       private void applyAnnotations(VARNAPanel vp) {
+               String[] annotations = _annotations.split(";");
+               for (int i = 0; i < annotations.length; i++) {
+                       String thisAnn = annotations[i];
+                       TextAnnotation ann = TextAnnotation.parse(thisAnn, vp);
+                       vp.addAnnotation(ann);
+               }
+       }
+
+
+}
+
+
diff --git a/srcjar/fr/orsay/lri/varna/models/VARNAEdits.java b/srcjar/fr/orsay/lri/varna/models/VARNAEdits.java
new file mode 100644 (file)
index 0000000..bf6823a
--- /dev/null
@@ -0,0 +1,403 @@
+package fr.orsay.lri.varna.models;
+
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.UndoableEdit;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement;
+import fr.orsay.lri.varna.applications.templateEditor.TemplatePanel;
+import fr.orsay.lri.varna.applications.templateEditor.TemplateEdits.ElementEdgeMoveTemplateEdit;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VARNAEdits {
+       public static final double MAX_DISTANCE= 55.0;
+       public static  class BasesShiftEdit extends AbstractUndoableEdit
+       {
+               private ArrayList<Integer> _indices; 
+               private double _dx; 
+               private double _dy;
+               private VARNAPanel _vp;
+               public BasesShiftEdit(ArrayList<Integer> indices, double dx, double dy, VARNAPanel p)
+               {
+                       _indices = indices;
+                       _dx = dx;
+                       _dy = dy;
+                       _vp = p;
+               }
+               public void undo() throws CannotUndoException {
+                       for (int index: _indices)
+                       {
+                               ModeleBase mb = _vp.getRNA().getBaseAt(index);
+                           _vp.getRNA().setCoord(index,new Point2D.Double(mb.getCoords().x-_dx,mb.getCoords().y-_dy));
+                           _vp.getRNA().setCenter(index,new Point2D.Double(mb.getCenter().x-_dx,mb.getCenter().y-_dy));
+                       }
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       for (int index: _indices)
+                       {
+                               ModeleBase mb = _vp.getRNA().getBaseAt(index);
+                           _vp.getRNA().setCoord(index,new Point2D.Double(mb.getCoords().x+_dx,mb.getCoords().y+_dy));
+                           _vp.getRNA().setCenter(index,new Point2D.Double(mb.getCenter().x-_dx,mb.getCenter().y-_dy));
+                       }
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Base #"+_indices+" shifted"; }
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof BasesShiftEdit)
+                       {
+                               BasesShiftEdit e = (BasesShiftEdit) anEdit;
+                               if (e._indices.equals(_indices))
+                               {
+                                       Point2D.Double tot = new Point2D.Double(_dx+e._dx,_dy+e._dy);
+                                       if (tot.distance(0.0, 0.0)<MAX_DISTANCE)
+                                       {
+                                               _dx = _dx+e._dx;
+                                               _dy = _dy+e._dy;
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               }
+       };
+
+       public static  class HelixRotateEdit extends AbstractUndoableEdit
+       {
+               private double _delta; 
+               private double _base; 
+               private double _pLimL; 
+               private double _pLimR; 
+               private Point _h; 
+               private Point _ml; 
+               private VARNAPanel _vp;
+               public HelixRotateEdit(double delta, double base, double pLimL, double pLimR, Point h, Point ml, VARNAPanel vp)
+               {
+                       _delta = delta;
+                       _base = base;
+                       _pLimL = pLimL;
+                       _pLimR = pLimR;
+                       _h = h;
+                       _ml = ml;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getVARNAUI().UIRotateEverything(-_delta, _base, _pLimL, _pLimR, _h, _ml);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getVARNAUI().UIRotateEverything(_delta, _base, _pLimL, _pLimR, _h, _ml);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Helix #"+_h+" rotated angle:"+_delta; }
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof HelixRotateEdit)
+                       {
+                               HelixRotateEdit e = (HelixRotateEdit) anEdit;
+                               if (e._h.equals(_h))
+                               {
+                                       double totAngle = e._delta+_delta;
+                                       while (totAngle>Math.PI)
+                                       { totAngle -= 2.0*Math.PI; }
+                                       if (Math.abs(totAngle)<Math.PI/8.0)
+                                       {
+                                               _delta=totAngle;
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               }
+       };
+       
+       
+       public static  class SingleBaseMoveEdit extends AbstractUndoableEdit
+       {
+               private int _index; 
+               private double _ox; 
+               private double _oy;
+               private double _nx; 
+               private double _ny;
+               private VARNAPanel _vp;
+               public SingleBaseMoveEdit(int index, double nx, double ny, VARNAPanel p)
+               {
+                       _index = index;
+                       ModeleBase mb = p.getRNA().getBaseAt(index);
+                       _ox = mb.getCoords().x;
+                       _oy = mb.getCoords().y;
+                       _nx = nx;
+                       _ny = ny;
+                       _vp = p;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getRNA().setCoord(_index,new Point2D.Double(_ox,_oy));
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getRNA().setCoord(_index,new Point2D.Double(_nx,_ny));
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Base #"+_index+" moved"; }
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof SingleBaseMoveEdit)
+                       {
+                               SingleBaseMoveEdit e = (SingleBaseMoveEdit) anEdit;
+                               if (e._index==_index)
+                               {
+                                       Point2D.Double po1 = new Point2D.Double(_ox,_oy);
+                                       Point2D.Double pn1 = new Point2D.Double(_nx,_ny);
+                                       Point2D.Double po2 = new Point2D.Double(e._ox,e._oy);
+                                       Point2D.Double pn2 = new Point2D.Double(e._nx,e._ny);
+                                       if ((pn1.equals(po2))&&(po1.distance(pn2)<MAX_DISTANCE))
+                                       {
+                                               _nx = e._nx;
+                                               _ny = e._ny;
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               }
+       };
+
+       public static  class HelixFlipEdit extends AbstractUndoableEdit
+       {
+               private Point _h; 
+               private VARNAPanel _vp;
+               public HelixFlipEdit(Point h, VARNAPanel vp)
+               {
+                       _h = h;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getVARNAUI().UIFlipHelix(_h);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getVARNAUI().UIFlipHelix(_h);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Helix #"+_h+" flipped";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       return false;
+               }
+       };
+
+       public static  class AddBPEdit extends AbstractUndoableEdit
+       {
+               private ModeleBP _msbp; 
+               private int _i;
+               private int _j;
+               private VARNAPanel _vp;
+               public AddBPEdit(int i, int j, ModeleBP msbp, VARNAPanel vp)
+               {
+                       _msbp = msbp;
+                       _i = i;
+                       _j = j;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getRNA().removeBP(_msbp);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getRNA().addBP(_i,_j,_msbp);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Add BP ("+_i+","+_j+")";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       return false;
+               }
+       };
+
+       public static  class RemoveBPEdit extends AbstractUndoableEdit
+       {
+               private ModeleBP _msbp; 
+               private int _i;
+               private int _j;
+               private VARNAPanel _vp;
+               public RemoveBPEdit( int i, int j,ModeleBP msbp, VARNAPanel vp)
+               {
+                       _msbp = msbp;
+                       _i = i;
+                       _j = j;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getRNA().addBP(_i,_j,_msbp);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getRNA().removeBP(_msbp);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Remove BP ("+_i+","+_j+")";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       return false;
+               }
+       };
+
+       public static  class RescaleRNAEdit extends AbstractUndoableEdit
+       {
+               private double _factor;
+               private VARNAPanel _vp;
+               public RescaleRNAEdit( double angle, VARNAPanel vp)
+               {
+                       _factor = angle;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getRNA().rescale(1.0/_factor);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getRNA().rescale(_factor);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Rescale RNA factor:"+_factor+"";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof RescaleRNAEdit)
+                       {
+                               RescaleRNAEdit e = (RescaleRNAEdit) anEdit;
+                               double cumFact = _factor*e._factor;
+                               if (cumFact>.7 || cumFact<1.3)
+                               {
+                                       _factor *= e._factor;
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+       };
+
+       public static  class RotateRNAEdit extends AbstractUndoableEdit
+       {
+               private double _angle;
+               private VARNAPanel _vp;
+               public RotateRNAEdit( double angle, VARNAPanel vp)
+               {
+                       _angle = angle;
+                       _vp = vp;
+               }
+               public void undo() throws CannotUndoException {
+                       _vp.getRNA().globalRotation(-_angle);
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       _vp.getRNA().globalRotation(_angle);
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Rotate RNA angle:"+_angle+"";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       if (anEdit instanceof RotateRNAEdit)
+                       {
+                               RotateRNAEdit e = (RotateRNAEdit) anEdit;
+                               if (Math.abs(_angle+e._angle)<30)
+                               {
+                                       _angle += e._angle;
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+       };
+
+       public static  class RedrawEdit extends AbstractUndoableEdit
+       {
+               private int _prevMode;
+               private int _newMode;
+               private boolean _prevFlat;
+               private boolean _newFlat;
+               private ArrayList<Point2D.Double> _backupCoords = new ArrayList<Point2D.Double>();
+               private ArrayList<Point2D.Double> _backupCenters = new ArrayList<Point2D.Double>();
+               private VARNAPanel _vp;
+               
+
+               public RedrawEdit(VARNAPanel vp,boolean newFlat)
+               {
+                       this(vp.getRNA().getDrawMode(),vp,newFlat);
+               }
+
+               public RedrawEdit(int newMode, VARNAPanel vp)
+               {
+                       this(newMode,vp,vp.getFlatExteriorLoop());
+               }
+       
+               public RedrawEdit(int newMode, VARNAPanel vp, boolean newFlat)
+               {
+                       _vp = vp;
+                       _newMode = newMode;
+                       _newFlat = newFlat;
+                       _prevFlat = _vp.getFlatExteriorLoop();
+                       for (ModeleBase mb: _vp.getRNA().get_listeBases())
+                       {
+                               _backupCoords.add(new Point2D.Double(mb.getCoords().x,mb.getCoords().y));
+                               _backupCenters.add(new Point2D.Double(mb.getCenter().x,mb.getCenter().y));
+                       }
+                       _prevMode = _vp.getDrawMode();
+               }
+               public void undo() throws CannotUndoException {
+                       RNA r = _vp.getRNA();
+                       _vp.setFlatExteriorLoop(_prevFlat);
+                       r.setDrawMode(_prevMode);
+                       for (int index =0;index<_vp.getRNA().get_listeBases().size();index++)
+                       {
+                           Point2D.Double oldCoord = _backupCoords.get(index);
+                           Point2D.Double oldCenter = _backupCenters.get(index);
+                           r.setCoord(index, oldCoord);
+                           r.setCenter(index, oldCenter);
+                       }
+                       _vp.repaint();                  
+               }
+               public void redo() throws CannotRedoException {
+                       try {
+                               _vp.setFlatExteriorLoop(_newFlat);
+                               _vp.getRNA().drawRNA(_newMode,_vp.getConfig());
+                       } catch (ExceptionNAViewAlgorithm e) {
+                               e.printStackTrace();
+                       }
+                       _vp.repaint();
+               }
+               public boolean canUndo() { return true; }
+               public boolean canRedo() { return true; }
+               public String getPresentationName() { return "Redraw whole RNA";}
+               public boolean addEdit(UndoableEdit anEdit)
+               {
+                       return false;
+               }
+       };
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/annotations/ChemProbAnnotation.java b/srcjar/fr/orsay/lri/varna/models/annotations/ChemProbAnnotation.java
new file mode 100644 (file)
index 0000000..83c2158
--- /dev/null
@@ -0,0 +1,260 @@
+package fr.orsay.lri.varna.models.annotations;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+
+public class ChemProbAnnotation implements Serializable {
+
+       public static final String HEADER_TEXT = "ChemProbAnnotation";
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 5833315460145031242L;
+
+
+       public enum ChemProbAnnotationType 
+       {
+               TRIANGLE,
+               ARROW,
+               PIN,
+               DOT;
+       };
+       
+       public static double DEFAULT_INTENSITY = 1.0;
+       public static ChemProbAnnotationType DEFAULT_TYPE = ChemProbAnnotationType.ARROW;
+       public static Color DEFAULT_COLOR = Color.blue.darker();
+
+       private ModeleBase _mbfst;
+       private ModeleBase _mbsnd;
+       private Color _color;
+       private double _intensity;
+       private ChemProbAnnotationType _type;
+       private boolean _outward;
+       
+       public static String XML_ELEMENT_NAME = "ChemProbAnnotation";
+       public static String XML_VAR_INDEX5_NAME = "Index5";
+       public static String XML_VAR_INDEX3_NAME = "Index3";
+       public static String XML_VAR_COLOR_NAME = "Color";
+       public static String XML_VAR_INTENSITY_NAME = "Intensity";
+       public static String XML_VAR_TYPE_NAME = "Type";
+       public static String XML_VAR_OUTWARD_NAME = "Outward";
+
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_INDEX5_NAME,"CDATA",""+_mbfst.getIndex());
+               atts.addAttribute("","",XML_VAR_INDEX3_NAME,"CDATA",""+_mbsnd.getIndex());
+               atts.addAttribute("","",XML_VAR_COLOR_NAME,"CDATA",XMLUtils.toHTMLNotation(_color));
+               atts.addAttribute("","",XML_VAR_INTENSITY_NAME,"CDATA",""+_intensity);
+               atts.addAttribute("","",XML_VAR_TYPE_NAME,"CDATA",""+_type);
+               atts.addAttribute("","",XML_VAR_OUTWARD_NAME,"CDATA",""+_outward);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+       
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd, String styleDesc) {
+               this(mbfst,mbsnd);
+               applyStyle(styleDesc);
+       }
+
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd) {
+               this(mbfst,mbsnd,ChemProbAnnotation.DEFAULT_TYPE,ChemProbAnnotation.DEFAULT_INTENSITY);
+       }
+
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd, double intensity) {
+               this(mbfst,mbsnd,ChemProbAnnotation.DEFAULT_TYPE,intensity);
+       }
+
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd, ChemProbAnnotationType type) {
+               this(mbfst,mbsnd,type,ChemProbAnnotation.DEFAULT_INTENSITY);
+       }
+       
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd, ChemProbAnnotationType type, double intensity) {
+               this(mbfst,mbsnd, type, intensity, DEFAULT_COLOR, true);
+       }
+
+       public ChemProbAnnotation(ModeleBase mbfst, ModeleBase mbsnd, ChemProbAnnotationType type, double intensity, Color color, boolean out) {
+               if (mbfst.getIndex()>mbsnd.getIndex())
+               {
+                       ModeleBase tmp = mbsnd;
+                       mbsnd = mbfst;
+                       mbfst = tmp;
+               }
+               _mbfst = mbfst;
+               _mbsnd = mbsnd;
+               _type = type;
+               _intensity = intensity;
+               _color = color;
+               _outward = out;
+       }
+
+       public boolean isOut()
+       {
+               return _outward; 
+       }
+       
+       public void setOut(boolean b)
+       {
+               _outward = b;
+       }
+
+       public Color getColor()
+       {
+               return _color; 
+       }
+
+       public double getIntensity()
+       {
+               return _intensity; 
+       }
+       
+       public ChemProbAnnotationType getType()
+       {
+               return _type; 
+       }
+       
+       public void setColor(Color c){
+               _color = c;
+       }
+
+       public void setIntensity(double d){
+               _intensity = d;
+       }
+       
+       public Point2D.Double getAnchorPosition()
+       {
+               Point2D.Double result = new Point2D.Double(
+                               (_mbfst.getCoords().x+_mbsnd.getCoords().x)/2.0,
+                               (_mbfst.getCoords().y+_mbsnd.getCoords().y)/2.0);
+               return result;
+       }
+       
+       public Point2D.Double getDirVector()
+       {
+               Point2D.Double norm = getNormalVector();
+               Point2D.Double result = new Point2D.Double(-norm.y,norm.x);
+               Point2D.Double anchor = getAnchorPosition();
+               Point2D.Double center = new Point2D.Double(
+                               (_mbfst.getCenter().x+_mbsnd.getCenter().x)/2.0,
+                               (_mbfst.getCenter().y+_mbsnd.getCenter().y)/2.0);
+               Point2D.Double vradius = new Point2D.Double(
+                               (center.x-anchor.x)/2.0,
+                               (center.y-anchor.y)/2.0);
+               if (_outward)
+               {
+                 if (result.x*vradius.x+result.y*vradius.y>0)
+                 {
+                       return new Point2D.Double(-result.x,-result.y);
+                 }
+               }
+               else
+               {
+                         if (result.x*vradius.x+result.y*vradius.y<0)
+                         {
+                               return new Point2D.Double(-result.x,-result.y);
+                         }                     
+               }
+               return result;          
+       }
+       public Point2D.Double getNormalVector()
+       {
+               Point2D.Double tmp;
+               if (_mbfst==_mbsnd)
+               {
+                       tmp = new Point2D.Double(
+                                       (-(_mbsnd.getCenter().y-_mbsnd.getCoords().y)),
+                                       ((_mbsnd.getCenter().x-_mbsnd.getCoords().x)));                 
+               }
+               else
+               {
+                       tmp = new Point2D.Double(
+                               (_mbsnd.getCoords().x-_mbfst.getCoords().x)/2.0,
+                               (_mbsnd.getCoords().y-_mbfst.getCoords().y)/2.0);
+               }
+               
+               double norm = tmp.distance(0, 0);
+               Point2D.Double result = new Point2D.Double(tmp.x/norm,tmp.y/norm);
+               return result;                          
+       }
+       
+       public static ChemProbAnnotationType annotTypeFromString(String value)
+       {
+               if (value.toLowerCase().equals("arrow"))
+               {return ChemProbAnnotationType.ARROW;}
+               else if (value.toLowerCase().equals("triangle"))
+               {return ChemProbAnnotationType.TRIANGLE;}
+               else if (value.toLowerCase().equals("pin"))
+               {return ChemProbAnnotationType.PIN;}
+               else if (value.toLowerCase().equals("dot"))
+               {return ChemProbAnnotationType.DOT;}
+               else
+               {return ChemProbAnnotationType.ARROW;}
+       }
+       
+       
+       public void applyStyle(String styleDesc)
+       {
+               String[] chemProbs = styleDesc.split(",");
+               for (int i = 0; i < chemProbs.length; i++) {
+                       String thisStyle = chemProbs[i];
+                       String[] data = thisStyle.split("=");
+                       if (data.length==2)
+                       {
+                               String name = data[0];
+                               String value = data[1];
+                               if (name.toLowerCase().equals("color"))
+                               {
+                                       Color c = Color.decode(value);
+                                       if (c==null)
+                                       { c = _color; }
+                                       setColor(c);
+                               }
+                               else if (name.toLowerCase().equals("intensity"))
+                               {
+                                       _intensity = Double.parseDouble(value);
+                               }
+                               else if (name.toLowerCase().equals("dir"))
+                               {
+                                       _outward = value.toLowerCase().equals("out");
+                               }
+                               else if (name.toLowerCase().equals("glyph"))
+                               {
+                                       _type= annotTypeFromString(value);
+                               }
+                       }
+               }
+       }
+       
+       public void setType(ChemProbAnnotationType s)
+       {
+               _type = s;
+       }
+
+       public ChemProbAnnotation clone()
+       {
+               ChemProbAnnotation result = new ChemProbAnnotation(this._mbfst,this._mbsnd);
+               result._intensity = _intensity;
+               result._type = _type;
+               result._color= _color;
+               result._outward = _outward;
+               return result;
+       }
+       
+       public String toString()
+       {
+               return "Chem. prob. "+this._type+" Base#"+this._mbfst.getBaseNumber()+"-"+this._mbsnd.getBaseNumber();
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/annotations/HighlightRegionAnnotation.java b/srcjar/fr/orsay/lri/varna/models/annotations/HighlightRegionAnnotation.java
new file mode 100644 (file)
index 0000000..1e671bd
--- /dev/null
@@ -0,0 +1,376 @@
+package fr.orsay.lri.varna.models.annotations;
+
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurClicMovement;
+import fr.orsay.lri.varna.models.VARNAConfigLoader;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.rna.VARNAPoint;
+import fr.orsay.lri.varna.utils.XMLUtils;
+import fr.orsay.lri.varna.views.VueHighlightRegionEdit;
+import fr.orsay.lri.varna.views.VueUI;
+
+public class HighlightRegionAnnotation implements Serializable {
+       
+       public static final String HEADER_TEXT = "HighlightRegionAnnotation";
+
+       private static final long serialVersionUID = 7087014168028684775L;
+       public static final Color DEFAULT_OUTLINE_COLOR = Color.decode("#6ed86e");
+       public static final Color DEFAULT_FILL_COLOR = Color.decode("#bcffdd");
+       public static final double DEFAULT_RADIUS = 16.0;
+       
+       private Color _outlineColor = DEFAULT_OUTLINE_COLOR;
+       private Color _fillColor = DEFAULT_FILL_COLOR;
+       private double _radius = DEFAULT_RADIUS;
+       private ArrayList<ModeleBase> _bases;
+       
+       public static String XML_ELEMENT_NAME = "region";
+       public static String XML_VAR_OUTLINE_NAME = "outline";
+       public static String XML_VAR_FILL_NAME = "fill";
+       public static String XML_VAR_RADIUS_NAME = "radius";
+
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_OUTLINE_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_outlineColor));
+               atts.addAttribute("","",XML_VAR_FILL_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_fillColor));
+               atts.addAttribute("","",XML_VAR_RADIUS_NAME,"CDATA",""+_radius);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               XMLUtils.toXML(hd, _bases);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+  public HighlightRegionAnnotation(RNA r, int startIndex, int stopIndex)
+  {
+         this(r.getBasesBetween(startIndex, stopIndex));
+  }
+
+  public HighlightRegionAnnotation()
+  {
+         this(new ArrayList<ModeleBase>());
+  }
+
+  public HighlightRegionAnnotation(ArrayList<ModeleBase> b)
+  {
+         this(b,DEFAULT_FILL_COLOR,DEFAULT_OUTLINE_COLOR,DEFAULT_RADIUS);
+  }
+  
+  public HighlightRegionAnnotation(ArrayList<ModeleBase> b,Color fill, Color outline, double radius)
+  {
+         _bases = b;
+         _fillColor = fill;
+         _outlineColor = outline;
+         _radius = radius;
+  }
+  
+       public HighlightRegionAnnotation clone()
+       {
+               return new HighlightRegionAnnotation(_bases,_fillColor,_outlineColor,_radius);
+       }
+       
+  public int getMinIndex()
+  {
+         int min = Integer.MAX_VALUE;
+         for (ModeleBase mb : _bases)
+         {
+                 min = Math.min(min, mb.getIndex());
+         }
+         return min;
+  }
+
+  public int getMaxIndex()
+  {
+         int max = Integer.MIN_VALUE;
+         for (ModeleBase mb : _bases)
+         {
+                 max = Math.max(max, mb.getIndex());
+         }
+         return max;
+  }
+
+  
+  public void setOutlineColor(Color c)
+  {
+         _outlineColor = c;  
+  }
+
+  public ArrayList<ModeleBase> getBases()
+  {
+         return _bases;
+  }
+
+  public void setBases(ArrayList<ModeleBase> b)
+  {
+         _bases = b;
+  }
+  
+  public void setFillColor(Color c)
+  {
+         _fillColor = c;  
+  }
+
+  public Color getFillColor()
+  {
+         return _fillColor;  
+  }
+  public Color getOutlineColor()
+  {
+         return _outlineColor;  
+  }
+
+  public double getRadius()
+  {
+         return _radius;  
+  }
+
+  public void setRadius(double v)
+  {
+         _radius = v;  
+  }
+  
+  public static final int NUM_STEPS_ROUNDED_CORNERS = 16;
+  
+  private Point2D.Double symImage(Point2D.Double p, Point2D.Double center)
+  {
+         return new Point2D.Double(2.*center.x-p.x, 2.*center.y-p.y);
+  }
+  
+  private LinkedList<Point2D.Double> buildRoundedCorner(Point2D.Double p1, Point2D.Double p2, Point2D.Double anotherPoint)
+  {
+               LinkedList<Point2D.Double> result = new LinkedList<Point2D.Double>();
+               Point2D.Double m = new Point2D.Double((p1.x+p2.x)/2.0,(p1.y+p2.y)/2.0);
+               double rad = p1.distance(p2)/2.;
+               double angle = Math.atan2(p1.y-m.y, p1.x-m.x);
+               
+               double incr = Math.PI/((double)NUM_STEPS_ROUNDED_CORNERS+1);
+               
+               Point2D.Double pdir = new Point2D.Double(m.x+rad*Math.cos(angle+Math.PI/2.),m.y+rad*Math.sin(angle+Math.PI/2.));
+               if (pdir.distance(anotherPoint)<p1.distance(anotherPoint))
+               { incr = -incr; }
+               
+               for(int k=1;k<=NUM_STEPS_ROUNDED_CORNERS;k++)
+               {
+                       double angle2 = angle+k*incr;
+                       Point2D.Double interForward = new Point2D.Double(m.x+rad*Math.cos(angle2),m.y+rad*Math.sin(angle2));
+                       result.addLast(interForward);
+               }
+               return result;
+  }
+  
+  
+       public GeneralPath getShape(Point2D.Double[] realCoords,Point2D.Double[] realCenters, double scaleFactor)
+       {
+               GeneralPath p = new GeneralPath();
+               LinkedList<Point2D.Double> pointList = new LinkedList<Point2D.Double>();
+               for (int i = 0;i<getBases().size();i++)
+               { 
+                       int j1 = getBases().get(i).getIndex();
+                       {
+                               int j0 = j1-1;
+                               int j2 = j1+1;
+                               Point2D.Double p0 = new Point2D.Double(0., 0.);
+                               Point2D.Double p1 = new Point2D.Double(0., 0.);                 
+                               Point2D.Double p2 = new Point2D.Double(0., 0.);
+                               if (i==0)
+                               {
+                                       // Single point
+                                       if (i==getBases().size()-1)
+                                       {
+                                               p1 = realCoords[j1];                    
+                                               p0 = new Point2D.Double(p1.x+scaleFactor *getRadius(), p1.y);
+                                               p2 = new Point2D.Double(p1.x-scaleFactor *getRadius(), p1.y);
+                                       }
+                                       else
+                                       {
+                                               p1 = realCoords[j1];                    
+                                               p2 = realCoords[j2];
+                                               p0 = symImage(p2, p1);
+                                       }
+                               }
+                               else if (i==getBases().size()-1)
+                               {
+                                       p0 = realCoords[j0];
+                                       p1 = realCoords[j1];                            
+                                       p2 = symImage(p0, p1);;                                 
+                               }
+                               else
+                               {
+                                       p0 = realCoords[j0];
+                                       p1 = realCoords[j1];                    
+                                       p2 = realCoords[j2];
+                               }
+
+                               double dist1 = p2.distance(p1);
+                               Point2D.Double v1 = new Point2D.Double((p2.x-p1.x)/dist1,(p2.y-p1.y)/dist1);
+                               Point2D.Double vn1 = new Point2D.Double(v1.y,-v1.x);
+                               double dist2 = p1.distance(p0);
+                               Point2D.Double v2 = new Point2D.Double((p1.x-p0.x)/dist2,(p1.y-p0.y)/dist2);
+                               Point2D.Double vn2 = new Point2D.Double(v2.y,-v2.x);
+                               double h = (new Point2D.Double(vn2.x-vn1.x,vn2.y-vn1.y).distance(new Point2D.Double(0,0))/2.0);
+                               Point2D.Double vn = new Point2D.Double((vn1.x+vn2.x)/2.0,(vn1.y+vn2.y)/2.0);
+                               double D = vn.distance(new Point2D.Double(0.0,0.0));
+                               vn.x/= D;
+                               vn.y/= D;
+                               double nnorm = (D+h*h/D);
+                               
+                               double nnormF = nnorm;
+                               double nnormB = nnorm;
+                               
+                               
+                               Point2D.Double interForward = new Point2D.Double(p1.x + nnormF*scaleFactor *getRadius()*vn.x, 
+                                               p1.y + nnormF*scaleFactor *getRadius()*vn.y);
+                               Point2D.Double interBackward = new Point2D.Double(p1.x - nnormB*scaleFactor *getRadius()*vn.x, 
+                                               p1.y - nnormB*scaleFactor *getRadius()*vn.y);
+
+
+                               if (pointList.size()>0)
+                               {
+                                       Point2D.Double prev1 = pointList.getLast();                     
+                                       Point2D.Double prev2 = pointList.getFirst();
+
+                                       if ((interForward.distance(prev1)+interBackward.distance(prev2))<(interForward.distance(prev2)+interBackward.distance(prev1)))
+                                       {
+                                               pointList.addLast(interForward);
+                                               pointList.addFirst(interBackward);
+                                       }
+                                       else
+                                       {
+                                               pointList.addFirst(interForward);
+                                               pointList.addLast(interBackward);
+                                       }
+                               }
+                               else
+                               {
+                                       pointList.addLast(interForward);
+                                       pointList.addFirst(interBackward);
+                               }
+                       }
+               }
+               if (getBases().size()==1)
+               {
+                       int midl = pointList.size()/2;
+                       Point2D.Double mid = pointList.get(midl);
+                       Point2D.Double apoint = new Point2D.Double(mid.x+1.,mid.y);
+                       LinkedList<Point2D.Double> pointListStart = buildRoundedCorner(pointList.get(midl-1), pointList.get(midl), apoint);
+                       pointList.addAll(midl, pointListStart);
+                       mid = pointList.get(midl);
+                       apoint = new Point2D.Double(mid.x+1.,mid.y);
+                       LinkedList<Point2D.Double> pointListEnd = buildRoundedCorner(pointList.get(pointList.size()-1),pointList.get(0), apoint);
+                       pointList.addAll(0,pointListEnd);                       
+               }
+               else if (getBases().size()>1)
+               {
+                       int midl = pointList.size()/2;
+                       Point2D.Double apoint = symImage(pointList.get(midl),pointList.get(midl-1));
+                       LinkedList<Point2D.Double> pointListStart = buildRoundedCorner(pointList.get(midl-1), pointList.get(midl), apoint);
+                       pointList.addAll(midl, pointListStart);
+                       apoint = symImage(realCoords[getBases().get(getBases().size()-1).getIndex()],
+                                       realCoords[getBases().get(getBases().size()-2).getIndex()]);
+                       LinkedList<Point2D.Double> pointListEnd = buildRoundedCorner(pointList.get(pointList.size()-1),pointList.get(0), apoint);
+                       pointList.addAll(0,pointListEnd);
+               }
+               
+
+               if (pointList.size()>0)
+               {
+                       Point2D.Double point = pointList.get(0);
+                       p.moveTo((float)point.x, (float)point.y);
+
+                       for (int i=1;i<pointList.size();i++)
+                       {
+                               point = pointList.get(i);
+                               p.lineTo((float)point.x, (float)point.y);                               
+                       }
+                       p.closePath();
+               }
+               return p;
+       }
+
+       
+       public static HighlightRegionAnnotation parseHighlightRegionAnnotation(String txt, VARNAPanel vp)
+       {
+               try
+               {
+               String[] parts = txt.split(":");
+               String[] coords = parts[0].split("-");
+               int from = Integer.parseInt(coords[0]);
+               int to = Integer.parseInt(coords[1]);
+               int  i = vp.getRNA().getIndexFromBaseNumber(from);
+               int  j = vp.getRNA().getIndexFromBaseNumber(to);
+               Color fill = HighlightRegionAnnotation.DEFAULT_FILL_COLOR;
+               Color outline = HighlightRegionAnnotation.DEFAULT_OUTLINE_COLOR;
+               double radius = HighlightRegionAnnotation.DEFAULT_RADIUS;
+               ArrayList<ModeleBase> bases = vp.getRNA().getBasesBetween(i, j);
+               if (parts.length>1)
+               {
+               try
+               {
+                       String[] options = parts[1].split(",");
+                       for (int k = 0; k < options.length; k++) 
+                       {
+                               //System.out.println(options[k]);
+                               try
+                               {
+                                       String[] data = options[k].split("=");
+                                       String lhs = data[0].toLowerCase();
+                                       String rhs = data[1];
+                                       if (lhs.equals("fill"))
+                                       {
+                                               fill = VARNAConfigLoader.getSafeColor(rhs, fill);
+                                       }
+                                       else if (lhs.equals("outline"))
+                                       {
+                                               outline = VARNAConfigLoader.getSafeColor(rhs, outline);
+                                       }
+                                       else if (lhs.equals("radius"))
+                                       {
+                                               radius = Double.parseDouble(rhs);
+                                       }       
+                               }
+                               catch(Exception e)
+                               {
+                               }                               
+                       }
+               }
+               catch(Exception e)
+               {
+                       e.printStackTrace();
+               }
+               }
+               return new HighlightRegionAnnotation(bases,fill,outline,radius);
+               }
+       catch(Exception e)
+       {
+               e.printStackTrace();
+       }
+       return null;
+       }
+       
+       public String toString()
+       {
+               //String result = "HighlightRegionAnnotation[";
+               //result += "fill:"+_fillColor.toString();
+               //result += ",outline:"+_outlineColor.toString();
+               //result += ",radius:"+_radius;
+               //return result+"]";
+               String result = "Highlighted region "+getMinIndex()+"-"+getMaxIndex();
+               return result;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/annotations/TextAnnotation.java b/srcjar/fr/orsay/lri/varna/models/annotations/TextAnnotation.java
new file mode 100644 (file)
index 0000000..0db63a5
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.annotations;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.VARNAConfigLoader;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.VARNAPoint;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+
+
+/**
+ * The annotated text model
+ * 
+ * @author Darty@lri.fr
+ * 
+ */
+public class TextAnnotation implements Serializable {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 465236085501860747L;
+       
+       public enum AnchorType{
+               POSITION,
+               BASE,
+               HELIX,
+               LOOP
+       };
+       
+
+       public static final String HEADER_TEXT = "TextAnnotation";
+       
+       /**
+        * default text color
+        */
+       public static final Color DEFAULTCOLOR = Color.black;
+       /**
+        * default text font
+        */
+       public static final Font DEFAULTFONT = new Font("Arial", Font.PLAIN, 12);
+
+       private String _text;
+       private AnchorType _typeAnchor;
+       private Color _color;
+       private double _angle;
+       private Object _anchor;
+       private Font _font;
+       
+       public static String XML_ELEMENT_NAME = "textAnnotation";
+       public static String XML_VAR_TYPE_NAME = "type";
+       public static String XML_VAR_COLOR_NAME = "color";
+       public static String XML_VAR_ANGLE_NAME = "angle";
+       public static String XML_VAR_TEXT_NAME = "text";
+
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_TYPE_NAME,"CDATA",""+_typeAnchor);
+               atts.addAttribute("","",XML_VAR_COLOR_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_color));
+               atts.addAttribute("","",XML_VAR_ANGLE_NAME,"CDATA",""+_angle);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               atts.clear();
+               hd.startElement("","",XML_VAR_TEXT_NAME,atts);
+               XMLUtils.exportCDATAString(hd, _text);
+               hd.endElement("","",XML_VAR_TEXT_NAME);
+               switch (_typeAnchor)
+               {
+               case POSITION:
+                       ((VARNAPoint)_anchor).toXML(hd,"pos");
+                       break;
+               case BASE:
+                       XMLUtils.toXML(hd, (ModeleBase)_anchor);
+                       break;
+               case HELIX:
+                       XMLUtils.toXML(hd, (ArrayList<ModeleBase>)_anchor);
+                       break;
+               case LOOP:
+                       XMLUtils.toXML(hd, (ArrayList<ModeleBase>)_anchor);
+                       break;
+               }               
+               XMLUtils.toXML(hd, _font);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+       /**
+        * creates an annoted text on a VARNAPanel with the specified text
+        * 
+        * @param texte Textual content of the annotation
+        */
+       public TextAnnotation(String texte) {
+               _text = texte;
+               _color = DEFAULTCOLOR;
+               _font = DEFAULTFONT;
+               _angle = 0;
+       }
+
+       /**
+        * /** creates an annoted text on a VARNAPanel with the specified text and
+        * is static position
+        * 
+        * @param texte
+        * @param x
+        * @param y
+        */
+       public TextAnnotation(String texte, double x, double y) {
+               this(texte);
+               _anchor = new VARNAPoint(x, y);
+               _typeAnchor = AnchorType.POSITION;
+       }
+
+       /**
+        * creates an annoted text on a VARNAPanel with the specified text fixed to
+        * a base
+        * 
+        * @param texte
+        * @param mb
+        */
+       public TextAnnotation(String texte, ModeleBase mb) {
+               this(texte);
+               _anchor = mb;
+               _typeAnchor = AnchorType.BASE;
+       }
+
+       /**
+        * creates an annoted text on a VARNAPanel with the specified text fixed to
+        * a helix (if type is HELIX) or to a loop (if type is LOOP)
+        * 
+        * @param texte
+        * @param listeBase
+        * @param type
+        * @throws Exception
+        */
+       public TextAnnotation(String texte, ArrayList<ModeleBase> listeBase,
+                       AnchorType type) throws Exception {
+               this(texte);
+               _anchor = listeBase;
+
+               if (type == AnchorType.HELIX)
+                       _typeAnchor = AnchorType.HELIX;
+               else if (type == AnchorType.LOOP)
+                       _typeAnchor = AnchorType.LOOP;
+               else
+                       throw new Exception("Bad argument");
+       }
+
+       /**
+        * creates an annoted text from another one
+        * 
+        * @param textAnnotation
+        */
+       public TextAnnotation(TextAnnotation textAnnotation) {
+               _anchor = textAnnotation.getAncrage();
+               _font = textAnnotation.getFont();
+               _text = textAnnotation.getTexte();
+               _typeAnchor = textAnnotation.getType();
+       }
+
+       /**
+        * 
+        * @return the text
+        */
+       public String getTexte() {
+               return _text;
+       }
+
+       public void setText(String _texte) {
+               this._text = _texte;
+       }
+
+       /**
+        * 
+        * @return the font
+        */
+       public Font getFont() {
+               return _font;
+       }
+
+       public void setFont(Font _font) {
+               this._font = _font;
+       }
+
+       public Object getAncrage() {
+               return _anchor;
+       }
+
+       public void setAncrage(ModeleBase mb) {
+               _anchor = mb;
+               _typeAnchor = AnchorType.BASE;
+       }
+
+       public void setAncrage(double x, double y) {
+               _anchor = new VARNAPoint(x, y);
+               _typeAnchor = AnchorType.POSITION;
+       }
+
+       public void setAncrage(ArrayList<ModeleBase> list, AnchorType type)
+                       throws Exception {
+               _anchor = list;
+               if (type == AnchorType.HELIX)
+                       _typeAnchor = AnchorType.HELIX;
+               else if (type == AnchorType.LOOP)
+                       _typeAnchor = AnchorType.LOOP;
+               else
+                       throw new Exception("Bad argument");
+       }
+
+       public AnchorType getType() {
+               return _typeAnchor;
+       }
+
+       public void setType(AnchorType t) {
+               _typeAnchor = t;
+       }
+
+
+       public Color getColor() {
+               return _color;
+       }
+
+       public void setColor(Color color) {
+               this._color = color;
+       }
+
+       
+       public String getHelixDescription()
+       {
+               ArrayList<ModeleBase> listeBase =  ((ArrayList<ModeleBase>)_anchor);
+               int minA = Integer.MAX_VALUE,maxA = Integer.MIN_VALUE;
+               int minB = Integer.MAX_VALUE,maxB = Integer.MIN_VALUE;
+               for(ModeleBase mb : listeBase)
+               {
+                       int i = mb.getBaseNumber();
+                       if (mb.getElementStructure()>i)
+                       {
+                               minA = Math.min(minA, i);
+                               maxA = Math.max(maxA, i);
+                       }
+                       else
+                       {
+                               minB = Math.min(minB, i);
+                               maxB = Math.max(maxB, i);                               
+                       }
+               }
+               return "["+minA+","+maxA+"] ["+minB+","+maxB+"]";
+       }
+       
+       public String getLoopDescription()
+       {
+               ArrayList<ModeleBase> listeBase =  ((ArrayList<ModeleBase>)_anchor);
+               int min = Integer.MAX_VALUE,max = Integer.MIN_VALUE;
+               for(ModeleBase mb : listeBase)
+               {
+                       int i = mb.getBaseNumber();
+                               min = Math.min(min, i);
+                               max = Math.max(max, i);
+               }
+               return "["+min+","+max+"]";
+       }
+       
+       public String toString() {
+               String tmp = "["+_text+"] ";
+               switch (_typeAnchor) {
+               case POSITION:
+                       NumberFormat formatter = new DecimalFormat(".00"); 
+                       return tmp+" at ("+formatter.format(getCenterPosition().x)+","+formatter.format(getCenterPosition().y)+")";
+               case BASE:
+                       return tmp+" on base "+((ModeleBase) _anchor).getBaseNumber();
+               case HELIX:
+                       return tmp+" on helix "+getHelixDescription();
+               case LOOP:
+                       return tmp+" on loop "+getLoopDescription();
+               default:
+                       return tmp;
+               }               
+       }
+
+       /**
+        * 
+        * @return the text position center
+        */
+       public Point2D.Double getCenterPosition() {
+               switch (_typeAnchor) {
+               case POSITION:
+                       return ((VARNAPoint) _anchor).toPoint2D();
+               case BASE:
+                       return ((ModeleBase) _anchor).getCoords();
+               case HELIX:
+                       return calculLoopHelix();
+               case LOOP:
+                       return calculLoop();
+               default:
+                       return new Point2D.Double(0., 0.);
+               }
+       }
+
+       private Point2D.Double calculLoop() {
+               ArrayList<ModeleBase> liste = extractedArrayListModeleBaseFromAncrage();
+               double totalX = 0., totalY = 0.;
+               for (ModeleBase base : liste) {
+                       totalX += base.getCoords().x;
+                       totalY += base.getCoords().y;
+               }
+               return new Point2D.Double(totalX / liste.size(), totalY / liste.size());
+       }
+
+       private Point2D.Double calculLoopHelix() {
+               ArrayList<ModeleBase> liste = extractedArrayListModeleBaseFromAncrage();
+               Collections.sort(liste);
+               double totalX = 0., totalY = 0.;
+               double num=0.0;
+               for (int i=0;i<liste.size(); i++) {
+                       ModeleBase base =liste.get(i);
+                       if ((i>0 && (i<liste.size()-1)) || ((liste.size()/2)%2==0))
+                       {
+                               totalX += base.getCoords().x;
+                               totalY += base.getCoords().y;
+                               num += 1;
+                       }
+               }
+               return new Point2D.Double(totalX / num, totalY / num);
+       }
+
+       
+       private ArrayList<ModeleBase> extractedArrayListModeleBaseFromAncrage() {
+               return (ArrayList<ModeleBase>) _anchor;
+       }
+
+       /**
+        * clone a TextAnnotation
+        */
+       public TextAnnotation clone() {
+               TextAnnotation textAnnot = null;
+               try {
+                       switch (_typeAnchor) {
+                       case BASE:
+                               textAnnot = new TextAnnotation(_text, (ModeleBase) _anchor);
+                               break;
+                       case POSITION:
+                               textAnnot = new TextAnnotation(_text,
+                                               ((VARNAPoint) _anchor).x,
+                                               ((VARNAPoint) _anchor).y);
+                               break;
+                       case LOOP:
+                               textAnnot = new TextAnnotation(_text,
+                                               extractedArrayListModeleBaseFromAncrage(), AnchorType.LOOP);
+                               break;
+                       case HELIX:
+                               textAnnot = new TextAnnotation(_text,
+                                               extractedArrayListModeleBaseFromAncrage(), AnchorType.HELIX);
+                               break;
+                       default:
+                               break;
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               textAnnot.setFont(_font);
+               textAnnot.setColor(_color);
+               return textAnnot;
+
+       }
+
+       /**
+        * copy a textAnnotation
+        * 
+        * @param textAnnotation
+        */
+       public void copy(TextAnnotation textAnnotation) {
+               _anchor = textAnnotation.getAncrage();
+               _font = textAnnotation.getFont();
+               _text = textAnnotation.getTexte();
+               _typeAnchor = textAnnotation.getType();
+               _color = textAnnotation.getColor();
+               _angle = textAnnotation.getAngleInDegres();
+       }
+
+
+       /**
+        * 
+        * @return the angle in degrees
+        */
+       public double getAngleInDegres() {
+               // if (_typeAncrage == TextAnnotation.HELIX)
+               // _angle = calculAngleDegres();
+               return _angle;
+       }
+
+       /**
+        * 
+        * @return the angle in radians
+        */
+       public double getAngleInRadians() {
+               return (getAngleInDegres() * Math.PI) / 180.;
+       }
+
+       public void setAngleInDegres(double _angle) {
+               this._angle = _angle;
+       }
+
+       public void setAngleInRadians(double _angle) {
+               this._angle = _angle * 180 / Math.PI;
+       }
+       
+       
+       public static TextAnnotation parse(String thisAnn, VARNAPanel vp)
+       {
+               String[] data = thisAnn.split(":");
+
+               String text = "";
+               int anchor = -1;
+               int x = -1;
+               int y = -1;
+               TextAnnotation.AnchorType type = TextAnnotation.AnchorType.LOOP;
+               Font font = TextAnnotation.DEFAULTFONT;
+               Color color = TextAnnotation.DEFAULTCOLOR;
+               TextAnnotation ann = null;
+               try {
+                       if (data.length == 2) {
+                               text = data[0];
+                               String[] data2 = data[1].split(",");
+                               for (int j = 0; j < data2.length; j++) {
+                                       String opt = data2[j];
+                                       String[] data3 = opt.split("=");
+                                       if (data3.length == 2) {
+                                               String name = data3[0].toLowerCase();
+                                               String value = data3[1];
+                                               if (name.equals("type")) {
+                                                       if (value.toUpperCase().equals("H")) {
+                                                               type = TextAnnotation.AnchorType.HELIX;
+                                                       } else if (value.toUpperCase().equals("L")) {
+                                                               type = TextAnnotation.AnchorType.LOOP;
+                                                       } else if (value.toUpperCase().equals("P")) {
+                                                               type = TextAnnotation.AnchorType.POSITION;
+                                                       } else if (value.toUpperCase().equals("B")) {
+                                                               type = TextAnnotation.AnchorType.BASE;
+                                                       }
+                                               } else if (name.equals("x")) {
+                                                       x = Integer.parseInt(value);
+                                               } else if (name.equals("y")) {
+                                                       y = Integer.parseInt(value);
+                                               } else if (name.equals("anchor")) {
+                                                       anchor = Integer.parseInt(value);
+                                               } else if (name.equals("size")) {
+                                                       font = font.deriveFont((float) Integer
+                                                                       .parseInt(value));
+                                               } else if (name.equals("color")) {
+                                                       color = VARNAConfigLoader.getSafeColor(value, color);
+                                               }
+                                       }
+                               }
+                               switch (type) {
+                               case POSITION:
+                                       if ((x != -1) && (y != -1)) {
+                                               Point2D.Double p = vp
+                                                               .panelToLogicPoint(new Point2D.Double(x, y));
+                                               ann = new TextAnnotation(text, p.x, p.y);
+                                       }
+                                       break;
+                               case BASE:
+                                       if (anchor != -1) {
+                                               int index = vp.getRNA().getIndexFromBaseNumber(
+                                                               anchor);
+                                               ModeleBase mb = vp.getRNA().get_listeBases()
+                                                               .get(index);
+                                               ann = new TextAnnotation(text, mb);
+                                       }
+                                       break;
+                               case HELIX:
+                                       if (anchor != -1) {
+                                               ArrayList<ModeleBase> mbl = new ArrayList<ModeleBase>();
+                                               int index = vp.getRNA().getIndexFromBaseNumber(
+                                                               anchor);
+                                               ArrayList<Integer> il = vp.getRNA()
+                                                               .findHelix(index);
+                                               for (int k : il) {
+                                                       mbl.add(vp.getRNA().get_listeBases().get(k));
+                                               }
+                                               ann = new TextAnnotation(text, mbl, type);
+                                       }
+                                       break;
+                               case LOOP:
+                                       if (anchor != -1) {
+                                               ArrayList<ModeleBase> mbl = new ArrayList<ModeleBase>();
+                                               int index = vp.getRNA().getIndexFromBaseNumber(
+                                                               anchor);
+                                               ArrayList<Integer> il = vp.getRNA().findLoop(index);
+                                               for (int k : il) {
+                                                       mbl.add(vp.getRNA().get_listeBases().get(k));
+                                               }
+                                               ann = new TextAnnotation(text, mbl, type);
+                                       }
+                                       break;
+                               }
+                               if (ann != null) {
+                                       ann.setColor(color);
+                                       ann.setFont(font);
+                               }
+                       }
+               } catch (Exception e) {
+                       System.err.println("Apply Annotations: " + e.toString());
+               }
+               return ann;
+               }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/ArcCommand.java b/srcjar/fr/orsay/lri/varna/models/export/ArcCommand.java
new file mode 100644 (file)
index 0000000..aeb1466
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class ArcCommand extends GraphicElement {
+
+       private Point2D.Double center;
+       private double width, height;
+       private double startAngle, endAngle;
+
+       public ArcCommand(Point2D.Double origine, double width, double height,
+                       double startAngle, double endAngle) {
+               this.center = origine;
+               this.width = width;
+               this.height = height;
+               this.startAngle = startAngle;
+               this.endAngle = endAngle;
+       }
+
+       public Point2D.Double getCenter() {
+               return center;
+       }
+
+       public double getWidth() {
+               return width;
+       }
+
+       public double getHeight() {
+               return height;
+       }
+
+       public double getStartAngle() {
+               return startAngle;
+       }
+
+       public double getEndAngle() {
+               return endAngle;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/CircleCommand.java b/srcjar/fr/orsay/lri/varna/models/export/CircleCommand.java
new file mode 100644 (file)
index 0000000..bc6c286
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class CircleCommand extends GraphicElement {
+
+       private Point2D.Double _base;
+       private double _radius;
+       private double _thickness;
+
+       public CircleCommand(Point2D.Double base, double radius, double thickness) {
+               _base = base;
+               _radius = radius;
+               _thickness = thickness;
+       }
+
+       public Point2D.Double get_base() {
+               return _base;
+       }
+
+       public double get_radius() {
+               return _radius;
+       }
+
+       public double get_thickness() {
+               return _thickness;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/ColorCommand.java b/srcjar/fr/orsay/lri/varna/models/export/ColorCommand.java
new file mode 100644 (file)
index 0000000..fd1bea9
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+
+public class ColorCommand extends GraphicElement {
+       Color _color;
+
+       public ColorCommand(Color c) {
+               _color = c;
+       }
+
+       public Color getColor() {
+               return _color;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/FillCircleCommand.java b/srcjar/fr/orsay/lri/varna/models/export/FillCircleCommand.java
new file mode 100644 (file)
index 0000000..8c971e8
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+
+public class FillCircleCommand extends GraphicElement {
+
+       private Point2D.Double _base;
+       private double _radius;
+       private double _thickness;
+       private Color _color;
+
+       public FillCircleCommand(Point2D.Double base, double radius,
+                       double thickness, Color color) {
+               _base = base;
+               _radius = radius;
+               _thickness = thickness;
+               _color = color;
+       }
+
+       public Point2D.Double get_base() {
+               return _base;
+       }
+
+       public double get_radius() {
+               return _radius;
+       }
+
+       public double get_thickness() {
+               return _thickness;
+       }
+
+       public Color get_color() {
+               return _color;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/FillPolygonCommand.java b/srcjar/fr/orsay/lri/varna/models/export/FillPolygonCommand.java
new file mode 100644 (file)
index 0000000..d4266ae
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+
+public class FillPolygonCommand extends GraphicElement {
+
+       private Point2D.Double[] _points;
+       //private double _thickness;
+       private Color _color;
+
+       public FillPolygonCommand(Point2D.Double[] points, 
+                       Color color) {
+               _points = points;
+               _color = color;
+       }
+
+       public Point2D.Double[] get_points() {
+               return _points;
+       }
+
+
+       public Color get_color() {
+               return _color;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/FontCommand.java b/srcjar/fr/orsay/lri/varna/models/export/FontCommand.java
new file mode 100644 (file)
index 0000000..bf93264
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+public class FontCommand extends GraphicElement {
+       private int _font;
+       private double _size;
+
+       public FontCommand(int font, double size) {
+               _font = font;
+               _size = size;
+       }
+
+       public int get_font() {
+               return _font;
+       }
+
+       public double get_size() {
+               return _size;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/GraphicElement.java b/srcjar/fr/orsay/lri/varna/models/export/GraphicElement.java
new file mode 100644 (file)
index 0000000..459308c
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+public abstract class GraphicElement {
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/LineCommand.java b/srcjar/fr/orsay/lri/varna/models/export/LineCommand.java
new file mode 100644 (file)
index 0000000..476abe9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class LineCommand extends GraphicElement {
+       private Point2D.Double _orig;
+       private Point2D.Double _dest;
+       private double _thickness;
+
+       public LineCommand(Point2D.Double orig, Point2D.Double dest,
+                       double thickness) {
+               _orig = orig;
+               _dest = dest;
+               _thickness = thickness;
+       }
+
+       public Point2D.Double get_orig() {
+               return _orig;
+       }
+
+       public Point2D.Double get_dest() {
+               return _dest;
+       }
+
+       public double get_thickness() {
+               return _thickness;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/PSExport.java b/srcjar/fr/orsay/lri/varna/models/export/PSExport.java
new file mode 100644 (file)
index 0000000..7907150
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D.Double;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+/**
+ * @author ponty
+ * 
+ */
+public class PSExport extends SecStrDrawingProducer {
+
+       public PSExport()
+       {
+               super();
+               super.setScale(0.4);
+       }
+       
+       private String PSMacros() {
+               String setFontSize =
+               // Params [fontsize|...]
+               "/setbasefont \n" + "{ /Helvetica-Bold findfont\n" + // =>
+                               // [font|scale|...]
+                               "  exch scalefont\n" + // => [scaled_font|...]
+                               "  setfont \n" + // => [...]
+                               "  } def\n\n";
+
+               String writeTextCentered =
+               // Params [txt|size|...]
+               "/txtcenter \n" + "{ dup \n" + // => [txt|txt|size|...]
+                               "  stringwidth pop\n" + // => [wtxt|txt|size|...]
+                               "  2 div neg \n" + // => [-wtxt/2|txt|size|...]
+                               "  3 -1 roll \n" + // => [size|-wtxt/2|txt...]
+                               "  2 div neg\n" + // => [-size/2|-wtxt/2|txt|...]
+                               "  rmoveto\n" + // => [txt|...]
+                               "  show\n" + // => [...]
+                               "  } def\n\n";
+
+               String drawEllipse = "/ellipse {\n" + "  /endangle exch def\n"
+                               + "  /startangle exch def\n" + "  /yrad exch def\n"
+                               + "  /xrad exch def\n" + "  /y exch def\n" + "  /x exch def\n"
+                               + "  /savematrix matrix currentmatrix def\n"
+                               + "  x y translate\n" + "  xrad yrad scale\n"
+                               + "  0 0 1 startangle endangle arc\n"
+                               + "  savematrix setmatrix\n" + "  } def\n\n";
+               return setFontSize + writeTextCentered + drawEllipse;
+       }
+
+       private String EPSHeader(double minX, double maxX, double minY, double maxY) {
+               String bbox = PSBBox(minX, minY, maxX, maxY);
+               String init = "%!PS-Adobe-3.0\n" + "%%Pages: 1\n" + bbox
+                               + "%%EndComments\n" + "%%Page: 1 1\n";
+               String macros = PSMacros();
+               return init + macros;
+       }
+
+       private String EPSFooter() {
+               return "showpage\n" + "%%EndPage: 1\n" + "%%EOF";
+       }
+
+       private String PSNewPath() {
+               return ("newpath\n");
+       }
+
+       private String PSMoveTo(double x, double y) {
+               return ("" + x + " " + y + " moveto\n");
+       }
+
+       private String PSLineTo(double dx, double dy) {
+               return ("" + dx + " " + dy + " lineto\n");
+       }
+
+       private String PSRLineTo(double dx, double dy) {
+               return ("" + dx + " " + dy + " rlineto\n");
+       }
+
+       private String PSSetLineWidth(double thickness) {
+               thickness /= 2;
+               return ("" + thickness + " setlinewidth\n");
+       }
+
+       private String PSStroke() {
+               return ("stroke\n");
+       }
+
+       private String PSArc(double x, double y, double radiusX, double radiusY,
+                       double angleFrom, double angleTo) {
+
+               double centerX = x;
+               double centerY = y;
+
+               // return (centerX + " " + centerY + " "+ radiusX/2.0+" " + angleFrom +
+               // " " + angleTo + "  arc\n");
+
+               return (centerX + " " + centerY + " " + radiusX / 2.0 + " " + radiusY
+                               / 2.0 + " " + angleTo + " " + angleFrom + " ellipse\n");
+
+       }
+
+       private String PSArc(double x, double y, double radius, double angleFrom,
+                       double angleTo) {
+
+               return ("" + x + " " + y + " " + radius + " " + angleFrom + " "
+                               + angleTo + "  arc\n");
+       }
+
+       private String PSBBox(double minX, double maxX, double minY, double maxY) {
+               String norm = ("%%BoundingBox: " + (long) Math.floor(minX) + " "
+                               + (long) Math.floor(minY) + " " + (long) Math.ceil(maxX) + " "
+                               + (long) Math.ceil(maxY) + "\n");
+               String high = ("%%HighResBoundingBox: " + (long) Math.floor(minX) + " "
+                               + (long) Math.floor(minY) + " " + (long) Math.ceil(maxX) + " "
+                               + (long) Math.ceil(maxY) + "\n");
+               return norm + high;
+       }
+
+       private String PSText(String txt) {
+               return ("(" + txt + ") ");
+       }
+
+       @SuppressWarnings("unused")
+       private String PSShow() {
+               return ("show\n");
+       }
+
+       private String PSClosePath() {
+               return ("closepath\n");
+       }
+
+       private String PSFill() {
+               return ("fill\n");
+       }
+
+       private String PSSetColor(Color col) {
+               return ("" + (((double) col.getRed()) / 255.0) + " "
+                               + (((double) col.getGreen()) / 255.0) + " "
+                               + (((double) col.getBlue()) / 255.0) + " setrgbcolor\n");
+       }
+
+       private String fontName(int font) {
+               switch (font) {
+               case (FONT_TIMES_ROMAN):
+                       return "/Times-Roman";
+               case (FONT_TIMES_BOLD):
+                       return "/Times-Bold";
+               case (FONT_TIMES_ITALIC):
+                       return "/Times-Italic";
+               case (FONT_TIMES_BOLD_ITALIC):
+                       return "/Times-BoldItalic";
+               case (FONT_HELVETICA):
+                       return "/Helvetica";
+               case (FONT_HELVETICA_BOLD):
+                       return "/Helvetica-Bold";
+               case (FONT_HELVETICA_OBLIQUE):
+                       return "/Helvetica-Oblique";
+               case (FONT_HELVETICA_BOLD_OBLIQUE):
+                       return "/Helvetica-BoldOblique";
+               case (FONT_COURIER):
+                       return "/Courier";
+               case (FONT_COURIER_BOLD):
+                       return "/Courier-Bold";
+               case (FONT_COURIER_OBLIQUE):
+                       return "/Courier-Oblique";
+               case (FONT_COURIER_BOLD_OBLIQUE):
+                       return "/Courier-BoldOblique";
+               }
+               return "/Helvetica";
+       }
+
+       private String PSSetFont(int font, double size) {
+               return (fontName(font) + " findfont " + size + " scalefont setfont\n");
+       }
+
+       public String setFontS(int font, double size) {
+               _fontsize = (long) (0.4 * size);
+               return PSSetFont(font, _fontsize);
+       }
+
+       public String setColorS(Color col) {
+               super.setColorS(col);
+               String result = PSSetColor(col);
+               return result;
+       }
+
+       public String drawLineS(Point2D.Double p0, Point2D.Double p1,
+                       double thickness) {
+               String tmp = "";
+               tmp += PSMoveTo(p0.x, p0.y);
+               tmp += PSLineTo(p1.x, p1.y);
+               tmp += PSSetLineWidth(thickness);
+               tmp += PSStroke();
+               return tmp;
+       }
+
+       public String drawTextS(Point2D.Double p, String txt) {
+               String tmp = "";
+               tmp += PSMoveTo(p.x, p.y);
+               tmp += ("" + (_fontsize / 2.0 + 1) + " \n");
+               tmp += PSText(txt);
+               tmp += (" txtcenter\n");
+               return tmp;
+       }
+
+       public String drawRectangleS(Point2D.Double orig, Point2D.Double dims,
+                       double thickness) {
+               String tmp = PSNewPath();
+               tmp += PSMoveTo(orig.x, orig.y);
+               tmp += PSRLineTo(0, dims.y);
+               tmp += PSRLineTo(dims.x, 0);
+               tmp += PSRLineTo(0, -dims.y);
+               tmp += PSClosePath();
+               tmp += PSSetLineWidth(thickness);
+               tmp += PSStroke();
+               return tmp;
+       }
+
+       public String drawCircleS(Point2D.Double p, double radius, double thickness) {
+               String tmp = PSNewPath();
+               tmp += PSArc(p.x, p.y, radius, 0, 360);
+               tmp += PSSetLineWidth(thickness);
+               tmp += PSStroke();
+               return tmp;
+       }
+
+       public String fillCircleS(Point2D.Double p, double radius,
+                       double thickness, Color color) {
+               String tmp = PSNewPath();
+               tmp += PSArc(p.x, p.y, radius, 0, 360);
+               tmp += PSSetLineWidth(thickness);
+               tmp += PSSetColor(color);
+               tmp += PSFill();
+               return tmp;
+       }
+
+       public String footerS() {
+               return EPSFooter();
+       }
+
+       public String headerS(Rectangle2D.Double bb) {
+               return EPSHeader(bb.x, bb.y, bb.x + bb.width, bb.y + bb.height);
+       }
+
+       @Override
+       public String drawArcS(Point2D.Double origine, double width, double height,
+                       double startAngle, double endAngle) {
+               return PSArc(origine.x, origine.y, width, height, startAngle, endAngle)
+                               + PSStroke();
+       }
+
+       @Override
+       public String drawPolygonS(Double[] points, double thickness) {
+               String tmp = PSNewPath();
+               tmp += PSSetLineWidth(thickness);
+               for (int i = 0; i < points.length; i++) {
+                       if (i == 0) {
+                               tmp += PSMoveTo(points[i].x, points[i].y);
+                       } else {
+                               tmp += PSLineTo(points[i].x, points[i].y);
+                       }
+               }
+               tmp += PSClosePath();
+               tmp += PSStroke();
+               return tmp;
+       }
+
+       @Override
+       public String fillPolygonS(Double[] points, Color color) {
+               Color bck = _curColor;
+               String tmp = PSNewPath();
+               for (int i = 0; i < points.length; i++) {
+                       if (i == 0) {
+                               tmp += PSMoveTo(points[i].x, points[i].y);
+                       } else {
+                               tmp += PSLineTo(points[i].x, points[i].y);
+                       }
+               }
+               tmp += PSClosePath();
+               tmp += PSSetColor(color);
+               tmp += PSFill();
+               tmp += PSSetColor(bck);
+               return tmp;
+       }
+
+       @Override
+       public String drawBaseStartS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBaseEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairStartS(int i, int j, ModeleBP bps) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneStartS(int i, int j) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneEndS(int index) {
+               return "";
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/export/PolygonCommand.java b/srcjar/fr/orsay/lri/varna/models/export/PolygonCommand.java
new file mode 100644 (file)
index 0000000..cfcf0bd
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class PolygonCommand extends GraphicElement {
+
+       private Point2D.Double[] _points;
+       private double _thickness;
+
+       public PolygonCommand(Point2D.Double[] points, double thickness) {
+               _points = points;
+               _thickness = thickness;
+       }
+
+       public Point2D.Double[] get_points() {
+               return _points;
+       }
+
+       public double get_thickness() {
+               return _thickness;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/RectangleCommand.java b/srcjar/fr/orsay/lri/varna/models/export/RectangleCommand.java
new file mode 100644 (file)
index 0000000..8e0f080
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class RectangleCommand extends GraphicElement {
+       private Point2D.Double _orig;
+       private Point2D.Double _dims;
+       private double _thickness;
+
+       public RectangleCommand(Point2D.Double orig, Point2D.Double dims,
+                       double thickness) {
+               _orig = orig;
+               _dims = dims;
+               _thickness = thickness;
+       }
+
+       public Point2D.Double get_orig() {
+               return _orig;
+       }
+
+       public Point2D.Double get_dims() {
+               return _dims;
+       }
+
+       public double get_thickness() {
+               return _thickness;
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/export/SVGExport.java b/srcjar/fr/orsay/lri/varna/models/export/SVGExport.java
new file mode 100644 (file)
index 0000000..0ddd711
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D.Double;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public class SVGExport extends SecStrDrawingProducer {
+
+       private double _fontsize = 10.0;
+       private Rectangle2D.Double _bb = new Rectangle2D.Double(0, 0, 10, 10);
+       double _thickness = 2.0;
+       
+       
+       public SVGExport()
+       {
+               super();
+               super.setScale(0.5);
+       }
+
+       private String getRGBString(Color col) {
+               int rpc = (int) ((((double) col.getRed()) / 255.0) * 100);
+               int gpc = (int) ((((double) col.getGreen()) / 255.0) * 100);
+               int bpc = (int) ((((double) col.getBlue()) / 255.0) * 100);
+               return "rgb(" + rpc + "%, " + gpc + "%, " + bpc + "%)";
+       }
+
+       public String drawCircleS(Point2D.Double base, double radius,
+                       double thickness) {
+               _thickness = thickness;
+               return "<circle cx=\"" + base.x + "\" cy=\"" + (_bb.height - base.y)
+                               + "\" r=\"" + radius + "\" stroke=\"" + getRGBString(_curColor)
+                               + "\" stroke-width=\"" + thickness + "\" fill=\"none\"/>\n";
+       }
+
+       public String drawLineS(Point2D.Double orig, Point2D.Double dest,
+                       double thickness) {
+               _thickness = thickness;
+               return "<line x1=\"" + orig.x + "\" y1=\"" + (_bb.height - orig.y)
+                               + "\" x2=\"" + dest.x + "\" y2=\"" + (_bb.height - dest.y)
+                               + "\" stroke=\"" + getRGBString(_curColor)
+                               + "\" stroke-width=\"" + thickness + "\" />\n";
+       }
+
+       public String drawRectangleS(Point2D.Double orig, Point2D.Double dims,
+                       double thickness) {
+               _thickness = thickness;
+               return "<rect x=\""+ orig.x+"\" y=\""+(_bb.height - orig.y - dims.y)+"\" width=\""+dims.x
+                 +"\" height=\""+dims.y+
+                 "\" fill=\"none\" style=\"stroke-width:"+thickness+";stroke:"+getRGBString(_curColor)+"\"/>";
+       }
+
+       public String drawTextS(Point2D.Double base, String txt) {
+               //System.out.println(txt);
+               return "<text x=\""
+                               + (base.x)
+                               + "\" y=\""
+                               + (_bb.height - base.y + 0.4 * _fontsize)
+                               + "\" text-anchor=\"middle\" font-family=\"Verdana\" font-size=\""
+                               + _fontsize + "\" fill=\"" + getRGBString(_curColor) + "\" >"
+                               + txt + "</text>\n";
+       }
+
+       public String fillCircleS(Point2D.Double base, double radius,
+                       double thickness, Color col) {
+               _thickness = thickness;
+
+               return "<circle cx=\"" + base.x + "\" cy=\"" + (_bb.height - base.y)
+                               + "\" r=\"" + radius + "\" stroke=\"none\" stroke-width=\""
+                               + thickness + "\" fill=\"" + getRGBString(col) + "\"/>\n";
+       }
+
+       public String footerS() {
+               return "</svg>\n";
+       }
+
+       public String headerS(Rectangle2D.Double bb) {
+               _bb = bb;
+               return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+                               + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \n"
+                               + "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
+                               + "\n"
+                               + "<svg width=\"100%\" height=\"100%\" version=\"1.1\"\n"
+                               + "xmlns=\"http://www.w3.org/2000/svg\">\n";
+       }
+
+       public String setFontS(int font, double size) {
+               _fontsize = 0.5 * size;
+               return "";
+       }
+
+       
+       private Point2D.Double polarToCartesian(Point2D.Double center, double radiusX, double radiusY, double angleInDegrees) {
+                 double angleInRadians = (angleInDegrees) * Math.PI / 180.0;
+
+                 return new Point2D.Double(center.x + (radiusX * Math.cos(angleInRadians)),
+                                 _bb.height - (center.y + (radiusY * Math.sin(angleInRadians))));
+               }
+
+       
+       public String drawArcS(Point2D.Double o, double width, double height,
+                       double startAngle, double endAngle) {
+               double rx = width / 2.0;
+               double ry = height / 2.0;
+               
+               Point2D.Double ps = polarToCartesian(o,rx,ry,startAngle);
+               Point2D.Double pe = polarToCartesian(o,rx,ry,endAngle);
+               
+               String d = "<path d=\"M " + ps.x + "," +  ps.y + " A " + rx + "," + ry
+                               + " 0 1,1 " +  pe.x + "," + pe.y + "\" style=\"fill:none; stroke:"
+                               + getRGBString(_curColor) + "; stroke-width:" + _thickness
+                               + "\"/>\n";             
+               return d;
+       }
+
+       public String drawPolygonS(Double[] points, double thickness) {
+               String result = "<path d=\"";
+               for (int i = 0; i < points.length; i++) {
+                       if (i == 0) {
+                               result += "M " + points[i].x + " " + (_bb.height - points[i].y)
+                                               + " ";
+                       } else {
+                               result += "L " + points[i].x + " " + (_bb.height - points[i].y)
+                                               + " ";
+                       }
+               }
+               result += "z\" style=\"fill:none; stroke:" + getRGBString(_curColor)
+                               + "; stroke-width:" + thickness + ";\"/>\n";
+               return result;
+       }
+
+       @Override
+       public String fillPolygonS(Double[] points, Color col) {
+               String result = "<path d=\"";
+               for (int i = 0; i < points.length; i++) {
+                       if (i == 0) {
+                               result += "M " + points[i].x + " " + (_bb.height - points[i].y)
+                                               + " ";
+                       } else {
+                               result += "L " + points[i].x + " " + (_bb.height - points[i].y)
+                                               + " ";
+                       }
+               }
+               result += "z\" fill=\""+getRGBString(col)+"\" style=\"stroke:none;\"/>\n";
+               return result;
+       }
+       
+       @Override
+       public String drawBaseStartS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBaseEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairStartS(int i, int j, ModeleBP bps) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneStartS(int i, int j) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneEndS(int index) {
+               return "";
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/SecStrDrawingProducer.java b/srcjar/fr/orsay/lri/varna/models/export/SecStrDrawingProducer.java
new file mode 100644 (file)
index 0000000..a80e8e2
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Vector;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public abstract class SecStrDrawingProducer {
+       public static final int FONT_TIMES_ROMAN = 0;
+       public static final int FONT_TIMES_BOLD = 1;
+       public static final int FONT_TIMES_ITALIC = 2;
+       public static final int FONT_TIMES_BOLD_ITALIC = 3;
+       public static final int FONT_HELVETICA = 16;
+       public static final int FONT_HELVETICA_OBLIQUE = 17;
+       public static final int FONT_HELVETICA_BOLD = 18;
+       public static final int FONT_HELVETICA_BOLD_OBLIQUE = 19;
+       public static final int FONT_COURIER = 12;
+       public static final int FONT_COURIER_BOLD = 13;
+       public static final int FONT_COURIER_OBLIQUE = 14;
+       public static final int FONT_COURIER_BOLD_OBLIQUE = 15;
+
+       private Vector<GraphicElement> _commands = new Vector<GraphicElement>();
+
+       private double _scale = 1.0;
+       private double _xmin = Double.MAX_VALUE;
+       private double _ymin = Double.MAX_VALUE;
+       private double _xmax = -Double.MAX_VALUE;
+       private double _ymax = -Double.MAX_VALUE;
+
+       protected Color _curColor = Color.black;
+       protected Color _backgroundColor = null;
+       
+       protected double _fontsize = 10.0;
+       protected int _font = FONT_HELVETICA_BOLD;
+
+       
+       public Color getCurrentColor() {
+               return _curColor;
+       }
+
+       public double getCurFontSize() {
+               return _fontsize;
+       }
+
+       public int getCurrentFont() {
+               return _font;
+       }
+       
+
+       public abstract String drawBaseStartS(int index);
+       public abstract String drawBaseEndS(int index);
+       public abstract String drawBasePairStartS(int i, int j, ModeleBP bps);
+       public abstract String drawBasePairEndS(int index);
+       public abstract String drawBackboneStartS(int i, int j);
+       public abstract String drawBackboneEndS(int index);
+
+       public abstract String drawLineS(Point2D.Double orig,
+                       Point2D.Double dest, double thickness);
+
+       
+       public abstract String drawArcS(Point2D.Double origine, double width,
+                       double height, double startAngle, double endAngle);
+
+       public abstract String drawTextS(Point2D.Double base, String txt);
+
+       public abstract String drawRectangleS(Point2D.Double orig,
+                       Point2D.Double dims, double thickness);
+       
+       public abstract String drawCircleS(Point2D.Double base, double radius,
+                       double thickness);
+
+       public abstract String fillCircleS(Point2D.Double base, double radius,
+                       double thickness, Color color);
+
+       public abstract String drawPolygonS(Point2D.Double[] points,
+                       double thickness);
+
+       public abstract String fillPolygonS(Point2D.Double[] points,
+                Color color);
+
+       public abstract String setFontS(int font, double size);
+
+       public String setColorS(Color col) {
+               _curColor = col;
+               return "";
+       }
+
+       public abstract String headerS(Rectangle2D.Double bb);
+
+       public abstract String footerS();
+
+       @SuppressWarnings("unused")
+       private void resetBoundingBox() {
+               _xmin = Double.MAX_VALUE;
+               _ymin = Double.MAX_VALUE;
+               _xmax = -Double.MAX_VALUE;
+               _ymax = -Double.MAX_VALUE;
+       }
+
+       private void updateBoundingBox(double x, double y) {
+               _xmin = Math.min(_xmin, x - 10);
+               _ymin = Math.min(_ymin, y - 10);
+               _xmax = Math.max(_xmax, x + 10);
+               _ymax = Math.max(_ymax, y + 10);
+       }
+
+       public void drawLine(double x0, double y0, double x1, double y1,
+                       double thickness) {
+               updateBoundingBox(x0, y0);
+               updateBoundingBox(x1, y1);
+               _commands.add(new LineCommand(new Point2D.Double(x0, y0),
+                               new Point2D.Double(x1, y1), thickness));
+       }
+
+       public void drawArc(Point2D.Double origine, double width, double height,
+                       double startAngle, double endAngle) {
+               updateBoundingBox(origine.x + width/2., origine.y + height/2.);
+               updateBoundingBox(origine.x - width/2., origine.y - height/2.);
+               _commands.add(new ArcCommand(origine, width, height, startAngle,
+                               endAngle));
+       }
+
+       public void drawText(double x, double y, String txt) {
+               updateBoundingBox(x, y);
+
+               _commands.add(new TextCommand(new Point2D.Double(x, y), new String(txt)));
+       }
+
+       public void drawRectangle(double x, double y, double w, double h,
+                       double thickness) {
+               updateBoundingBox(x, y);
+               updateBoundingBox(x + w, y + h);
+
+               _commands.add(new RectangleCommand(new Point2D.Double(x, y),
+                               new Point2D.Double(w, h), thickness));
+       }
+       
+       public void fillRectangle(double x, double y, double w, double h, Color color) {
+               double [] xtab = new double[4];
+               double [] ytab = new double[4];
+               xtab[0] = x;
+               xtab[1] = x+w;
+               xtab[2] = x+w;
+               xtab[3] = x;
+               ytab[0] = y;
+               ytab[1] = y;
+               ytab[2] = y+h;
+               ytab[3] = y+h;
+               fillPolygon(xtab, ytab, color);
+       }
+
+       public void drawCircle(double x, double y, double radius, double thickness) {
+               updateBoundingBox(x - radius, y - radius);
+               updateBoundingBox(x + radius, y + radius);
+
+               _commands.add(new CircleCommand(new Point2D.Double(x, y), radius,
+                               thickness));
+
+       }
+
+       public void setColor(Color col) {
+               _curColor = col;
+               _commands.add(new ColorCommand(col));
+       }
+       
+       public void setBackgroundColor(Color col){
+               _backgroundColor = col;
+       }
+
+       public void removeBackgroundColor(){
+               _backgroundColor = null;
+       }
+
+       public void fillCircle(double x, double y, double radius, double thickness,
+                       Color color) {
+               updateBoundingBox(x - radius, y - radius);
+               updateBoundingBox(x + radius, y + radius);
+
+               _commands.add(new FillCircleCommand(new Point2D.Double(x, y), radius,
+                               thickness, color));
+       }
+
+       public void drawPolygon(double[] xtab, double[] ytab, double thickness) {
+               if (xtab.length == ytab.length) {
+                       Point2D.Double points[] = new Point2D.Double[xtab.length];
+                       for (int i = 0; i < xtab.length; i++) {
+                               points[i] = new Point2D.Double(xtab[i], ytab[i]);
+                               updateBoundingBox(xtab[i], ytab[i]);
+                       }
+                       _commands.add(new PolygonCommand(points, thickness));
+               }
+       }
+
+       public void drawPolygon(GeneralPath p, 
+                       double thickness) {
+               PathIterator pi = p.getPathIterator(null);
+               Vector<Point2D.Double> v = new Vector<Point2D.Double>();  
+               double[] coords = new double[6];
+               while (!pi.isDone())
+               {
+                       int code = pi.currentSegment(coords);
+                       if (code == PathIterator.SEG_MOVETO)
+                       { v.add(new Point2D.Double(coords[0],coords[1])); }
+                       if (code == PathIterator.SEG_LINETO)
+                       { v.add(new Point2D.Double(coords[0],coords[1])); }                     
+                       pi.next();
+               }
+               double[] xtab = new double[v.size()];
+               double[] ytab = new double[v.size()];
+               for(int i=0;i<v.size();i++)
+               {
+                       xtab[i] = v.get(i).x;
+                       ytab[i] = v.get(i).y;
+               }
+               drawPolygon(xtab,ytab, thickness);
+       }
+
+               
+       public void fillPolygon(GeneralPath p, 
+                       Color color) {
+               PathIterator pi = p.getPathIterator(null);
+               Vector<Point2D.Double> v = new Vector<Point2D.Double>();  
+               double[] coords = new double[6];
+               while (!pi.isDone())
+               {
+                       int code = pi.currentSegment(coords);
+                       if (code == PathIterator.SEG_MOVETO)
+                       { v.add(new Point2D.Double(coords[0],coords[1])); }
+                       if (code == PathIterator.SEG_LINETO)
+                       { v.add(new Point2D.Double(coords[0],coords[1])); }                     
+                       pi.next();
+               }
+               double[] xtab = new double[v.size()];
+               double[] ytab = new double[v.size()];
+               for(int i=0;i<v.size();i++)
+               {
+                       xtab[i] = v.get(i).x;
+                       ytab[i] = v.get(i).y;
+               }
+               fillPolygon(xtab,ytab, color);
+       }
+
+       
+       public void fillPolygon(double[] xtab, double[] ytab, 
+                       Color color) {
+               if (xtab.length == ytab.length) {
+                       Point2D.Double points[] = new Point2D.Double[xtab.length];
+                       for (int i = 0; i < xtab.length; i++) {
+                               points[i] = new Point2D.Double(xtab[i], ytab[i]);
+                               updateBoundingBox(xtab[i], ytab[i]);
+                       }
+                       _commands.add(new FillPolygonCommand(points, color));
+               }
+       }
+
+       public void setFont(int font, double size) {
+               _fontsize = size;
+               _font = font;
+               _commands.add(new FontCommand(font, size));
+       }
+
+       public void setScale(double sc) {
+               _scale = sc;
+       }
+
+       public double getScale() {
+               return _scale;
+       }
+
+       public Rectangle2D.Double getBoundingBox() {
+               return (new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax
+                               - _ymin));
+       }
+
+       private Point2D.Double transform(Point2D.Double p, double factor,
+                       double dx, double dy) {
+               return transform(p.x, p.y, factor, dx, dy);
+       }
+
+       private Point2D.Double transform(double x, double y, double factor,
+                       double dx, double dy) {
+
+               return new Point2D.Double((x + dx) * factor, (y + dy) * factor);
+       }
+
+       public String export() {
+               Rectangle2D.Double oldbb = getBoundingBox();
+               double dx = -oldbb.x;
+               double dy = -oldbb.y;
+               Rectangle2D.Double nbb = new Rectangle2D.Double(0, 0, oldbb.width* _scale, oldbb.height * _scale);
+               StringBuffer buf = new StringBuffer();
+               buf.append(headerS(nbb));
+               if (_backgroundColor!= null)
+               {
+                       double w = oldbb.width* _scale;
+                       double h = oldbb.height* _scale;
+                       Point2D.Double[] tab = new      Point2D.Double[4];
+                       tab[0] = new Point2D.Double(0,0);
+                       tab[1] = new Point2D.Double(w,0);
+                       tab[2] = new Point2D.Double(w,h);
+                       tab[3] = new Point2D.Double(0,h);
+                   buf.append(this.fillPolygonS(tab, _backgroundColor));
+               }
+               for (int i = 0; i < _commands.size(); i++) {
+                       GraphicElement ge = _commands.elementAt(i);
+                       if (ge instanceof LineCommand) {
+                               LineCommand c = (LineCommand) ge;
+                               String tmp = drawLineS(transform(c.get_orig(), _scale, dx, dy),
+                                               transform(c.get_dest(), _scale, dx, dy), c
+                                                               .get_thickness());
+                               buf.append(tmp);
+                       } else if (ge instanceof TextCommand) {
+                               TextCommand c = (TextCommand) ge;
+                               String tmp = drawTextS(transform(c.get_base(), _scale, dx, dy),
+                                               c.get_txt());
+                               buf.append(tmp);
+                       } else if (ge instanceof RectangleCommand) {
+                               RectangleCommand c = (RectangleCommand) ge;
+                               String tmp = drawRectangleS(transform(c.get_orig(), _scale, dx,
+                                               dy), transform(c.get_dims(), _scale, 0.0, 0.0), c
+                                               .get_thickness());
+                               buf.append(tmp);
+                       } else if (ge instanceof CircleCommand) {
+                               CircleCommand c = (CircleCommand) ge;
+                               String tmp = drawCircleS(
+                                               transform(c.get_base(), _scale, dx, dy), c.get_radius()
+                                                               * _scale, c.get_thickness());
+                               buf.append(tmp);
+                       } else if (ge instanceof FillCircleCommand) {
+                               FillCircleCommand c = (FillCircleCommand) ge;
+                               String tmp = fillCircleS(
+                                               transform(c.get_base(), _scale, dx, dy), c.get_radius()
+                                                               * _scale, c.get_thickness(), c.get_color());
+                               buf.append(tmp);
+                       } else if (ge instanceof FontCommand) {
+                               FontCommand c = (FontCommand) ge;
+                               String tmp = setFontS(c.get_font(), c.get_size());
+                               buf.append(tmp);
+                       } else if (ge instanceof ColorCommand) {
+                               ColorCommand c = (ColorCommand) ge;
+                               String tmp = setColorS(c.getColor());
+                               buf.append(tmp);
+                       } else if (ge instanceof ArcCommand) {                          
+                               ArcCommand c = (ArcCommand) ge;
+                               String tmp = drawArcS(
+                                               transform(c.getCenter(), _scale, dx, dy), c.getWidth()
+                                                               * _scale, c.getHeight() * _scale, c
+                                                               .getStartAngle(), c.getEndAngle());
+                               buf.append(tmp);
+                       } else if (ge instanceof PolygonCommand) {
+                               PolygonCommand c = (PolygonCommand) ge;
+                               Point2D.Double[] points = c.get_points();
+                               for (int j = 0; j < points.length; j++) {
+                                       points[j] = transform(points[j], _scale, dx, dy);
+                               }
+                               String tmp = drawPolygonS(points, c.get_thickness());
+                               buf.append(tmp);
+                       } else if (ge instanceof FillPolygonCommand) {
+                               FillPolygonCommand c = (FillPolygonCommand) ge;
+                               Point2D.Double[] points = c.get_points();
+                               for (int j = 0; j < points.length; j++) {
+                                       points[j] = transform(points[j], _scale, dx, dy);
+                               }
+                               String tmp = fillPolygonS(points, c
+                                               .get_color());
+                               buf.append(tmp);
+                       }
+               }
+               buf.append(footerS());
+               return buf.toString();
+       }
+
+       public void reset() {
+
+       }
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/SecStrProducerGraphics.java b/srcjar/fr/orsay/lri/varna/models/export/SecStrProducerGraphics.java
new file mode 100644 (file)
index 0000000..6f61ffc
--- /dev/null
@@ -0,0 +1,119 @@
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class SecStrProducerGraphics implements VueVARNAGraphics{
+       SecStrDrawingProducer _ss;
+       double _thickness;
+       Color _color;
+       
+       public SecStrProducerGraphics(SecStrDrawingProducer ss)
+       {
+               _ss = ss;
+       }
+
+       public void draw(GeneralPath s) {
+                       _ss.fillPolygon(s, getColor());
+       }
+       
+       public void drawArc(double x, double y, double rx, double ry,
+                       double angleStart, double angleEnd) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       public void drawLine(double x1, double y1, double x2, double y2) {
+               _ss.drawLine(x1, -y1, x2, -y2, _thickness);
+       }
+       
+       public void drawCircle(double x, double y, double r) {
+               _ss.drawCircle(x+0.5*r, -y-0.5*r, 0.5*r, _thickness);
+       }
+
+       public void drawRect(double x, double y, double w, double h) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       public void drawRoundRect(double x, double y, double w, double h,
+                       double rx, double ry) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void drawStringCentered(String res, double x, double y) {
+               _ss.drawText(x, -y, res);
+       }
+
+       public void fill(GeneralPath s) {
+               _ss.fillPolygon(s, getColor());
+       }
+       
+       public void fillCircle(double x, double y, double r) {
+               _ss.fillCircle(x+0.5*r, -y-0.5*r, 0.5*r,  _thickness, _ss.getCurrentColor());
+       }
+
+       public void fillRect(double x, double y, double w, double h) {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void fillRoundRect(double x, double y, double w, double h,
+                       double rx, double ry) {
+               // TODO Auto-generated method stub
+               
+       }
+       public Color getColor() {
+               return _ss.getCurrentColor();
+       }
+       
+       public Dimension getStringDimension(String s) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       public void setColor(Color c) {
+               _ss.setColor(c);
+       }
+       
+       public void setSelectionStroke() {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       public void setFont(Font f) {
+               //System.out.println("Font "+f.getSize2D());
+               _ss.setFont(_ss.FONT_HELVETICA_BOLD,f.getSize2D());
+       }
+
+       public void setPlainStroke() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       public void setStrokeThickness(double t) {
+               _thickness = t;
+       }
+       
+       public void saveToDisk(String path) throws ExceptionWritingForbidden
+       {
+               FileWriter fout;
+               try {
+                       fout = new FileWriter(path);
+                       fout.write(_ss.export());
+                       fout.close();
+               } catch (IOException e) {
+                       throw new ExceptionWritingForbidden(e.getMessage());
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/SwingGraphics.java b/srcjar/fr/orsay/lri/varna/models/export/SwingGraphics.java
new file mode 100644 (file)
index 0000000..a4bba5c
--- /dev/null
@@ -0,0 +1,135 @@
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Arc2D.Double;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Rectangle2D;
+
+public class SwingGraphics implements VueVARNAGraphics {
+       private BasicStroke _dashedStroke;
+       private BasicStroke _plainStroke;
+       Graphics2D _g2d;
+       private boolean _debug = false;
+
+       
+       public SwingGraphics(Graphics2D g2d)
+       {
+               _g2d = g2d;
+               float[] dash = { 5.0f, 5.0f };
+               _dashedStroke = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,    BasicStroke.JOIN_ROUND, 3.0f, dash, 0);
+               _plainStroke = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,     BasicStroke.JOIN_ROUND, 3.0f);
+       }
+       
+       public Dimension getStringDimension(String s) {
+               FontMetrics fm = _g2d.getFontMetrics();
+               Rectangle2D r = fm.getStringBounds(s, _g2d);
+               return (new Dimension((int) r.getWidth(), (int) fm.getAscent()
+                               - fm.getDescent()));
+       }
+
+       public void drawStringCentered(String res, double x,
+                       double y) {
+               Dimension d = getStringDimension(res);
+               x -= (double) d.width / 2.0;
+               y += (double) d.height / 2.0;
+               if (_debug)
+               {
+                   Stroke bck = _g2d.getStroke();
+                   _g2d.setStroke(_plainStroke);
+                   _g2d.draw(new Rectangle2D.Double(x, y - d.height, d.width, d.height));
+                   _g2d.setStroke(bck);
+               }
+               _g2d.drawString(res, (float) (x), (float) (y));
+       }
+
+       public void draw(GeneralPath s) {
+               _g2d.draw(s);
+       }
+
+       public void drawArc(double x, double y, double rx, double ry,
+                       double angleStart, double angleEnd) {
+               _g2d.drawArc((int) (x-rx/2.), (int) (y-ry/2.), (int) rx, (int) ry, (int) angleStart, (int) angleEnd);   
+       }
+
+       public void drawLine(double x1, double y1, double x2, double y2) {
+               _g2d.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
+       }
+
+       public void drawCircle(double x, double y, double r) {
+               _g2d.draw(new Ellipse2D.Double(x, y, r, r));
+       }
+
+       public void drawRect(double x, double y, double w, double h) {
+               _g2d.drawRect((int)x, (int)y, (int)w, (int)h);
+       }
+
+       public void drawRoundRect(double x, double y, double w, double h,
+                       double rx, double ry) {
+               _g2d.drawRoundRect((int)x, (int)y, (int)w, (int)h, (int)rx, (int)ry);
+       }
+
+       public void drawString(String s, double x, double y) {
+               _g2d.drawString(s, (float)x, (float)y);
+       }
+
+       public void fill(GeneralPath s) {
+               _g2d.fill(s);
+       }
+
+       public void fillCircle(double x, double y, double r) {
+               _g2d.fill(new Ellipse2D.Double(x, y, r, r));
+       }
+
+       public void fillRect(double x, double y, double w, double h) {
+               _g2d.fill(new Rectangle2D.Double(x, y, w, h));
+       }
+
+       public void fillRoundRect(double x, double y, double w, double h,
+                       double rx, double ry) {
+               _g2d.fillRoundRect((int)x, (int)y, (int)w, (int)h, (int)rx, (int)ry);
+       }
+
+       public Color getColor() {
+               return _g2d.getColor();
+       }
+
+       public void setColor(Color c) {
+               _g2d.setColor(c);
+       }
+
+       public void setSelectionStroke() {
+               _g2d.setStroke(_dashedStroke);
+       }
+
+       public void setFont(Font f) {
+               _g2d.setFont(f);
+       }
+
+       public void setPlainStroke() {
+               _g2d.setStroke(_plainStroke);
+       }
+       
+       private BasicStroke deriveStroke(BasicStroke s, double t)
+       {
+               return new BasicStroke((float)t, s.getEndCap(), s.getLineJoin(), s.getMiterLimit(), s.getDashArray(), s.getDashPhase()) ;
+       }
+
+       public void setStrokeThickness(double t) {
+               boolean dashed = (_g2d.getStroke()==_dashedStroke); 
+               _plainStroke = deriveStroke(_plainStroke, t);
+               //_dashedStroke = deriveStroke(_dashedStroke, t);
+               if(dashed)
+               {  _g2d.setStroke(_dashedStroke); }
+               else
+               { _g2d.setStroke(_plainStroke); }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/TextCommand.java b/srcjar/fr/orsay/lri/varna/models/export/TextCommand.java
new file mode 100644 (file)
index 0000000..daf05e8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.geom.Point2D;
+
+public class TextCommand extends GraphicElement {
+       private Point2D.Double _base;
+       private String _txt;
+
+       public TextCommand(Point2D.Double base, String txt) {
+               _base = base;
+               _txt = txt;
+       }
+
+       public Point2D.Double get_base() {
+               return _base;
+       }
+
+       public String get_txt() {
+               return _txt;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/TikzExport.java b/srcjar/fr/orsay/lri/varna/models/export/TikzExport.java
new file mode 100644 (file)
index 0000000..d98f316
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D.Double;
+import java.util.Hashtable;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public class TikzExport extends SecStrDrawingProducer {
+
+
+       public TikzExport()
+       {
+               super();
+               super.setScale(0.2);
+       }
+       
+
+       private String TikzHeader() {
+               return   "\\documentclass[tikz,border=10pt]{standalone}\n"
+                               +"\\usepackage{tikz,relsize}\n"
+                               +"\\usetikzlibrary{positioning}\n"
+                               +"\\begin{document}\n"
+                               +"\\begin{tikzpicture}[inner sep=0, fill=none,draw=none,text=none,font={\\sf}]\n";
+       }
+
+       private String formatPoint(Point2D.Double p)
+       {
+               return formatPoint(p.x,p.y);
+       }
+
+       private String formatPoint(double x, double y)
+       {
+               return "("+(super.getScale()*x)+","+(super.getScale()*y)+")";
+       }
+
+       public String drawCircleS(Point2D.Double p, double radius, double thickness) {
+               return "  \\draw[draw=currColor] "+formatPoint(p)+" circle ("+(radius*super.getScale())+");\n";
+       }
+
+       public String fillCircleS(Point2D.Double p, double radius,
+                       double thickness, Color col) {
+               return setColorS(col)
+                       +"  \\fill[fill=currColor] "+formatPoint(p)+" circle ("+(radius*super.getScale())+");\n";
+       }
+
+
+       public String drawLineS(Point2D.Double p0, Point2D.Double p1,
+                       double thickness) {
+               return "  \\draw[draw=currColor] "+formatPoint(p0)+" -- "+formatPoint(p1)+";\n";
+       }
+
+       public String drawRectangleS(Point2D.Double p, Point2D.Double dims,
+                       double thickness) {
+               return "  \\draw[draw=currColor] "+formatPoint(p)+" -- "+formatPoint(p.x+dims.x,p.y)+" -- "+formatPoint(p.x+dims.x,p.y+dims.y)+" -- "+formatPoint(p.x,p.y+dims.y)+" -- "+formatPoint(p)+";\n";
+       }
+
+       public String drawTextS(Point2D.Double p, String txt) {
+               return "  \\node[text=currColor] at "+formatPoint(p)+" {"+txt+"};\n";
+       }
+
+       public String setFontS(int font, double size) {
+               _font = font;
+               _fontsize = 1.2 * size;
+               return "";
+       }
+
+       public String setColorS(Color col) {
+               super.setColorS(col);
+               return "\\definecolor{currColor}{rgb}{"+(((double)col.getRed())/255.)+","+(((double)col.getGreen())/255.)+","+(((double)col.getBlue())/255.)+"}\n";
+       }
+
+       public String footerS() {
+               return "\\end{tikzpicture}\n"
+                       +  "\\end{document}";
+       }
+
+       public String headerS(Rectangle2D.Double bb) {
+               return TikzHeader();
+       }
+
+       @Override
+       public String drawArcS(Point2D.Double origine, double width, double height,
+                       double startAngle, double endAngle) {
+               return "";
+
+       }
+
+       @Override
+       public String drawPolygonS(Double[] points, double thickness) {
+               if (points.length > 0) {
+                       String result = "\\draw[draw=currColor] ";
+                       for (int i = 0; i < points.length; i++) {
+                               result += ""+formatPoint(points[i])+" -- ";
+                       }
+                       result += ""+formatPoint(points[0])+";";
+                       return result;
+               } else {
+                       return "";
+               }
+       }
+
+       @Override
+       public String fillPolygonS(Double[] points,  Color col) {
+               if (points.length > 0) {
+                       String result = "\\fill[fill=currColor] ";
+                       for (int i = 0; i < points.length; i++) {
+                               result += ""+formatPoint(points[i])+" -- ";
+                       }
+                       result += ""+formatPoint(points[0])+";";
+                       return setColorS(col)
+                                       +result;
+               } else {
+                       return setColorS(col);
+               }
+       }
+       
+       @Override
+       public String drawBaseStartS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBaseEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairStartS(int i, int j, ModeleBP bps) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneStartS(int i, int j) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneEndS(int index) {
+               return "";
+       }
+
+
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/export/VueVARNAGraphics.java b/srcjar/fr/orsay/lri/varna/models/export/VueVARNAGraphics.java
new file mode 100644 (file)
index 0000000..7737abf
--- /dev/null
@@ -0,0 +1,31 @@
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.GeneralPath;
+
+public interface VueVARNAGraphics {
+       public Dimension getStringDimension(String s);
+       public void drawStringCentered(String res, double x, double y);
+       public void setColor(Color c);
+       public Color getColor();
+       public void drawLine(double x1, double y1, double x2, double y2);
+       public void drawRect(double x, double y, double w, double h);
+       public void fillRect(double x, double y, double w, double h);
+       public void drawCircle(double x, double y, double r);
+       public void fillCircle(double x, double y, double r);
+       public void drawRoundRect(double x, double y, double w, double h, double rx, double ry); 
+       public void fillRoundRect(double x, double y, double w, double h, double rx, double ry); 
+       public void drawArc(double x, double y, double rx, double ry, double angleStart, double angleEnd);
+       //public void drawString(String s, double x, double y);
+       public void draw(GeneralPath s);
+       public void fill(GeneralPath s);
+       public void setFont(Font f);
+       public void setSelectionStroke();
+       public void setPlainStroke();
+       public void setStrokeThickness(double t);
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/export/XFIGExport.java b/srcjar/fr/orsay/lri/varna/models/export/XFIGExport.java
new file mode 100644 (file)
index 0000000..179a3ac
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.export;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D.Double;
+import java.util.Hashtable;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public class XFIGExport extends SecStrDrawingProducer {
+
+       private int _font = SecStrDrawingProducer.FONT_TIMES_ROMAN;
+       @SuppressWarnings("unused")
+       private StringBuffer buf = new StringBuffer();
+
+       private Hashtable<Color, Integer> _definedCols = new Hashtable<Color, Integer>();
+
+       // From XFig 3.2 file format, indexed RGB colors are in the range [32,543]
+       private int _nextColCode = 32;
+       private final static int UPPER_BOUND_COLOR_CODE = 543;
+
+       public XFIGExport()
+       {
+               super();
+               super.setScale(20.0);
+       }
+       
+       
+       private String ensureColorDefinition(Color col) {
+               if (!_definedCols.containsKey(col)) {
+                       if (_nextColCode < UPPER_BOUND_COLOR_CODE) {
+                               int curColorCode = _nextColCode;
+                               _definedCols.put(col, curColorCode);
+                               _nextColCode++;
+
+                               String RGBR = Integer.toHexString(col.getRed());
+                               if (RGBR.length() < 2) {
+                                       RGBR = "0" + RGBR;
+                               }
+                               String RGBG = Integer.toHexString(col.getGreen());
+                               if (RGBG.length() < 2) {
+                                       RGBG = "0" + RGBG;
+                               }
+                               String RGBB = Integer.toHexString(col.getBlue());
+                               if (RGBB.length() < 2) {
+                                       RGBB = "0" + RGBB;
+                               }
+                               String RGBHex = "#" + RGBR + RGBG + RGBB;
+                               RGBHex = RGBHex.toUpperCase();
+                               return "0 " + curColorCode + " " + RGBHex + "\n";
+                       }
+               }
+               return "";
+       }
+
+       private int getColorCode(Color col) {
+               if (_definedCols.containsKey(col)) {
+                       return _definedCols.get(col);
+               }
+               return 0;
+       }
+
+       private int getCurColorCode() {
+               if (_definedCols.containsKey(_curColor)) {
+                       return _definedCols.get(_curColor);
+               }
+               return 0;
+       }
+
+       private String XFIGHeader() {
+               return "#FIG 3.2\n" + "Landscape\n" + "Center\n" + "Inches\n"
+                               + "Letter  \n" + "100.00\n" + "Single\n" + "-2\n" + "1200 2\n";
+       }
+
+       public String drawCircleS(Point2D.Double p, double radius, double thickness) {
+               return ("1 3 0 " + (long) thickness + " " + getCurColorCode()
+                               + " 7 50 -1 -1 0.000 1 0.0000 " + (long) p.x + " "
+                               + (long) -p.y + " " + (long) radius + " " + (long) radius + " 1 1 1 1\n");
+       }
+
+       public String drawLineS(Point2D.Double p0, Point2D.Double p1,
+                       double thickness) {
+               return ("2 1 0 " + (long) thickness + " " + getCurColorCode()
+                               + " 7 60 -1 -1 0.000 0 0 -1 0 0 2\n" + " " + (long) p0.x + " "
+                               + (long) -p0.y + " " + (long) p1.x + " " + (long) -p1.y + "\n");
+       }
+
+       public String drawRectangleS(Point2D.Double p, Point2D.Double dims,
+                       double thickness) {
+               return ("2 2 0 " + (long) thickness + " " + getCurColorCode()
+                               + " 7 50 -1 -1 0.000 0 0 -1 0 0 5\n" + "\t " + (long) (p.x)
+                               + " " + (long) (-p.y) + " " + (long) (p.x + dims.x) + " "
+                               + (long) (-p.y) + " " + (long) (p.x + dims.x) + " "
+                               + (long) -(p.y + dims.y) + " " + (long) (p.x) + " "
+                               + (long) -(p.y + dims.y) + " " + (long) (p.x) + " "
+                               + (long) -(p.y) + "\n");
+       }
+
+       public String drawTextS(Point2D.Double p, String txt) {
+               return ("4 1 " + getCurColorCode() + " 40 -1 " + _font + " "
+                               + (long) _fontsize + " 0.0000 6 " + (long) 4 * _fontsize + " "
+                               + (long) (2 * _fontsize) + " " + (long) (p.x) + " "
+                               + (long) -(p.y - 6 * _fontsize) + " " + txt + "\\001\n");
+       }
+
+       public String fillCircleS(Point2D.Double p, double radius,
+                       double thickness, Color col) {
+               String coldef = ensureColorDefinition(col);
+               return (coldef + "1 3 0 " + (long) thickness + " 0 "
+                               + getColorCode(col) + " 50 0 20 0.000 1 0.0000 " + (long) p.x
+                               + " " + (long) -p.y + " " + (long) radius + " " + (long) radius + " 1 1 1 1\n");
+       }
+
+       public String setFontS(int font, double size) {
+               _font = font;
+               _fontsize = 1.2 * size;
+               return "";
+       }
+
+       public String setColorS(Color col) {
+               super.setColorS(col);
+               return (ensureColorDefinition(col));
+       }
+
+       public String footerS() {
+               return "";
+       }
+
+       public String headerS(Rectangle2D.Double bb) {
+               return XFIGHeader();
+       }
+
+       @Override
+       public String drawArcS(Point2D.Double origine, double width, double height,
+                       double startAngle, double endAngle) {
+               double p1x = origine.x;
+               double p1y = -origine.y;
+               double p2x = origine.x + width / 2.0;
+               double p2y = -origine.y - height / 2.0;
+               double p3x = origine.x + width;
+               double p3y = p1y;
+               double cx = (p1x + p3x) / 2.0;
+               double cy = p3y + height / 2.0;
+               return ("5 1 0 1 " + getCurColorCode() + " 7 50 0 -1 4.000 0 0 0 0 "
+                               + cx + " " + cy + " " + (int) p1x + " " + (int) p1y + " "
+                               + (int) p2x + " " + (int) p2y + " " + (int) p3x + " "
+                               + (int) p3y + "\n");
+
+       }
+
+       @Override
+       public String drawPolygonS(Double[] points, double thickness) {
+               if (points.length > 0) {
+                       String result = "2 3 0 1 " + getCurColorCode()
+                                       + " 7 40 0 -1 4.000 0 0 0 0 0 " + (points.length + 1)
+                                       + "\n";
+                       for (int i = 0; i < points.length; i++) {
+                               result += (int) Math.round(points[i].x) + " "
+                                               + (int) Math.round(-points[i].y) + " ";
+                       }
+                       result += (int) Math.round(points[0].x) + " "
+                                       + (int) Math.round(-points[0].y) + " ";
+                       result += "\n";
+                       return result;
+               } else {
+                       return "";
+               }
+       }
+
+       @Override
+       public String fillPolygonS(Double[] points,  Color col) {
+               if (points.length > 0) {
+                       String coldef = ensureColorDefinition(col);
+                       String result = "2 3 0 1 0 " + getColorCode(col)
+                                       + " 35 0 0 4.000 0 0 0 0 0 " + (points.length + 1) + "\n";
+                       for (int i = 0; i < points.length; i++) {
+                               result += (int) Math.round(points[i].x) + " "
+                                               + (int) Math.round(-points[i].y) + " ";
+                       }
+                       result += (int) Math.round(points[0].x) + " "
+                                       + (int) Math.round(-points[0].y) + " ";
+                       result += "\n";
+                       return coldef + result;
+               } else {
+                       return "";
+               }
+       }
+
+       @Override
+       public String drawBaseStartS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBaseEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairStartS(int i, int j, ModeleBP bps) {
+               return "";
+       }
+
+       @Override
+       public String drawBasePairEndS(int index) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneStartS(int i, int j) {
+               return "";
+       }
+
+       @Override
+       public String drawBackboneEndS(int index) {
+               return "";
+       }
+
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/ComputeArcCenter.java b/srcjar/fr/orsay/lri/varna/models/geom/ComputeArcCenter.java
new file mode 100644 (file)
index 0000000..0aa8ebf
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.geom;
+
+public class ComputeArcCenter {
+       
+       /**
+        * Given an arc length (l) and segment length (delta) of the arc,
+        * find where to put the center, returned as a position of the perpendicular
+        * bisector of the segment. The positive side is the one where the arc is drawn.
+        * It works using Newton's method.
+        */
+       public static double computeArcCenter(double delta, double l) {
+               double x_n = 0;
+               double x_n_plus_1, f_x_n, f_x_n_plus_1;
+               f_x_n = f(x_n,delta);
+               while (true) {
+                       x_n_plus_1 = x_n - (f_x_n - l)/fprime(x_n,delta);
+                       f_x_n_plus_1 = f(x_n_plus_1,delta);
+                       // We want a precision of 0.1 on arc length
+                       if (x_n_plus_1 == Double.NEGATIVE_INFINITY || Math.abs(f_x_n_plus_1 - f_x_n) < 0.1) {
+                               //System.out.println("computeArcCenter: steps = " + steps + "    result = " + x_n_plus_1);
+                               return x_n_plus_1;
+                       }
+                       x_n = x_n_plus_1;
+                       f_x_n = f_x_n_plus_1;
+               }
+       }
+       
+       private static double f(double c, double delta) {
+               if (c < 0) {
+                       return 2*Math.atan(delta/(-2*c)) * Math.sqrt(delta*delta/4 + c*c);
+               } else if (c != 0) { // c > 0
+                       return (2*Math.PI - 2*Math.atan(delta/(2*c))) * Math.sqrt(delta*delta/4 + c*c);
+               } else { // c == 0
+                       return Math.PI * Math.sqrt(delta*delta/4 + c*c);
+               }
+       }
+       
+       /**
+        * d/dc f(c,delta)
+        */
+       private static double fprime(double c, double delta) {
+               if (c < 0) {
+                       return delta/(c*c + delta/4)*Math.sqrt(delta*delta/4 + c*c) + 2*Math.atan(delta/(-2*c))*c/Math.sqrt(delta*delta/4 + c*c);
+               } else if (c != 0) { // c > 0
+                       return delta/(c*c + delta/4)*Math.sqrt(delta*delta/4 + c*c) + (2*Math.PI - 2*Math.atan(delta/(-2*c)))*c/Math.sqrt(delta*delta/4 + c*c);
+               } else { // c == 0
+                       return 2;
+               }
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/ComputeEllipseAxis.java b/srcjar/fr/orsay/lri/varna/models/geom/ComputeEllipseAxis.java
new file mode 100644 (file)
index 0000000..b8d699b
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.geom;
+
+public class ComputeEllipseAxis {
+       public static boolean debug = false;
+       
+       /**
+        * Given one axis half-length b and the circumference l,
+        * find the other axis half-length a.
+        * Returns 0 if there is no solution.
+        * Implemented with Newton's method.
+        */
+       public static double computeEllipseAxis(double b, double l) {
+               if (l/4 <= b || b <= 0 || l <= 0) {
+                       // No such ellipse can exist.
+                       return 0;
+               } else {
+                       int steps = 0;
+                       double x_n = 10;
+                       double x_n_plus_1, f_x_n, f_x_n_plus_1;
+                       f_x_n = f(x_n,b) - l;
+                       while (true) {
+                               //System.out.println("x_n = " + x_n + "  f(x_n)=" + f_x_n);
+                               x_n_plus_1 = x_n - f_x_n/fprime(x_n,b);
+                               f_x_n_plus_1 = f(x_n_plus_1,b) - l;
+                               if (x_n_plus_1 < 0) {
+                                       System.out.println("ComputeEllipseAxis: x_n < 0 => returning 0");
+                                       return 0;
+                               }
+                               // We want a precision of 0.01 on arc length
+                               if (Math.abs(f_x_n_plus_1 - f_x_n) < 0.01) {
+                                       if (debug) System.out.println("#steps = " + steps);
+                                       return x_n_plus_1;
+                               }
+                               x_n = x_n_plus_1;
+                               f_x_n = f_x_n_plus_1;
+                               steps++;
+                       }
+               }
+       }
+       
+       
+       
+       private static double f(double a, double b) {
+               // This is Ramanujan's approximation of an ellipse circumference
+               // Nice because it is fast to compute (no need to compute an integral)
+               // and the derivative is also simple (and fast to compute).
+               return Math.PI*(3*(a+b) - Math.sqrt(10*a*b + 3*(a*a + b*b)));
+       }
+       
+       private static double fprime(double a, double b) {
+               return Math.PI*(3 - (5*b + 3*a)/Math.sqrt(10*a*b + 3*(a*a + b*b)));
+       }
+       
+       
+       /*
+       private static void test(double a, double b, double l) {
+               double a2 = computeEllipseAxis(b, l);
+               System.out.println("true a=" + a + " l=" + l + "   estimated a=" + a2 + " l(from true a)=" + f(a,b));
+       }
+       
+       private static void test(double b, double l) {
+               double a2 = computeEllipseAxis(b, l);
+               System.out.println("true l=" + l + "   estimated a=" + a2);
+       }
+       
+       
+       public static void main(String[] args) {
+               double b = 4;
+               test(100, b, 401.3143);
+               test(7, b, 35.20316);
+               test(4, b, 25.13274);
+               test(1, b, 17.15684);
+               test(0.5, b, 16.37248);
+               test(0.25, b, 16.11448);
+               test(0.1, b, 16.02288);
+               test(0.01, b, 16.00034);
+               test(0, b, 16);
+               test(10000, b, 40000.03);
+       }
+       */
+       
+       /*
+       public static void main(String[] args) {
+               test(222.89291150684895, 2240);
+       }
+       */
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/CubicBezierCurve.java b/srcjar/fr/orsay/lri/varna/models/geom/CubicBezierCurve.java
new file mode 100644 (file)
index 0000000..7ff9aaf
--- /dev/null
@@ -0,0 +1,205 @@
+package fr.orsay.lri.varna.models.geom;
+
+
+import java.awt.geom.Point2D;
+
+/**
+ * This class implements a cubic Bezier curve
+ * with a constant speed parametrization.
+ * The Bezier curve is approximated by a sequence of n straight lines,
+ * where the n+1 points between the lines are
+ * { B(k/n), k=0,1,...,n } where B is the standard
+ * parametrization given here:
+ * http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves
+ * You can then use the constant speed parametrization over this sequence
+ * of straight lines.
+ * 
+ * @author Raphael Champeimont
+ */
+public class CubicBezierCurve {
+       
+       /**
+        * The four points defining the curve.
+        */
+       private Point2D.Double P0, P1, P2, P3;
+       
+
+       
+       private int n;
+       /**
+        * The number of lines approximating the Bezier curve.
+        */
+       public int getN() {
+               return n;
+       }
+       
+       
+       /**
+        * Get the (exact) length of the approximation curve.
+        */
+       public double getApproxCurveLength() {
+               return lengths[n-1];
+       }
+       
+       
+       
+       /**
+        * The n+1 points between the n lines.
+        */
+       private Point2D.Double[] points;
+       
+       
+       
+       /**
+        * Array of length n.
+        * lengths[i] is the sum of lengths of lines up to and including the
+        * line starting at point points[i]. 
+        */
+       private double[] lengths;
+       
+       
+       /**
+        * Array of length n.
+        * The vectors along each line, with a norm of 1.
+        */
+       private Point2D.Double[] unitVectors; 
+       
+       
+       
+       /**
+        * The standard exact cubic Bezier curve parametrization.
+        * Argument t must be in [0,1].
+        */
+       public Point2D.Double standardParam(double t) {
+               double x = Math.pow(1-t,3) * P0.x
+                 + 3 * Math.pow(1-t,2) * t * P1.x
+                 + 3 * (1-t) * t * t * P2.x
+                 + t * t * t * P3.x;
+               double y = Math.pow(1-t,3) * P0.y
+                 + 3 * Math.pow(1-t,2) * t * P1.y
+                 + 3 * (1-t) * t * t * P2.y
+                 + t * t * t * P3.y;
+               return new Point2D.Double(x, y);
+       }
+       
+       
+       
+
+       
+       /**
+        * Uniform approximated parameterization.
+        * A value in t must be in [0, getApproxCurveLength()].
+        * We have built a function f such that f(t) is the position of
+        * the point on the approximation curve (n straight lines).
+        * The interesting property is that the length of the curve
+        * { f(t), t in [0,l] } is exactly l.
+        * The java function is simply the application of f over each element
+        * of a sorted array, ie. uniformParam(t)[k] = f(t[k]).
+        * Computation time is O(n+m) where n is the number of lines in which
+        * the curve is divided and m is the length of the array given as an
+        * argument. The use of a sorted array instead of m calls to the
+        * function enables us to have a complexity of O(n+m) instead of O(n*m)
+        * because we don't need to search in all the n possible lines for
+        * each value in t (as we know their are in increasing order).
+        */
+       public Point2D.Double[] uniformParam(double[] t) {
+               int m = t.length;
+               Point2D.Double[] result = new Point2D.Double[m];
+               int line = 0;
+               for (int i=0; i<m; i++) {
+                       while ((line<n) && (lengths[line] < t[i])) {
+                               line++;
+                       }
+                       if (line >= n) {
+                               // In theory should not happen, but float computation != math.
+                               line = n-1;
+                       }
+                       if (t[i] < 0) {
+                               throw (new IllegalArgumentException("t[" + i + "] < 0"));
+                       }
+                       // So now we know on which line we are
+                       double lengthOnLine = t[i] - (line != 0 ? lengths[line-1] : 0);
+                       double x = points[line].x + unitVectors[line].x * lengthOnLine;
+                       double y = points[line].y + unitVectors[line].y * lengthOnLine;
+                       result[i] = new Point2D.Double(x, y);
+               }
+               return result;
+       }
+       
+       
+       
+       /**
+        * A Bezier curve can be defined by four points,
+        * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves
+        * Here we give this four points and a integer to say in how many
+        * line segments we want to cut the Bezier curve (if n is bigger 
+        * the computation takes longer but the precision is better).
+        * The number of lines must be at least 1.
+        */
+       public CubicBezierCurve(
+                       Point2D.Double P0,
+                       Point2D.Double P1,
+                       Point2D.Double P2,
+                       Point2D.Double P3,
+                       int n) {
+               this.P0 = P0;
+               this.P1 = P1;
+               this.P2 = P2;
+               this.P3 = P3;
+               this.n = n;
+               if (n < 1) {
+                       throw (new IllegalArgumentException("n must be at least 1"));
+               }
+               computeData();
+       }
+
+       
+       private void computeData() {
+               points = new Point2D.Double[n+1];
+               for (int k=0; k<=n; k++) {
+                       points[k] = standardParam(((double) k) / n);
+               }
+               
+               lengths = new double[n];
+               unitVectors = new Point2D.Double[n];
+               double sum = 0;
+               for (int i=0; i<n; i++) {
+                       double l = lineLength(points[i], points[i+1]);
+                       double dx = (points[i+1].x - points[i].x) / l;
+                       double dy = (points[i+1].y - points[i].y) / l;
+                       unitVectors[i] = new Point2D.Double(dx, dy);
+                       sum += l;
+                       lengths[i] = sum;
+               }
+               
+
+               
+       }
+       
+       
+       private double lineLength(Point2D.Double P1, Point2D.Double P2) {
+               return P2.distance(P1);
+       }
+       
+       
+       public Point2D.Double getP0() {
+               return P0;
+       }
+
+       public Point2D.Double getP1() {
+               return P1;
+       }
+
+       public Point2D.Double getP2() {
+               return P2;
+       }
+
+       public Point2D.Double getP3() {
+               return P3;
+       }
+
+
+
+
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/HalfEllipse.java b/srcjar/fr/orsay/lri/varna/models/geom/HalfEllipse.java
new file mode 100644 (file)
index 0000000..d4155df
--- /dev/null
@@ -0,0 +1,192 @@
+package fr.orsay.lri.varna.models.geom;
+
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+
+/**
+ * Ellipse, with axis = X and Y.
+ * This class is useful for constant speed parameterization
+ * (just like CubicBezierCurve).
+ * The ellipse drawn is in fact an half-ellipse, from 0 to PI.
+ * 
+ * @author Raphael Champeimont
+ */
+public class HalfEllipse {
+       
+       /**
+        * The four points defining the curve.
+        */
+       private double a, b;
+       
+
+       
+       private int n;
+       /**
+        * The number of lines approximating the curve.
+        */
+       public int getN() {
+               return n;
+       }
+       
+       
+       /**
+        * Get the (exact) length of the approximation curve.
+        */
+       public double getApproxCurveLength() {
+               return lengths[n-1];
+       }
+       
+       
+       
+       /**
+        * The n+1 points between the n lines.
+        */
+       private Point2D.Double[] points;
+       
+       
+       
+       /**
+        * Array of length n.
+        * lengths[i] is the sum of lengths of lines up to and including the
+        * line starting at point points[i]. 
+        */
+       private double[] lengths;
+       
+       
+       /**
+        * Array of length n.
+        * The vectors along each line, with a norm of 1.
+        */
+       private Point2D.Double[] unitVectors; 
+       
+       
+       
+       /**
+        * The standard ellipse parameterization.
+        * Argument t must be in [0,1].
+        */
+       public Point2D.Double standardParam(double t) {
+               double x = a*Math.cos(t*Math.PI);
+               double y = b*Math.sin(t*Math.PI);
+               return new Point2D.Double(x, y);
+       }
+       
+       
+       
+
+       
+       /**
+        * Uniform approximated parameterization.
+        * A value in t must be in [0, getApproxCurveLength()].
+        * We have built a function f such that f(t) is the position of
+        * the point on the approximation curve (n straight lines).
+        * The interesting property is that the length of the curve
+        * { f(t), t in [0,l] } is exactly l.
+        * The java function is simply the application of f over each element
+        * of a sorted array, ie. uniformParam(t)[k] = f(t[k]).
+        * Computation time is O(n+m) where n is the number of lines in which
+        * the curve is divided and m is the length of the array given as an
+        * argument. The use of a sorted array instead of m calls to the
+        * function enables us to have a complexity of O(n+m) instead of O(n*m)
+        * because we don't need to search in all the n possible lines for
+        * each value in t (as we know their are in increasing order).
+        */
+       public Point2D.Double[] uniformParam(double[] t) {
+               int m = t.length;
+               Point2D.Double[] result = new Point2D.Double[m];
+               int line = 0;
+               for (int i=0; i<m; i++) {
+                       while ((line<n) && (lengths[line] < t[i])) {
+                               line++;
+                       }
+                       if (line >= n) {
+                               // In theory should not happen, but float computation != math.
+                               line = n-1;
+                       }
+                       if (t[i] < 0) {
+                               throw (new IllegalArgumentException("t[" + i + "] < 0"));
+                       }
+                       // So now we know on which line we are
+                       double lengthOnLine = t[i] - (line != 0 ? lengths[line-1] : 0);
+                       double x = points[line].x + unitVectors[line].x * lengthOnLine;
+                       double y = points[line].y + unitVectors[line].y * lengthOnLine;
+                       result[i] = new Point2D.Double(x, y);
+               }
+               return result;
+       }
+       
+       
+       
+       /**
+        * An ellipse that has axis equal to X and Y axis needs only
+        * two numbers (half-axis lengths) to be defined.
+        * They are resp. a for X axis and b for Y axis.
+        * n = how many line segments we want to cut the curve
+        * (if n is bigger the computation takes longer but the precision is better).
+        * The number of lines must be at least 1.
+        */
+       public HalfEllipse(double a, double b, int n) {
+               this.a = a;
+               this.b = b;
+               this.n = n;
+               if (n < 1) {
+                       throw (new IllegalArgumentException("n must be at least 1"));
+               }
+               computeData();
+       }
+       
+       
+       /**
+        * Returns that affine transform that moves the ellipse
+        * given by this class such that its 0/pi axis matches P0-P1.
+        */
+       public static AffineTransform matchAxisA(Point2D.Double P0, Point2D.Double P1) {
+               double theta = MiscGeom.angleFromVector(P0.x-P1.x, P0.y-P1.y);
+               Point2D.Double mid = new Point2D.Double((P0.x+P1.x)/2, (P0.y+P1.y)/2);
+               AffineTransform transform = new AffineTransform();
+               transform.translate(mid.x, mid.y);
+               transform.rotate(theta);
+               return transform;
+       }
+
+       
+       private void computeData() {
+               points = new Point2D.Double[n+1];
+               for (int k=0; k<=n; k++) {
+                       points[k] = standardParam(((double) k) / n);
+               }
+               
+               lengths = new double[n];
+               unitVectors = new Point2D.Double[n];
+               double sum = 0;
+               for (int i=0; i<n; i++) {
+                       double l = lineLength(points[i], points[i+1]);
+                       double dx = (points[i+1].x - points[i].x) / l;
+                       double dy = (points[i+1].y - points[i].y) / l;
+                       unitVectors[i] = new Point2D.Double(dx, dy);
+                       sum += l;
+                       lengths[i] = sum;
+               }
+               
+
+               
+       }
+       
+       
+       private double lineLength(Point2D.Double P1, Point2D.Double P2) {
+               return P2.distance(P1);
+       }
+       
+       public double getA() {
+               return a;
+       }
+       
+       public double getB() {
+               return b;
+       }
+
+
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/LinesIntersect.java b/srcjar/fr/orsay/lri/varna/models/geom/LinesIntersect.java
new file mode 100644 (file)
index 0000000..fa238fe
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.geom;
+
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+
+/**
+ * Like Line2D.Double.linesIntersect() of the standard library, but without the bug!
+ * See this: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6457965
+ * This result is incorrect with, for example:
+ * System.out.println(Line2D.Double.linesIntersect(179.2690296114372, 1527.2309703885628, 150.9847583639753, 1498.946699141101, 94.4162158690515, 1442.378156646177, 66.1319446215896, 1414.0938853987152));
+ * (real example observed on an RNA with no intersection at all)
+ * This lines obviously don't intersect (both X and Y ranges are clearly disjoint!)
+ * but linesIntersect() returns true.
+ * Here we provide a bug-free function.
+ */
+public class LinesIntersect {
+       /**
+        * Returns whether segment from (x1,y1) to (x2,y2)
+        * intersect with segment from (x3,y3) to (x4,y4).
+        */
+       public static boolean linesIntersect(
+                       double x1,
+            double y1,
+            double x2,
+            double y2,
+            double x3,
+            double y3,
+            double x4,
+            double y4) {
+               
+             double denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+             if (denom == 0.0) { // Lines are parallel.
+                return false;
+             }
+             double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
+             double ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
+             return (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0);
+       }
+       
+       /**
+        * Returns whether segment from p1 to p2 intersects segment from p3 to p4.
+        */
+       public static boolean linesIntersect(
+                       Point2D.Double p1,
+                       Point2D.Double p2,
+                       Point2D.Double p3,
+                       Point2D.Double p4) {
+               return linesIntersect(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);
+       }
+       
+       /**
+        * Returns whether the two segment intersect.
+        */
+       public static boolean linesIntersect(Line2D.Double line1, Line2D.Double line2) {
+               return linesIntersect(
+                               line1.x1, line1.y1, line1.x2, line1.y2,
+                               line2.x1, line2.y1, line2.x2, line2.y2);
+       }
+       
+       
+       private static void test(
+                       double x1,
+            double y1,
+            double x2,
+            double y2,
+            double x3,
+            double y3,
+            double x4,
+            double y4,
+            boolean expectedResult) {
+               boolean a = linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4);
+               boolean b = Line2D.Double.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4);
+               System.out.println("ours says " + a + " which is " + (a == expectedResult ? "correct" : "INCORRECT") +
+                               " / Line2D.Double says " + b + " which is " + (b == expectedResult ? "correct" : "INCORRECT"));
+       }
+       
+       public static void main(String[] args) {
+               test(179.2690296114372, 1527.2309703885628, 150.9847583639753, 1498.946699141101, 94.4162158690515, 1442.378156646177, 66.1319446215896, 1414.0938853987152, false);
+               test(0, 0, 0, 0, 1, 1, 1, 1, false);
+               test(0, 0, 0.5, 0.5, 1, 1, 2, 2, false);
+               test(0, 0, 2, 2, 0, 2, 2, 0, true);
+               test(0, 0, 2, 2, 4, 0, 3, 2, false);
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/geom/MiscGeom.java b/srcjar/fr/orsay/lri/varna/models/geom/MiscGeom.java
new file mode 100644 (file)
index 0000000..ce3bc7d
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.geom;
+
+import java.awt.geom.Point2D;
+
+
+/**
+ * @author Raphael Champeimont
+ * Misc geometry functions.
+ */
+public class MiscGeom {
+
+       /**
+        * Compute the angle made by a vector.
+        */
+       public static double angleFromVector(Point2D.Double v) {
+               return MiscGeom.angleFromVector(v.x, v.y);
+       }
+
+       public static double angleFromVector(double x, double y) {
+               double l = Math.hypot(x, y);
+               if (y > 0) {
+                       return Math.acos(x / l);
+               } else if (y < 0) {
+                       return - Math.acos(x / l);
+               } else {
+                       return x > 0 ? 0 : Math.PI;
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/Base.java b/srcjar/fr/orsay/lri/varna/models/naView/Base.java
new file mode 100644 (file)
index 0000000..bb8e0f8
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+public class Base {
+       private int mate;
+       private double x, y;
+       private boolean extracted;
+       private Region region = new Region();
+
+       public int getMate() {
+               return mate;
+       }
+
+       public void setMate(int mate) {
+               this.mate = mate;
+       }
+
+       public double getX() {
+               return x;
+       }
+
+       public void setX(double x) {
+               //System.out.println("  Base.setX()");
+               this.x = x;
+       }
+
+       public double getY() {
+               return y;
+       }
+
+       public void setY(double y) {
+               this.y = y;
+       }
+
+       public boolean isExtracted() {
+               return extracted;
+       }
+
+       public void setExtracted(boolean extracted) {
+               this.extracted = extracted;
+       }
+
+       public Region getRegion() {
+               return region;
+       }
+
+       public void setRegion(Region region) {
+               this.region = region;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/Connection.java b/srcjar/fr/orsay/lri/varna/models/naView/Connection.java
new file mode 100644 (file)
index 0000000..bca820b
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+public class Connection {
+       private Loop loop = new Loop();
+       private Region region = new Region();
+       // Start and end form the 1st base pair of the region.
+       private int start, end;
+       private double xrad, yrad, angle;
+       // True if segment between this connection and the
+       // next must be extruded out of the circle
+       private boolean extruded;
+       // True if the extruded segment must be drawn long.
+       private boolean broken;
+       
+       private boolean _isNull=false;
+
+       public boolean isNull() {
+               return _isNull;
+       }
+
+       public void setNull(boolean isNull) {
+               _isNull = isNull;
+       }
+
+       
+       public Loop getLoop() {
+               return loop;
+       }
+
+       public void setLoop(Loop loop) {
+               this.loop = loop;
+       }
+
+       public Region getRegion() {
+               return region;
+       }
+
+       public void setRegion(Region region) {
+               this.region = region;
+       }
+
+       public int getStart() {
+               return start;
+       }
+
+       public void setStart(int start) {
+               this.start = start;
+       }
+
+       public int getEnd() {
+               return end;
+       }
+
+       public void setEnd(int end) {
+               this.end = end;
+       }
+
+       public double getXrad() {
+               return xrad;
+       }
+
+       public void setXrad(double xrad) {
+               this.xrad = xrad;
+       }
+
+       public double getYrad() {
+               return yrad;
+       }
+
+       public void setYrad(double yrad) {
+               this.yrad = yrad;
+       }
+
+       public double getAngle() {
+               return angle;
+       }
+
+       public void setAngle(double angle) {
+               this.angle = angle;
+       }
+
+       public boolean isExtruded() {
+               return extruded;
+       }
+
+       public void setExtruded(boolean extruded) {
+               this.extruded = extruded;
+       }
+
+       public boolean isBroken() {
+               return broken;
+       }
+
+       public void setBroken(boolean broken) {
+               this.broken = broken;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/Loop.java b/srcjar/fr/orsay/lri/varna/models/naView/Loop.java
new file mode 100644 (file)
index 0000000..ae08bbd
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+public class Loop {
+       private int nconnection;
+       private ArrayList<Connection> connections = new ArrayList<Connection>();
+       private Hashtable<Integer,Connection> _connections = new Hashtable<Integer,Connection>();
+       private int number;
+       private int depth;
+       private boolean mark;
+       private double x, y, radius;
+
+       public int getNconnection() {
+               return nconnection;
+       }
+
+       public void setNconnection(int nconnection) {
+               this.nconnection = nconnection;
+       }
+
+       public void setConnection(int i, Connection c)
+       {
+               Integer n = new Integer(i);
+               if (c != null)
+               _connections.put(n, c);
+               else
+               {
+                       if (!_connections.containsKey(n))
+                       {
+                               _connections.put(n, new Connection());
+                       }
+                       _connections.get(i).setNull(true);
+               }
+       }
+
+       public Connection getConnection(int i)
+       {
+               Integer n = new Integer(i);
+               if (!_connections.containsKey(n))
+               { _connections.put(n, new Connection()); }
+               Connection c = _connections.get(n);
+               if (c.isNull())
+                       return null;
+               else
+                       return c;
+       }
+       
+       public void addConnection(int i, Connection c)
+       {
+               _connections.put(_connections.size(),c);
+       }
+       
+
+       public int getNumber() {
+               return number;
+       }
+
+       public void setNumber(int number) {
+               this.number = number;
+       }
+
+       public int getDepth() {
+               return depth;
+       }
+
+       public void setDepth(int depth) {
+               this.depth = depth;
+       }
+
+       public boolean isMark() {
+               return mark;
+       }
+
+       public void setMark(boolean mark) {
+               this.mark = mark;
+       }
+
+       public double getX() {
+               return x;
+       }
+
+       public void setX(double x) {
+               this.x = x;
+       }
+
+       public double getY() {
+               return y;
+       }
+
+       public void setY(double y) {
+               this.y = y;
+       }
+
+       public double getRadius() {
+               return radius;
+       }
+
+       public void setRadius(double radius) {
+               this.radius = radius;
+       }
+       
+       public String toString()
+       {
+               String result = "Loop:";
+               result += " nconnection "+nconnection;
+               result += " depth "+depth;
+               return result;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/NAView.java b/srcjar/fr/orsay/lri/varna/models/naView/NAView.java
new file mode 100644 (file)
index 0000000..5b7d023
--- /dev/null
@@ -0,0 +1,1302 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+
+import java.util.ArrayList;
+
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAObservable;
+
+public class NAView {
+       private final double ANUM = 9999.0;
+       private final int MAXITER = 500;
+
+       private ArrayList<Base> bases;
+       private int nbase, nregion, loop_count;
+
+       private Loop root = new Loop();
+       private ArrayList<Loop> loops;
+
+       private ArrayList<Region> regions;
+
+       private Radloop rlphead = new Radloop();
+
+       private double lencut=0.8;
+       private final double RADIUS_REDUCTION_FACTOR = 1.4;
+
+       // show algorithm step by step
+       private boolean debug = false;
+
+       private double angleinc;
+
+       private double _h;
+
+       private ArrayList<InterfaceVARNAListener> _listeVARNAListener = new ArrayList<InterfaceVARNAListener>();
+
+       private boolean noIterationFailureYet = true;
+
+       double HELIX_FACTOR = 0.6;
+       double BACKBONE_DISTANCE = 27;
+
+       public int naview_xy_coordinates(ArrayList<Short> pair_table2,
+                       ArrayList<Double> x, ArrayList<Double> y)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("naview_xy_coordinates");
+               if (pair_table2.size() == 0)
+                       return 0;
+               int i;
+               ArrayList<Integer> pair_table = new ArrayList<Integer>(pair_table2
+                               .size() + 1);
+               pair_table.add(pair_table2.size());
+
+               for (int j = 0; j < pair_table2.size(); j++) {
+                       pair_table.add(pair_table2.get(j) + 1);
+               }
+
+               if (debug) {
+                       infoStructure(pair_table);
+               }
+               // length
+               nbase = pair_table.get(0);
+               bases = new ArrayList<Base>(nbase + 1);
+
+               for (int index = 0; index < bases.size(); index++) {
+                       bases.add(new Base());
+               }
+
+               regions = new ArrayList<Region>();
+               for (int index = 0; index < nbase + 1; index++) {
+                       regions.add(new Region());
+               }
+
+               read_in_bases(pair_table);
+
+               if (debug)
+                       infoBasesMate();
+
+               rlphead = null;
+
+               find_regions();
+
+               if (debug)
+                       infoRegions();
+
+               loop_count = 0;
+               loops = new ArrayList<Loop>(nbase + 1);
+               for (int index = 0; index < nbase + 1; index++) {
+                       loops.add(new Loop());
+               }
+
+               construct_loop(0);
+
+               if (debug)
+                       infoBasesExtracted();
+
+               find_central_loop();
+
+               if (debug)
+                       infoRoot();
+
+               if (debug)
+                       dump_loops();
+
+               traverse_loop(root, null);
+
+               for (i = 0; i < nbase; i++) {
+                       x.add(100 + BACKBONE_DISTANCE * bases.get(i + 1).getX());
+                       y.add(100 + BACKBONE_DISTANCE * bases.get(i + 1).getY());
+               }
+
+               return nbase;
+       }
+
+       private void infoStructure(ArrayList<Integer> pair_table) {
+               System.out.println("structure:");
+               for (int j = 0; j < pair_table.size(); j++) {
+                       System.out.print("#" + j + ":" + pair_table.get(j) + "\t");
+                       if (j % 10 == 0)
+                               System.out.println();
+               }
+               System.out.println();
+       }
+
+       private void infoBasesMate() {
+               System.out.println("Bases mate:");
+               for (int index = 0; index < bases.size(); index++) {
+                       System.out.print("#" + index + ":" + bases.get(index).getMate()
+                                       + "\t");
+                       if (index % 10 == 0)
+                               System.out.println();
+               }
+               System.out.println();
+       }
+
+       private void infoRegions() {
+               System.out.println("regions:");
+               for (int index = 0; index < regions.size(); index++) {
+                       System.out.print("(" + regions.get(index).getStart1() + ","
+                                       + regions.get(index).getStart2() + ";"
+                                       + regions.get(index).getEnd1() + ","
+                                       + regions.get(index).getEnd2() + ")\t\t");
+                       if (index % 5 == 0)
+                               System.out.println();
+               }
+               System.out.println();
+       }
+
+       private void infoBasesExtracted() {
+               System.out.println("Bases extracted:");
+               for (int index = 0; index < bases.size(); index++) {
+                       System.out.print("i=" + index + ":"
+                                       + bases.get(index).isExtracted() + "\t");
+                       if (index % 5 == 0)
+                               System.out.println();
+               }
+               System.out.println();
+       }
+
+       private void infoRoot() {
+               System.out.println("root" + root.getNconnection() + ";"
+                               + root.getNumber());
+               System.out.println("\troot : ");
+               System.out.println("\tdepth=" + root.getDepth());
+               System.out.println("\tmark=" + root.isMark());
+               System.out.println("\tnumber=" + root.getNumber());
+               System.out.println("\tradius=" + root.getRadius());
+               System.out.println("\tx=" + root.getX());
+               System.out.println("\ty=" + root.getY());
+               System.out.println("\tnconnection=" + root.getNconnection());
+       }
+
+       private void read_in_bases(ArrayList<Integer> pair_table) {
+               if (debug)
+                       System.out.println("read_in_bases");
+
+               int i, npairs;
+
+               // Set up an origin.
+               bases.add(new Base());
+               bases.get(0).setMate(0);
+               bases.get(0).setExtracted(false);
+               bases.get(0).setX(ANUM);
+               bases.get(0).setY(ANUM);
+
+               for (npairs = 0, i = 1; i <= nbase; i++) {
+                       bases.add(new Base());
+                       bases.get(i).setExtracted(false);
+                       bases.get(i).setX(ANUM);
+                       bases.get(i).setY(ANUM);
+                       bases.get(i).setMate(pair_table.get(i));
+                       if ((int) pair_table.get(i) > i)
+                               npairs++;
+               }
+               // must have at least 1 pair to avoid segfault
+               if (npairs == 0) {
+                       bases.get(1).setMate(nbase);
+                       bases.get(nbase).setMate(1);
+               }
+       }
+
+       /**
+        * Identifies the regions in the structure.
+        */
+       private void find_regions()
+
+       {
+               if (debug)
+                       System.out.println("find_regions");
+               int i, mate, nb1;
+               nb1 = nbase + 1;
+               ArrayList<Boolean> mark = new ArrayList<Boolean>(nb1);
+               for (i = 0; i < nb1; i++)
+                       mark.add(false);
+               nregion = 0;
+               for (i = 0; i <= nbase; i++) {
+                       if ((mate = bases.get(i).getMate()) != 0 && !mark.get(i)) {
+                               regions.get(nregion).setStart1(i);
+                               regions.get(nregion).setEnd2(mate);
+                               mark.set(i, true);
+                               mark.set(mate, true);
+                               bases.get(i).setRegion(regions.get(nregion));
+                               bases.get(mate).setRegion(regions.get(nregion));
+                               for (i++, mate--; i < mate && bases.get(i).getMate() == mate; i++, mate--) {
+                                       mark.set(mate, true);
+                                       mark.set(i, true);
+                                       bases.get(i).setRegion(regions.get(nregion));
+                                       bases.get(mate).setRegion(regions.get(nregion));
+                               }
+                               regions.get(nregion).setEnd1(--i);
+                               regions.get(nregion).setStart2(mate + 1);
+                               if (debug) {
+                                       if (nregion == 0)
+                                               System.out.printf("\nRegions are:\n");
+                                       System.out.printf(
+                                                       "Region %d is %d-%d and %d-%d with gap of %d.\n",
+                                                       nregion + 1, regions.get(nregion).getStart1(),
+                                                       regions.get(nregion).getEnd1(), regions
+                                                                       .get(nregion).getStart2(), regions.get(
+                                                                       nregion).getEnd2(), regions.get(nregion)
+                                                                       .getStart2()
+                                                                       - regions.get(nregion).getEnd1() + 1);
+                               }
+                               nregion++;
+                       }
+               }
+       }
+
+       /**
+        * Starting at residue ibase, recursively constructs the loop containing
+        * said base and all deeper bases.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private Loop construct_loop(int ibase) throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("construct_loop");
+               int i, mate;
+               Loop retloop = new Loop(), lp = new Loop();
+               Connection cp = new Connection();
+               Region rp = new Region();
+               Radloop rlp = new Radloop();
+               retloop = loops.get(loop_count++);
+               retloop.setNconnection(0);
+               //System.out.println(""+ibase+" "+nbase);
+               //ArrayList<Connection> a = new ArrayList<Connection>(nbase + 1); 
+               //retloop.setConnections(a);
+//             for (int index = 0; index < nbase + 1; index++)
+//                     retloop.getConnections().add(new Connection());
+               //for (int index = 0; index < nbase + 1; index++)
+               //      retloop.addConnection(index,new Connection());
+               retloop.setDepth(0);
+               retloop.setNumber(loop_count);
+               retloop.setRadius(0.0);
+               for (rlp = rlphead; rlp != null; rlp = rlp.getNext())
+                       if (rlp.getLoopnumber() == loop_count)
+                               retloop.setRadius(rlp.getRadius());
+               i = ibase;
+               do {
+                       if ((mate = bases.get(i).getMate()) != 0) {
+                               rp = bases.get(i).getRegion();
+                               if (!bases.get(rp.getStart1()).isExtracted()) {
+                                       if (i == rp.getStart1()) {
+                                               bases.get(rp.getStart1()).setExtracted(true);
+                                               bases.get(rp.getEnd1()).setExtracted(true);
+                                               bases.get(rp.getStart2()).setExtracted(true);
+                                               bases.get(rp.getEnd2()).setExtracted(true);
+                                               lp = construct_loop(rp.getEnd1() < nbase ? rp.getEnd1() + 1
+                                                               : 0);
+                                       } else if (i == rp.getStart2()) {
+                                               bases.get(rp.getStart2()).setExtracted(true);
+                                               bases.get(rp.getEnd2()).setExtracted(true);
+                                               bases.get(rp.getStart1()).setExtracted(true);
+                                               bases.get(rp.getEnd1()).setExtracted(true);
+                                               lp = construct_loop(rp.getEnd2() < nbase ? rp.getEnd2() + 1
+                                                               : 0);
+                                       } else {
+                                               throw new ExceptionNAViewAlgorithm(
+                                                               "naview:Error detected in construct_loop. i = "
+                                                                               + i + " not found in region table.\n");
+                                       }
+                                       retloop.setNconnection(retloop.getNconnection() + 1);
+                                       cp = new Connection();
+                                       retloop.setConnection(retloop.getNconnection() - 1,     cp);
+                                       retloop.setConnection(retloop.getNconnection(), null);
+                                       cp.setLoop(lp);
+                                       cp.setRegion(rp);
+                                       if (i == rp.getStart1()) {
+                                               cp.setStart(rp.getStart1());
+                                               cp.setEnd(rp.getEnd2());
+                                       } else {
+                                               cp.setStart(rp.getStart2());
+                                               cp.setEnd(rp.getEnd1());
+                                       }
+                                       cp.setExtruded(false);
+                                       cp.setBroken(false);
+                                       lp.setNconnection(lp.getNconnection() + 1);
+                                       cp = new Connection();
+                                       lp.setConnection(lp.getNconnection() - 1, cp);
+                                       lp.setConnection(lp.getNconnection(), null);
+                                       cp.setLoop(retloop);
+                                       cp.setRegion(rp);
+                                       if (i == rp.getStart1()) {
+                                               cp.setStart(rp.getStart2());
+                                               cp.setEnd(rp.getEnd1());
+                                       } else {
+                                               cp.setStart(rp.getStart1());
+                                               cp.setEnd(rp.getEnd2());
+                                       }
+                                       cp.setExtruded(false);
+                                       cp.setBroken(false);
+                               }
+                               i = mate;
+                       }
+                       if (++i > nbase)
+                               i = 0;
+               } while (i != ibase);
+               return retloop;
+       }
+
+       /**
+        * Displays all the loops.
+        */
+       private void dump_loops() {
+               System.out.println("dump_loops");
+               int il, ilp, irp;
+               Loop lp;
+               Connection cp;
+
+               System.out.printf("\nRoot loop is #%d\n", loops.indexOf(root) + 1);
+               for (il = 0; il < loop_count; il++) {
+                       lp = loops.get(il);
+                       System.out.printf("Loop %d has %d connections:\n", il + 1, lp
+                                       .getNconnection());
+                       for (int i = 0; (cp = lp.getConnection(i)) != null; i++) {
+                               ilp = (loops.indexOf(cp.getLoop())) + 1;
+                               irp = (regions.indexOf(cp.getRegion())) + 1;
+                               System.out.printf("  Loop %d Region %d (%d-%d)\n", ilp, irp, cp
+                                               .getStart(), cp.getEnd());
+                       }
+               }
+       }
+
+       /**
+        * Find node of greatest branching that is deepest.
+        */
+       private void find_central_loop() {
+               if (debug)
+                       System.out.println("find_central_loop");
+               Loop lp = new Loop();
+               int maxconn, maxdepth, i;
+
+               determine_depths();
+               maxconn = 0;
+               maxdepth = -1;
+               for (i = 0; i < loop_count; i++) {
+                       lp = loops.get(i);
+                       if (lp.getNconnection() > maxconn) {
+                               maxdepth = lp.getDepth();
+                               maxconn = lp.getNconnection();
+                               root = lp;
+                       } else if (lp.getDepth() > maxdepth
+                                       && lp.getNconnection() == maxconn) {
+                               maxdepth = lp.getDepth();
+                               root = lp;
+                       }
+               }
+       }
+
+       /**
+        * Determine the depth of all loops.
+        */
+       private void determine_depths() {
+               if (debug)
+                       System.out.println("determine_depths");
+               Loop lp = new Loop();
+               int i, j;
+
+               for (i = 0; i < loop_count; i++) {
+                       lp = loops.get(i);
+                       for (j = 0; j < loop_count; j++)
+                               loops.get(j).setMark(false);
+                       lp.setDepth(depth(lp));
+               }
+       }
+
+       /**
+        * Determines the depth of loop, lp. Depth is defined as the minimum
+        * distance to a leaf loop where a leaf loop is one that has only one or no
+        * connections.
+        */
+       private int depth(Loop lp) {
+               if (debug)
+                       System.out.println("depth");
+               int count, ret, d;
+
+               if (lp.getNconnection() <= 1)
+                       return 0;
+               if (lp.isMark())
+                       return -1;
+               lp.setMark(true);
+               count = 0;
+               ret = 0;
+               for (int i = 0; lp.getConnection(i) != null; i++) {
+                       d = depth(lp.getConnection(i).getLoop());
+                       if (d >= 0) {
+                               if (++count == 1)
+                                       ret = d;
+                               else if (ret > d)
+                                       ret = d;
+                       }
+               }
+               lp.setMark(false);
+               return ret + 1;
+       }
+
+       /**
+        * This is the workhorse of the display program. The algorithm is recursive
+        * based on processing individual loops. Each base pairing region is
+        * displayed using the direction given by the circle diagram, and the
+        * connections between the regions is drawn by equally spaced points. The
+        * radius of the loop is set to minimize the square error for lengths
+        * between sequential bases in the loops. The "correct" length for base
+        * links is 1. If the least squares fitting of the radius results in loops
+        * being less than 1/2 unit apart, then that segment is extruded.
+        * 
+        * The variable, anchor_connection, gives the connection to the loop
+        * processed in an previous level of recursion.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private void traverse_loop(Loop lp, Connection anchor_connection)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  traverse_loop");
+               double xs, ys, xe, ye, xn, yn, angleinc, r;
+               double radius, xc, yc, xo, yo, astart, aend, a;
+               Connection cp, cpnext, acp, cpprev;
+               int i, j, n, ic;
+               double da, maxang;
+               int count, icstart, icend, icmiddle, icroot;
+               boolean done, done_all_connections, rooted;
+               int sign;
+               double midx, midy, nrx, nry, mx, my, vx, vy, dotmv, nmidx, nmidy;
+               int icstart1, icup, icdown, icnext, direction;
+               double dan, dx, dy, rr;
+               double cpx, cpy, cpnextx, cpnexty, cnx, cny, rcn, rc, lnx, lny, rl, ac, acn, sx, sy, dcp;
+               int imaxloop = 0;
+
+               angleinc = 2 * Math.PI / (nbase + 1);
+               acp = null;
+               icroot = -1;
+               int indice = 0;
+               
+               for (ic = 0; (cp = lp.getConnection(indice)) != null; indice++, ic++) {
+                       // xs = cos(angleinc*cp.setStart(); ys = sin(angleinc*cp.setStart();
+                       // xe =
+                       // cos(angleinc*cp.setEnd()); ye = sin(angleinc*cp.setEnd());
+                       xs = -Math.sin(angleinc * cp.getStart());
+                       ys = Math.cos(angleinc * cp.getStart());
+                       xe = -Math.sin(angleinc * cp.getEnd());
+                       ye = Math.cos(angleinc * cp.getEnd());
+                       xn = ye - ys;
+                       yn = xs - xe;
+                       r = Math.sqrt(xn * xn + yn * yn);
+                       cp.setXrad(xn / r);
+                       cp.setYrad(yn / r);
+                       cp.setAngle(Math.atan2(yn, xn));
+                       if (cp.getAngle() < 0.0)
+                               cp.setAngle(cp.getAngle() + 2 * Math.PI);
+                       if (anchor_connection != null
+                                       && anchor_connection.getRegion() == cp.getRegion()) {
+                               acp = cp;
+                               icroot = ic;
+                       }
+               }
+               // remplacement d'une etiquette de goto
+               set_radius: while (true) {
+                       determine_radius(lp, lencut);
+                       radius = lp.getRadius()/RADIUS_REDUCTION_FACTOR;
+                       if (anchor_connection == null)
+                               xc = yc = 0.0;
+                       else {
+                               xo = (bases.get(acp.getStart()).getX() + bases
+                                               .get(acp.getEnd()).getX()) / 2.0;
+                               yo = (bases.get(acp.getStart()).getY() + bases
+                                               .get(acp.getEnd()).getY()) / 2.0;
+                               xc = xo - radius * acp.getXrad();
+                               yc = yo - radius * acp.getYrad();
+                       }
+
+                       // The construction of the connectors will proceed in blocks of
+                       // connected connectors, where a connected connector pairs means two
+                       // connectors that are forced out of the drawn circle because they
+                       // are too close together in angle.
+
+                       // First, find the start of a block of connected connectors
+
+                       if (icroot == -1)
+                               icstart = 0;
+                       else
+                               icstart = icroot;
+                       cp = lp.getConnection(icstart);
+                       count = 0;
+                       if (debug)
+                       {
+                               System.out.printf("Now processing loop %d\n", lp.getNumber());
+                               System.out.println("  "+lp);
+                       }
+                       done = false;
+                       do {
+                               j = icstart - 1;
+                               if (j < 0)
+                                       j = lp.getNconnection() - 1;
+                               cpprev = lp.getConnection(j);
+                               if (!connected_connection(cpprev, cp)) {
+                                       done = true;
+                               } else {
+                                       icstart = j;
+                                       cp = cpprev;
+                               }
+                               if (++count > lp.getNconnection()) {
+                                       // Here everything is connected. Break on maximum angular
+                                       // separation between connections.
+
+                                       maxang = -1.0;
+                                       for (ic = 0; ic < lp.getNconnection(); ic++) {
+                                               j = ic + 1;
+                                               if (j >= lp.getNconnection())
+                                                       j = 0;
+                                               cp = lp.getConnection(ic);
+                                               cpnext = lp.getConnection(j);
+                                               ac = cpnext.getAngle() - cp.getAngle();
+                                               if (ac < 0.0)
+                                                       ac += 2 * Math.PI;
+                                               if (ac > maxang) {
+                                                       maxang = ac;
+                                                       imaxloop = ic;
+                                               }
+                                       }
+                                       icend = imaxloop;
+                                       icstart = imaxloop + 1;
+                                       if (icstart >= lp.getNconnection())
+                                               icstart = 0;
+                                       cp = lp.getConnection(icend);
+                                       cp.setBroken(true);
+                                       done = true;
+                               }
+                       } while (!done);
+                       done_all_connections = false;
+                       icstart1 = icstart;
+                       if (debug)
+                               System.out.printf("  Icstart1 = %d\n", icstart1);
+                       while (!done_all_connections) {
+                               count = 0;
+                               done = false;
+                               icend = icstart;
+                               rooted = false;
+                               while (!done) {
+                                       cp = lp.getConnection(icend);
+                                       if (icend == icroot)
+                                               rooted = true;
+                                       j = icend + 1;
+                                       if (j >= lp.getNconnection()) {
+                                               j = 0;
+                                       }
+                                       cpnext = lp.getConnection(j);
+                                       if (connected_connection(cp, cpnext)) {
+                                               if (++count >= lp.getNconnection())
+                                                       break;
+                                               icend = j;
+                                       } else {
+                                               done = true;
+                                       }
+                               }
+                               icmiddle = find_ic_middle(icstart, icend, anchor_connection,
+                                               acp, lp);
+                               ic = icup = icdown = icmiddle;
+                               if (debug)
+                                       System.out.printf("  IC start = %d  middle = %d  end = %d\n",
+                                                       icstart, icmiddle, icend);
+                               done = false;
+                               direction = 0;
+                               while (!done) {
+                                       if (direction < 0) {
+                                               ic = icup;
+                                       } else if (direction == 0) {
+                                               ic = icmiddle;
+                                       } else {
+                                               ic = icdown;
+                                       }
+                                       if (ic >= 0) {
+                                               cp = lp.getConnection(ic);
+                                               if (anchor_connection == null || acp != cp) {
+                                                       if (direction == 0) {
+                                                               astart = cp.getAngle()
+                                                                               - Math.asin(1.0 / 2.0 / radius);
+                                                               aend = cp.getAngle()
+                                                                               + Math.asin(1.0 / 2.0 / radius);
+                                                               bases.get(cp.getStart()).setX(
+                                                                               xc + radius * Math.cos(astart));
+                                                               bases.get(cp.getStart()).setY(
+                                                                               yc + radius * Math.sin(astart));
+                                                               bases.get(cp.getEnd()).setX(
+                                                                               xc + radius * Math.cos(aend));
+                                                               bases.get(cp.getEnd()).setY(
+                                                                               yc + radius * Math.sin(aend));
+                                                       } else if (direction < 0) {
+                                                               j = ic + 1;
+                                                               if (j >= lp.getNconnection())
+                                                                       j = 0;
+                                                               cp = lp.getConnection(ic);
+                                                               cpnext = lp.getConnection(j);
+                                                               cpx = cp.getXrad();
+                                                               cpy = cp.getYrad();
+                                                               ac = (cp.getAngle() + cpnext.getAngle()) / 2.0;
+                                                               if (cp.getAngle() > cpnext.getAngle())
+                                                                       ac -= Math.PI;
+                                                               cnx = Math.cos(ac);
+                                                               cny = Math.sin(ac);
+                                                               lnx = cny;
+                                                               lny = -cnx;
+                                                               da = cpnext.getAngle() - cp.getAngle();
+                                                               if (da < 0.0)
+                                                                       da += 2 * Math.PI;
+                                                               if (cp.isExtruded()) {
+                                                                       if (da <= Math.PI / 2)
+                                                                               rl = 2.0;
+                                                                       else
+                                                                               rl = 1.5;
+                                                               } else {
+                                                                       rl = 1.0;
+                                                               }
+                                                               bases.get(cp.getEnd()).setX(
+                                                                               bases.get(cpnext.getStart()).getX()
+                                                                                               + rl * lnx);
+                                                               bases.get(cp.getEnd()).setY(
+                                                                               bases.get(cpnext.getStart()).getY()
+                                                                                               + rl * lny);
+                                                               bases.get(cp.getStart()).setX(
+                                                                               bases.get(cp.getEnd()).getX() + cpy);
+                                                               bases.get(cp.getStart()).setY(
+                                                                               bases.get(cp.getEnd()).getY() - cpx);
+                                                       } else {
+                                                               j = ic - 1;
+                                                               if (j < 0)
+                                                                       j = lp.getNconnection() - 1;
+                                                               cp = lp.getConnection(j);
+                                                               cpnext = lp.getConnection(ic);
+                                                               cpnextx = cpnext.getXrad();
+                                                               cpnexty = cpnext.getYrad();
+                                                               ac = (cp.getAngle() + cpnext.getAngle()) / 2.0;
+                                                               if (cp.getAngle() > cpnext.getAngle())
+                                                                       ac -= Math.PI;
+                                                               cnx = Math.cos(ac);
+                                                               cny = Math.sin(ac);
+                                                               lnx = -cny;
+                                                               lny = cnx;
+                                                               da = cpnext.getAngle() - cp.getAngle();
+                                                               if (da < 0.0)
+                                                                       da += 2 * Math.PI;
+                                                               if (cp.isExtruded()) {
+                                                                       if (da <= Math.PI / 2)
+                                                                               rl = 2.0;
+                                                                       else
+                                                                               rl = 1.5;
+                                                               } else {
+                                                                       rl = 1.0;
+                                                               }
+                                                               bases.get(cpnext.getStart()).setX(
+                                                                               bases.get(cp.getEnd()).getX() + rl
+                                                                                               * lnx);
+                                                               bases.get(cpnext.getStart()).setY(
+                                                                               bases.get(cp.getEnd()).getY() + rl
+                                                                                               * lny);
+                                                               bases.get(cpnext.getEnd()).setX(
+                                                                               bases.get(cpnext.getStart()).getX()
+                                                                                               - cpnexty);
+                                                               bases.get(cpnext.getEnd()).setY(
+                                                                               bases.get(cpnext.getStart()).getY()
+                                                                                               + cpnextx);
+                                                       }
+                                               }
+                                       }
+                                       if (direction < 0) {
+                                               if (icdown == icend) {
+                                                       icdown = -1;
+                                               } else if (icdown >= 0) {
+                                                       if (++icdown >= lp.getNconnection()) {
+                                                               icdown = 0;
+                                                       }
+                                               }
+                                               direction = 1;
+                                       } else {
+                                               if (icup == icstart)
+                                                       icup = -1;
+                                               else if (icup >= 0) {
+                                                       if (--icup < 0) {
+                                                               icup = lp.getNconnection() - 1;
+                                                       }
+                                               }
+                                               direction = -1;
+                                       }
+                                       done = icup == -1 && icdown == -1;
+                               }
+                               icnext = icend + 1;
+                               if (icnext >= lp.getNconnection())
+                                       icnext = 0;
+                               if (icend != icstart
+                                               && (!(icstart == icstart1 && icnext == icstart1))) {
+
+                                       // Move the bases just constructed (or the radius) so that
+                                       // the bisector of the end points is radius distance away
+                                       // from the loop center.
+
+                                       cp = lp.getConnection(icstart);
+                                       cpnext = lp.getConnection(icend);
+                                       dx = bases.get(cpnext.getEnd()).getX()
+                                                       - bases.get(cp.getStart()).getX();
+                                       dy = bases.get(cpnext.getEnd()).getY()
+                                                       - bases.get(cp.getStart()).getY();
+                                       midx = bases.get(cp.getStart()).getX() + dx / 2.0;
+                                       midy = bases.get(cp.getStart()).getY() + dy / 2.0;
+                                       rr = Math.sqrt(dx * dx + dy * dy);
+                                       mx = dx / rr;
+                                       my = dy / rr;
+                                       vx = xc - midx;
+                                       vy = yc - midy;
+                                       rr = Math.sqrt(dx * dx + dy * dy);
+                                       vx /= rr;
+                                       vy /= rr;
+                                       dotmv = vx * mx + vy * my;
+                                       nrx = dotmv * mx - vx;
+                                       nry = dotmv * my - vy;
+                                       rr = Math.sqrt(nrx * nrx + nry * nry);
+                                       nrx /= rr;
+                                       nry /= rr;
+
+                                       // Determine which side of the bisector the center should
+                                       // be.
+
+                                       dx = bases.get(cp.getStart()).getX() - xc;
+                                       dy = bases.get(cp.getStart()).getY() - yc;
+                                       ac = Math.atan2(dy, dx);
+                                       if (ac < 0.0)
+                                               ac += 2 * Math.PI;
+                                       dx = bases.get(cpnext.getEnd()).getX() - xc;
+                                       dy = bases.get(cpnext.getEnd()).getY() - yc;
+                                       acn = Math.atan2(dy, dx);
+                                       if (acn < 0.0)
+                                               acn += 2 * Math.PI;
+                                       if (acn < ac)
+                                               acn += 2 * Math.PI;
+                                       if (acn - ac > Math.PI)
+                                               sign = -1;
+                                       else
+                                               sign = 1;
+                                       nmidx = xc + sign * radius * nrx;
+                                       nmidy = yc + sign * radius * nry;
+                                       if (rooted) {
+                                               xc -= nmidx - midx;
+                                               yc -= nmidy - midy;
+                                       } else {
+                                               for (ic = icstart;;) {
+                                                       cp = lp.getConnection(ic);
+                                                       i = cp.getStart();
+                                                       bases.get(i).setX(
+                                                                       bases.get(i).getX() + nmidx - midx);
+                                                       bases.get(i).setY(
+                                                                       bases.get(i).getY() + nmidy - midy);
+                                                       i = cp.getEnd();
+                                                       bases.get(i).setX(
+                                                                       bases.get(i).getX() + nmidx - midx);
+                                                       bases.get(i).setY(
+                                                                       bases.get(i).getY() + nmidy - midy);
+                                                       if (ic == icend)
+                                                               break;
+                                                       if (++ic >= lp.getNconnection())
+                                                               ic = 0;
+                                               }
+                                       }
+                               }
+                               icstart = icnext;
+                               done_all_connections = icstart == icstart1;
+                       }
+                       for (ic = 0; ic < lp.getNconnection(); ic++) {
+                               cp = lp.getConnection(ic);
+                               j = ic + 1;
+                               if (j >= lp.getNconnection())
+                                       j = 0;
+                               cpnext = lp.getConnection(j);
+                               dx = bases.get(cp.getEnd()).getX() - xc;
+                               dy = bases.get(cp.getEnd()).getY() - yc;
+                               rc = Math.sqrt(dx * dx + dy * dy);
+                               ac = Math.atan2(dy, dx);
+                               if (ac < 0.0)
+                                       ac += 2 * Math.PI;
+                               dx = bases.get(cpnext.getStart()).getX() - xc;
+                               dy = bases.get(cpnext.getStart()).getY() - yc;
+                               rcn = Math.sqrt(dx * dx + dy * dy);
+                               acn = Math.atan2(dy, dx);
+                               if (acn < 0.0)
+                                       acn += 2 * Math.PI;
+                               if (acn < ac)
+                                       acn += 2 * Math.PI;
+                               dan = acn - ac;
+                               dcp = cpnext.getAngle() - cp.getAngle();
+                               if (dcp <= 0.0)
+                                       dcp += 2 * Math.PI;
+                               if (Math.abs(dan - dcp) > Math.PI) {
+                                       if (cp.isExtruded()) {
+                                               warningEmition("Warning from traverse_loop. Loop "
+                                                               + lp.getNumber() + " has crossed regions\n");
+                                       } else if ((cpnext.getStart() - cp.getEnd()) != 1) {
+                                               cp.setExtruded(true);
+                                               continue set_radius; // remplacement du goto
+                                       }
+                               }
+                               if (cp.isExtruded()) {
+                                       construct_extruded_segment(cp, cpnext);
+                               } else {
+                                       n = cpnext.getStart() - cp.getEnd();
+                                       if (n < 0)
+                                               n += nbase + 1;
+                                       angleinc = dan / n;
+                                       for (j = 1; j < n; j++) {
+                                               i = cp.getEnd() + j;
+                                               if (i > nbase)
+                                                       i -= nbase + 1;
+                                               a = ac + j * angleinc;
+                                               rr = rc + (rcn - rc) * (a - ac) / dan;
+                                               bases.get(i).setX(xc + rr * Math.cos(a));
+                                               bases.get(i).setY(yc + rr * Math.sin(a));
+                                       }
+                               }
+                       }
+                       break;
+               }
+               for (ic = 0; ic < lp.getNconnection(); ic++) {
+                       if (icroot != ic) {
+                               cp = lp.getConnection(ic);
+                               generate_region(cp);
+                               traverse_loop(cp.getLoop(), cp);
+                       }
+               }
+               n = 0;
+               sx = 0.0;
+               sy = 0.0;
+               for (ic = 0; ic < lp.getNconnection(); ic++) {
+                       j = ic + 1;
+                       if (j >= lp.getNconnection())
+                               j = 0;
+                       cp = lp.getConnection(ic);
+                       cpnext = lp.getConnection(j);
+                       n += 2;
+                       sx += bases.get(cp.getStart()).getX()
+                                       + bases.get(cp.getEnd()).getX();
+                       sy += bases.get(cp.getStart()).getY()
+                                       + bases.get(cp.getEnd()).getY();
+                       if (!cp.isExtruded()) {
+                               for (j = cp.getEnd() + 1; j != cpnext.getStart(); j++) {
+                                       if (j > nbase)
+                                               j -= nbase + 1;
+                                       n++;
+                                       sx += bases.get(j).getX();
+                                       sy += bases.get(j).getY();
+                               }
+                       }
+               }
+               lp.setX(sx / n);
+               lp.setY(sy / n);
+       }
+
+       /**
+        * For the loop pointed to by lp, determine the radius of the loop that will
+        * ensure that each base around the loop will have a separation of at least
+        * lencut around the circle. If a segment joining two connectors will not
+        * support this separation, then the flag, extruded, will be set in the
+        * first of these two indicators. The radius is set in lp.
+        * 
+        * The radius is selected by a least squares procedure where the sum of the
+        * squares of the deviations of length from the ideal value of 1 is used as
+        * the error function.
+        */
+       private void determine_radius(Loop lp, double lencut) {
+               if (debug)
+                       System.out.println("  Determine_radius");
+               double mindit, ci, dt, sumn, sumd, radius, dit;
+               int i, j, end, start, imindit = 0;
+               Connection cp = new Connection(), cpnext = new Connection();
+               double rt2_2 = 0.7071068;
+
+               do {
+                       mindit = 1.0e10;
+                       for (sumd = 0.0, sumn = 0.0, i = 0; i < lp.getNconnection(); i++) {
+                               cp = lp.getConnection(i);
+                               j = i + 1;
+                               if (j >= lp.getNconnection())
+                                       j = 0;
+                               cpnext = lp.getConnection(j);
+                               end = cp.getEnd();
+                               start = cpnext.getStart();
+                               if (start < end)
+                                       start += nbase + 1;
+                               dt = cpnext.getAngle() - cp.getAngle();
+                               if (dt <= 0.0)
+                                       dt += 2 * Math.PI;
+                               if (!cp.isExtruded())
+                                       ci = start - end;
+                               else {
+                                       if (dt <= Math.PI / 2)
+                                               ci = 2.0;
+                                       else
+                                               ci = 1.5;
+                               }
+                               sumn += dt * (1.0 / ci + 1.0);
+                               sumd += dt * dt / ci;
+                               dit = dt / ci;
+                               if (dit < mindit && !cp.isExtruded() && ci > 1.0) {
+                                       mindit = dit;
+                                       imindit = i;
+                               }
+                       }
+                       radius = sumn / sumd;
+                       if (radius < rt2_2)
+                               radius = rt2_2;
+                       if (mindit * radius < lencut) {
+                               lp.getConnection(imindit).setExtruded(true);
+                       }
+               } while (mindit * radius < lencut);
+               if (lp.getRadius() > 0.0)
+                       radius = lp.getRadius();
+               else
+                       lp.setRadius(radius);
+       }
+
+       /**
+        * Determines if the connections cp and cpnext are connected
+        */
+       private boolean connected_connection(Connection cp, Connection cpnext) {
+               if (debug)
+                       System.out.println("  Connected_connection");
+               if (cp.isExtruded()) {
+                       return true;
+               } else if (cp.getEnd() + 1 == cpnext.getStart()) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Finds the middle of a set of connected connectors. This is normally the
+        * middle connection in the sequence except if one of the connections is the
+        * anchor, in which case that connection will be used.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private int find_ic_middle(int icstart, int icend,
+                       Connection anchor_connection, Connection acp, Loop lp)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  Find_ic_middle");
+               int count, ret, ic, i;
+               boolean done;
+
+               count = 0;
+               ret = -1;
+               ic = icstart;
+               done = false;
+               while (!done) {
+                       if (count++ > lp.getNconnection() * 2) {
+                               throw new ExceptionNAViewAlgorithm(
+                                               "Infinite loop detected in find_ic_middle");
+                       }
+                       if (anchor_connection != null && lp.getConnection(ic) == acp) {
+                               ret = ic;
+                       }
+                       done = ic == icend;
+                       if (++ic >= lp.getNconnection()) {
+                               ic = 0;
+                       }
+               }
+               if (ret == -1) {
+                       for (i = 1, ic = icstart; i < (count + 1) / 2; i++) {
+                               if (++ic >= lp.getNconnection())
+                                       ic = 0;
+                       }
+                       ret = ic;
+               }
+               return ret;
+       }
+
+       /**
+        * Generates the coordinates for the base pairing region of a connection
+        * given the position of the starting base pair.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private void generate_region(Connection cp) throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  Generate_region");
+               int l, start, end, i, mate;
+               Region rp;
+
+               rp = cp.getRegion();
+               l = 0;
+               if (cp.getStart() == rp.getStart1()) {
+                       start = rp.getStart1();
+                       end = rp.getEnd1();
+               } else {
+                       start = rp.getStart2();
+                       end = rp.getEnd2();
+               }
+               if (bases.get(cp.getStart()).getX() > ANUM - 100.0
+                               || bases.get(cp.getEnd()).getX() > ANUM - 100.0) {
+                       throw new ExceptionNAViewAlgorithm(
+                                       "Bad region passed to generate_region. Coordinates not defined.");
+               }
+               for (i = start + 1; i <= end; i++) {
+                       l++;
+                       bases.get(i).setX(
+                                       bases.get(cp.getStart()).getX() + HELIX_FACTOR * l
+                                                       * cp.getXrad());
+                       bases.get(i).setY(
+                                       bases.get(cp.getStart()).getY() + HELIX_FACTOR * l
+                                                       * cp.getYrad());
+                       mate = bases.get(i).getMate();
+                       bases.get(mate).setX(
+                                       bases.get(cp.getEnd()).getX() + HELIX_FACTOR * l
+                                                       * cp.getXrad());
+                       bases.get(mate).setY(
+                                       bases.get(cp.getEnd()).getY() + HELIX_FACTOR * l
+                                                       * cp.getYrad());
+                       
+               }
+       }
+
+       /**
+        * Draws the segment of residue between the bases numbered start through
+        * end, where start and end are presumed to be part of a base pairing
+        * region. They are drawn as a circle which has a chord given by the ends of
+        * two base pairing regions defined by the connections.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private void construct_circle_segment(int start, int end)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  Construct_circle_segment");
+               double dx, dy, rr, midx, midy, xn, yn, nrx, nry, mx, my, a;
+               int l, j, i;
+
+               dx = bases.get(end).getX() - bases.get(start).getX();
+               dy = bases.get(end).getY() - bases.get(start).getY();
+               rr = Math.sqrt(dx * dx + dy * dy);
+               l = end - start;
+               if (l < 0)
+                       l += nbase + 1;
+               if (rr >= l) {
+                       dx /= rr;
+                       dy /= rr;
+                       for (j = 1; j < l; j++) {
+                               i = start + j;
+                               if (i > nbase)
+                                       i -= nbase + 1;
+                               bases.get(i).setX(
+                                               bases.get(start).getX() + dx * (double) j / (double) l);
+                               bases.get(i).setY(
+                                               bases.get(start).getY() + dy * (double) j / (double) l);
+                       }
+               } else {
+                       find_center_for_arc((l - 1), rr);
+                       dx /= rr;
+                       dy /= rr;
+                       midx = bases.get(start).getX() + dx * rr / 2.0;
+                       midy = bases.get(start).getY() + dy * rr / 2.0;
+                       xn = dy;
+                       yn = -dx;
+                       nrx = midx + _h * xn;
+                       nry = midy + _h * yn;
+                       mx = bases.get(start).getX() - nrx;
+                       my = bases.get(start).getY() - nry;
+                       rr = Math.sqrt(mx * mx + my * my);
+                       a = Math.atan2(my, mx);
+                       for (j = 1; j < l; j++) {
+                               i = start + j;
+                               if (i > nbase)
+                                       i -= nbase + 1;
+                               bases.get(i).setX(nrx + rr * Math.cos(a + j * angleinc));
+                               bases.get(i).setY(nry + rr * Math.sin(a + j * angleinc));
+                       }
+               }
+       }
+
+       /**
+        * Constructs the segment between cp and cpnext as a circle if possible.
+        * However, if the segment is too large, the lines are drawn between the two
+        * connecting regions, and bases are placed there until the connecting
+        * circle will fit.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private void construct_extruded_segment(Connection cp, Connection cpnext)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  Construct_extruded_segment");
+               double astart, aend1, aend2, aave, dx, dy, a1, a2, ac, rr, da, dac;
+               int start, end, n, nstart, nend;
+               boolean collision;
+
+               astart = cp.getAngle();
+               aend2 = aend1 = cpnext.getAngle();
+               if (aend2 < astart)
+                       aend2 += 2 * Math.PI;
+               aave = (astart + aend2) / 2.0;
+               start = cp.getEnd();
+               end = cpnext.getStart();
+               n = end - start;
+               if (n < 0)
+                       n += nbase + 1;
+               da = cpnext.getAngle() - cp.getAngle();
+               if (da < 0.0) {
+                       da += 2 * Math.PI;
+               }
+               if (n == 2)
+                       construct_circle_segment(start, end);
+               else {
+                       dx = bases.get(end).getX() - bases.get(start).getX();
+                       dy = bases.get(end).getY() - bases.get(start).getY();
+                       rr = Math.sqrt(dx * dx + dy * dy);
+                       dx /= rr;
+                       dy /= rr;
+                       if (rr >= 1.5 && da <= Math.PI / 2) {
+                               nstart = start + 1;
+                               if (nstart > nbase)
+                                       nstart -= nbase + 1;
+                               nend = end - 1;
+                               if (nend < 0)
+                                       nend += nbase + 1;
+                               bases.get(nstart).setX(bases.get(start).getX() + 0.5 * dx);
+                               bases.get(nstart).setY(bases.get(start).getY() + 0.5 * dy);
+                               bases.get(nend).setX(bases.get(end).getX() - 0.5 * dx);
+                               bases.get(nend).setY(bases.get(end).getY() - 0.5 * dy);
+                               start = nstart;
+                               end = nend;
+                       }
+                       do {
+                               collision = false;
+                               construct_circle_segment(start, end);
+                               nstart = start + 1;
+                               if (nstart > nbase)
+                                       nstart -= nbase + 1;
+                               dx = bases.get(nstart).getX() - bases.get(start).getX();
+                               dy = bases.get(nstart).getY() - bases.get(start).getY();
+                               a1 = Math.atan2(dy, dx);
+                               if (a1 < 0.0)
+                                       a1 += 2 * Math.PI;
+                               dac = a1 - astart;
+                               if (dac < 0.0)
+                                       dac += 2 * Math.PI;
+                               if (dac > Math.PI)
+                                       collision = true;
+                               nend = end - 1;
+                               if (nend < 0)
+                                       nend += nbase + 1;
+                               dx = bases.get(nend).getX() - bases.get(end).getX();
+                               dy = bases.get(nend).getY() - bases.get(end).getY();
+                               a2 = Math.atan2(dy, dx);
+                               if (a2 < 0.0)
+                                       a2 += 2 * Math.PI;
+                               dac = aend1 - a2;
+                               if (dac < 0.0)
+                                       dac += 2 * Math.PI;
+                               if (dac > Math.PI)
+                                       collision = true;
+                               if (collision) {
+                                       ac = minf2(aave, astart + 0.5);
+                                       bases.get(nstart).setX(
+                                                       bases.get(start).getX() + Math.cos(ac));
+                                       bases.get(nstart).setY(
+                                                       bases.get(start).getY() + Math.sin(ac));
+                                       start = nstart;
+                                       ac = maxf2(aave, aend2 - 0.5);
+                                       bases.get(nend).setX(bases.get(end).getX() + Math.cos(ac));
+                                       bases.get(nend).setY(bases.get(end).getY() + Math.sin(ac));
+                                       end = nend;
+                                       n -= 2;
+                               }
+                       } while (collision && n > 1);
+               }
+       }
+
+       /**
+        * Given n points to be placed equidistantly and equiangularly on a polygon
+        * which has a chord of length, b, find the distance, h, from the midpoint
+        * of the chord for the center of polygon. Positive values mean the center
+        * is within the polygon and the chord, whereas negative values mean the
+        * center is outside the chord. Also, the radial angle for each polygon side
+        * is returned in theta.
+        * 
+        * The procedure uses a bisection algorithm to find the correct value for
+        * the center. Two equations are solved, the angles around the center must
+        * add to 2*Math.PI, and the sides of the polygon excluding the chord must
+        * have a length of 1.
+        * 
+        * @throws ExceptionNAViewAlgorithm
+        */
+       private void find_center_for_arc(double n, double b)
+                       throws ExceptionNAViewAlgorithm {
+               if (debug)
+                       System.out.println("  Find_center_for_arc");
+               double h, hhi, hlow, r, disc, theta, e, phi;
+               int iter;
+
+               hhi = (n + 1.0) / Math.PI;
+               // changed to prevent div by zero if (ih)
+               hlow = -hhi - b / (n + 1.000001 - b);
+               if (b < 1)
+                       // otherwise we might fail below (ih)
+                       hlow = 0;
+               iter = 0;
+               do {
+                       h = (hhi + hlow) / 2.0;
+                       r = Math.sqrt(h * h + b * b / 4.0);
+                       // if (r<0.5) {r = 0.5; h = 0.5*Math.sqrt(1-b*b);}
+                       disc = 1.0 - 0.5 / (r * r);
+                       if (Math.abs(disc) > 1.0) {
+                               throw new ExceptionNAViewAlgorithm(
+                                               "Unexpected large magnitude discriminant = " + disc
+                                                               + " " + r);
+                       }
+                       theta = Math.acos(disc);
+                       // theta = 2*Math.acos(Math.sqrt(1-1/(4*r*r)));
+                       phi = Math.acos(h / r);
+                       e = theta * (n + 1) + 2 * phi - 2 * Math.PI;
+                       if (e > 0.0) {
+                               hlow = h;
+                       } else {
+                               hhi = h;
+                       }
+               } while (Math.abs(e) > 0.0001 && ++iter < MAXITER);
+               if (iter >= MAXITER) {
+                       if (noIterationFailureYet) {
+                               warningEmition("Iteration failed in find_center_for_arc");
+                               noIterationFailureYet = false;
+                       }
+                       h = 0.0;
+                       theta = 0.0;
+               }
+               _h = h;
+               angleinc = theta;
+       }
+
+       private double minf2(double x1, double x2) {
+               return ((x1) < (x2)) ? (x1) : (x2);
+       }
+
+       private double maxf2(double x1, double x2) {
+               return ((x1) > (x2)) ? (x1) : (x2);
+       }
+
+       public void warningEmition(String warningMessage) throws ExceptionNAViewAlgorithm {
+               throw (new ExceptionNAViewAlgorithm(warningMessage));
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/Radloop.java b/srcjar/fr/orsay/lri/varna/models/naView/Radloop.java
new file mode 100644 (file)
index 0000000..1fc775b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+public class Radloop {
+       private double radius;
+       private int loopnumber;
+       private Radloop next, prev;
+
+       public double getRadius() {
+               return radius;
+       }
+
+       public void setRadius(double radius) {
+               this.radius = radius;
+       }
+
+       public int getLoopnumber() {
+               return loopnumber;
+       }
+
+       public void setLoopnumber(int loopnumber) {
+               this.loopnumber = loopnumber;
+       }
+
+       public Radloop getNext() {
+               return next;
+       }
+
+       public void setNext(Radloop next) {
+               this.next = next;
+       }
+
+       public Radloop getPrev() {
+               return prev;
+       }
+
+       public void setPrev(Radloop prev) {
+               this.prev = prev;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/naView/Region.java b/srcjar/fr/orsay/lri/varna/models/naView/Region.java
new file mode 100644 (file)
index 0000000..7633935
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.naView;
+
+public class Region {
+       private int _start1, _end1, _start2, _end2;
+
+       public int getStart1() {
+               return _start1;
+       }
+
+       public void setStart1(int start1) {
+               this._start1 = start1;
+       }
+
+       public int getEnd1() {
+               return _end1;
+       }
+
+       public void setEnd1(int end1) {
+               this._end1 = end1;
+       }
+
+       public int getStart2() {
+               return _start2;
+       }
+
+       public void setStart2(int start2) {
+               this._start2 = start2;
+       }
+
+       public int getEnd2() {
+               return _end2;
+       }
+
+       public void setEnd2(int end2) {
+               this._end2 = end2;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/DrawRNATemplate.java b/srcjar/fr/orsay/lri/varna/models/rna/DrawRNATemplate.java
new file mode 100644 (file)
index 0000000..37b41b8
--- /dev/null
@@ -0,0 +1,1421 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.geom.ComputeArcCenter;
+import fr.orsay.lri.varna.models.geom.ComputeEllipseAxis;
+import fr.orsay.lri.varna.models.geom.CubicBezierCurve;
+import fr.orsay.lri.varna.models.geom.HalfEllipse;
+import fr.orsay.lri.varna.models.geom.LinesIntersect;
+import fr.orsay.lri.varna.models.geom.MiscGeom;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateCurveMethod;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateMethod;
+import fr.orsay.lri.varna.models.templates.RNANodeValueTemplate;
+import fr.orsay.lri.varna.models.templates.RNANodeValueTemplateBasePair;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplateAlign;
+import fr.orsay.lri.varna.models.templates.RNATemplateDrawingAlgorithmException;
+import fr.orsay.lri.varna.models.templates.RNATemplateMapping;
+import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
+import fr.orsay.lri.varna.models.templates.RNATemplate.In1Is;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+import fr.orsay.lri.varna.models.treealign.Tree;
+
+public class DrawRNATemplate {
+       private RNA rna;
+       private RNATemplateMapping mapping;
+       private ArrayList<ModeleBase> _listeBases;
+       
+       
+       public DrawRNATemplate(RNA rna) {
+               this.rna = rna;
+               this._listeBases = rna.getListeBases();
+       }
+       
+       public RNATemplateMapping getMapping() {
+               return mapping;
+       }
+       
+       
+       
+       /**
+        * Draw this RNA like the given template.
+        * The helixLengthAdjustmentMethod argument tells what to do in case
+        * some helices are of a different length in the template and the
+        * actual helix. See class DrawRNATemplateMethod above for possible values.
+        * @param straightBulges 
+        */
+       public void drawRNATemplate(
+                       RNATemplate template,
+                       VARNAConfig conf,
+                       DrawRNATemplateMethod helixLengthAdjustmentMethod,
+                       DrawRNATemplateCurveMethod curveMethod, boolean straightBulges)
+       throws RNATemplateDrawingAlgorithmException {
+               
+               // debug
+//             try {
+//                     RNA perfectMatchingRNA = template.toRNA();
+//                     System.out.println("An RNA that would perfectly match this template would be:");
+//                     System.out.println(perfectMatchingRNA.getStructDBN());
+//             } catch (ExceptionInvalidRNATemplate e) {
+//                     e.printStackTrace();
+//             }
+               
+               mapping = RNATemplateAlign.mapRNAWithTemplate(rna, template);
+               //System.out.println(mapping.showCompact(this));
+               
+               // debug
+//                     RNATemplateAlign.printMapping(mapping, template, getSeq());
+//                     try {
+//                             TreeGraphviz.treeToGraphvizPostscript(alignment, "alignment_graphviz.ps");
+//                     } catch (IOException e) {
+//                             e.printStackTrace();
+//                     }
+               
+               
+
+               Iterator<RNATemplateElement> iter;
+               double globalIncreaseFactor = 1;
+               Map<RNATemplateHelix, Point2D.Double> translateVectors = null;
+               if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.MAXSCALINGFACTOR) {
+                       // Compute increase factors for helices.
+                       Map<RNATemplateHelix, Double> lengthIncreaseFactor = new HashMap<RNATemplateHelix, Double>();
+                       double maxLengthIncreaseFactor = Double.NEGATIVE_INFINITY;
+                       //RNATemplateHelix maxIncreaseHelix = null;
+                       iter = template.rnaIterator();
+                       while (iter.hasNext()) {
+                               RNATemplateElement element = iter.next();
+                               if (element instanceof RNATemplateHelix
+                                               && mapping.getAncestor(element) != null
+                                               && !lengthIncreaseFactor.containsKey(element)) {
+                                       RNATemplateHelix helix = (RNATemplateHelix) element;
+                                       int[] basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix));
+                                       Arrays.sort(basesInHelixArray);
+                                       double l = computeLengthIncreaseFactor(basesInHelixArray, helix, straightBulges);
+                                       lengthIncreaseFactor.put(helix, l);
+                                       if (l > maxLengthIncreaseFactor) {
+                                               maxLengthIncreaseFactor = l;
+                                               //maxIncreaseHelix = helix;
+                                       }
+                               }
+                       }
+                       
+                       // debug
+                       //System.out.println("Max helix length increase factor = " + maxLengthIncreaseFactor + " reached with helix " + maxIncreaseHelix);;
+                       
+                       globalIncreaseFactor = Math.max(1, maxLengthIncreaseFactor);
+                       
+               } else if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.HELIXTRANSLATE) {
+                       try {
+                               // Now we need to propagate this helices translations
+                               Tree<RNANodeValueTemplate> templateAsTree = template.toTree();
+                               translateVectors = computeHelixTranslations(templateAsTree, mapping, straightBulges);
+                       
+                       } catch (ExceptionInvalidRNATemplate e) {
+                               throw (new RNATemplateDrawingAlgorithmException("ExceptionInvalidRNATemplate: " + e.getMessage()));
+                       }
+               }
+               
+               // Allocate the coords and centers arrays
+               // We create Point2D.Double objects in it but the algorithms
+               // we use may choose to create new Point2D.Double objects or to
+               // modify those created here.
+               Point2D.Double[] coords = new Point2D.Double[_listeBases.size()];
+               Point2D.Double[] centers = new Point2D.Double[_listeBases.size()];
+               double[] angles = new double[_listeBases.size()];
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       coords[i] = new Point2D.Double(0, 0);
+                       centers[i] = new Point2D.Double(0, 0);
+               }
+               
+               boolean computeCoords = true;
+               while (computeCoords) {
+                       computeCoords = false;
+                       // Compute coords and centers
+                       Set<RNATemplateHelix> alreadyDrawnHelixes = new HashSet<RNATemplateHelix>();
+                       RNATemplateHelix lastMappedHelix = null;
+                       EdgeEndPoint howWeGotOutOfLastHelix = null;
+                       int howWeGotOutOfLastHelixBaseIndex = 0;
+                       iter = template.rnaIterator();
+                       RNATemplateElement element = null;
+                       while (iter.hasNext()) {
+                               element = iter.next();
+                               if (element instanceof RNATemplateHelix
+                                               && mapping.getAncestor(element) != null) {
+                                       // We have a mapping between an helix in the RNA sequence
+                                       // and an helix in the template.
+                                       
+                                       RNATemplateHelix helix = (RNATemplateHelix) element;
+                                       boolean firstTimeWeMeetThisHelix;
+                                       int[] basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix));
+                                       Arrays.sort(basesInHelixArray);
+                                       
+                                       // Draw this helix if it has not already been done
+                                       if (!alreadyDrawnHelixes.contains(helix)) {
+                                               firstTimeWeMeetThisHelix = true;
+                                               drawHelixLikeTemplateHelix(basesInHelixArray, helix, coords, centers,angles, globalIncreaseFactor, translateVectors, straightBulges);
+                                               alreadyDrawnHelixes.add(helix);
+                                       } else {
+                                               firstTimeWeMeetThisHelix = false;
+                                       }
+                                       
+                                       EdgeEndPoint howWeGetInCurrentHelix;
+                                       if (firstTimeWeMeetThisHelix) {
+                                               if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) {
+                                                       howWeGetInCurrentHelix = helix.getIn1();
+                                               } else {
+                                                       howWeGetInCurrentHelix = helix.getIn2();
+                                               }
+                                       } else {
+                                               if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) {
+                                                       howWeGetInCurrentHelix = helix.getIn2();
+                                               } else {
+                                                       howWeGetInCurrentHelix = helix.getIn1();
+                                               }
+                                       }
+                                       
+                                       Point2D.Double P0 = new Point2D.Double();
+                                       Point2D.Double P3 = new Point2D.Double();
+                                       
+                                       if (lastMappedHelix != null) {
+                                               // Now draw the RNA sequence (possibly containing helixes)
+                                               // between the last template drawn helix and this one.
+       
+                                               if (lastMappedHelix == helix) {
+                                                       // Last helix is the same as the current one so
+                                                       // nothing matched (or at best a single
+                                                       // non-paired sequence) so we will just
+                                                       // use the Radiate algorithm
+                                                       
+                                                       Point2D.Double helixVector = new Point2D.Double();
+                                                       computeHelixEndPointDirections(howWeGotOutOfLastHelix, helixVector, new Point2D.Double());
+                                                       
+                                                       double angle = MiscGeom.angleFromVector(helixVector);
+                                                       int b1 = basesInHelixArray[basesInHelixArray.length/2 - 1];
+                                                       P0.setLocation(coords[b1]);
+                                                       int b2 = basesInHelixArray[basesInHelixArray.length/2];
+                                                       P3.setLocation(coords[b2]);
+                                                       Point2D.Double loopCenter = new Point2D.Double((P0.x + P3.x)/2, (P0.y + P3.y)/2);
+                                                       rna.drawLoop(b1,
+                                                                        b2,
+                                                                        loopCenter.x,
+                                                                        loopCenter.y,
+                                                                        angle,
+                                                                        coords,
+                                                                        centers,
+                                                                        angles,
+                                                                        straightBulges);
+                                                       // If the helix is flipped, we need to compute the symmetric
+                                                       // of the whole loop.
+                                                       if (helix.isFlipped()) {
+                                                               symmetric(loopCenter, helixVector, coords, b1, b2);
+                                                               symmetric(loopCenter, helixVector, centers, b1, b2);
+                                                       }
+                                               } else {
+                                                       // No helices matched between the last helix and
+                                                       // the current one, so we draw what is between
+                                                       // using the radiate algorithm but on the Bezier curve.
+                                                       
+                                                       int b1 = howWeGotOutOfLastHelixBaseIndex;
+                                                       int b2 = firstTimeWeMeetThisHelix ? basesInHelixArray[0] : basesInHelixArray[basesInHelixArray.length/2];
+                                                       P0.setLocation(coords[b1]);
+                                                       P3.setLocation(coords[b2]);
+                                                       
+                                                       Point2D.Double P1, P2;
+                                                       
+                                                       if (howWeGotOutOfLastHelix.getOtherElement() instanceof RNATemplateUnpairedSequence
+                                                                       && howWeGetInCurrentHelix.getOtherElement() instanceof RNATemplateUnpairedSequence) {
+                                                               // We will draw the bases on a Bezier curve
+                                                               P1 = new Point2D.Double();
+                                                               computeBezierTangentVectorTarget(howWeGotOutOfLastHelix, P0, P1);
+                                                               
+                                                               P2 = new Point2D.Double();
+                                                               computeBezierTangentVectorTarget(howWeGetInCurrentHelix, P3, P2);
+                                                       } else {
+                                                               // We will draw the bases on a straight line between P0 and P3
+                                                               P1 = null;
+                                                               P2 = null;
+                                                       }
+                                                       
+                                                       drawAlongCurve(b1, b2, P0, P1, P2, P3, coords, centers, angles, curveMethod, lastMappedHelix.isFlipped(), straightBulges);
+                                               }
+                                               
+                                       } else if (basesInHelixArray[0] > 0) {
+                                               // Here we draw what is before the first mapped helix.
+       
+                                               RNATemplateUnpairedSequence templateSequence;
+                                               // Try to find our template sequence as the mapped element of base 0
+                                               RNATemplateElement templateSequenceCandidate = mapping.getPartner(0);
+                                               if (templateSequenceCandidate != null
+                                                               && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                                       templateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                                               } else {
+                                                       // Try other idea: first template element if it is a sequence
+                                                       templateSequenceCandidate = template.getFirst();
+                                                       if (templateSequenceCandidate != null
+                                                                       && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                                               templateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                                                       } else {
+                                                               // We don't know where to start
+                                                               templateSequence = null;
+                                                       }
+                                               }
+                                               
+                                               int b1 = 0;
+                                               int b2 = firstTimeWeMeetThisHelix ? basesInHelixArray[0] : basesInHelixArray[basesInHelixArray.length/2];
+                                               P3.setLocation(coords[b2]);
+                                               
+                                               if (templateSequence != null) {
+                                                       coords[0].setLocation(templateSequence.getVertex5());
+                                                       coords[0].x *= globalIncreaseFactor;
+                                                       coords[0].y *= globalIncreaseFactor;
+                                               } else {
+                                                       // Put b1 at an ideal distance from b2, using the "flat exterior loop" method
+                                                       double idealLength = computeStraightLineIdealLength(b1, b2);
+                                                       Point2D.Double j = new Point2D.Double();
+                                                       if (howWeGetInCurrentHelix != null) {
+                                                               computeHelixEndPointDirections(howWeGetInCurrentHelix, new Point2D.Double(), j);
+                                                       } else {
+                                                               j.setLocation(1, 0);
+                                                       }
+                                                       coords[b1].setLocation(coords[b2].x + j.x*idealLength, coords[b2].y + j.y*idealLength);
+                                               }
+                                               P0.setLocation(coords[0]);
+                                               
+                                               Point2D.Double P1, P2;
+                                               
+                                               if (howWeGetInCurrentHelix.getOtherElement() instanceof RNATemplateUnpairedSequence
+                                                               && templateSequence != null) {
+                                                       // We will draw the bases on a Bezier curve
+                                                       P1 = new Point2D.Double();
+                                                       computeBezierTangentVectorTarget(templateSequence.getIn(), P0, P1);
+                                                       
+                                                       P2 = new Point2D.Double();
+                                                       computeBezierTangentVectorTarget(howWeGetInCurrentHelix, P3, P2);
+                                               } else {
+                                                       // We will draw the bases on a straight line between P0 and P3
+                                                       P1 = null;
+                                                       P2 = null;
+                                               }
+                                               
+                                               drawAlongCurve(b1, b2, P0, P1, P2, P3, coords, centers, angles, curveMethod, false, straightBulges);
+                                       }
+                                       
+                                       lastMappedHelix = helix;
+                                       howWeGotOutOfLastHelix = howWeGetInCurrentHelix.getNextEndPoint();
+                                       if (firstTimeWeMeetThisHelix) {
+                                               howWeGotOutOfLastHelixBaseIndex = basesInHelixArray[basesInHelixArray.length/2-1];
+                                       } else {
+                                               howWeGotOutOfLastHelixBaseIndex = basesInHelixArray[basesInHelixArray.length-1];
+                                       }
+                               }
+                       } // end template iteration
+                       
+                       
+                       // Now we need to draw what is after the last mapped helix.
+                       if (howWeGotOutOfLastHelixBaseIndex < coords.length-1
+                                       && element != null
+                                       && coords.length > 1) {
+                               
+                               RNATemplateUnpairedSequence beginTemplateSequence = null;
+                               if (lastMappedHelix == null) {
+                                       // No helix at all matched between the template and RNA!
+                                       // So the sequence we want to draw is the full RNA.
+                                       
+                                       // Try to find our template sequence as the mapped element of base 0
+                                       RNATemplateElement templateSequenceCandidate = mapping.getPartner(0);
+                                       if (templateSequenceCandidate != null
+                                                       && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                               beginTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                                       } else {
+                                               // Try other idea: first template element if it is a sequence
+                                               templateSequenceCandidate = template.getFirst();
+                                               if (templateSequenceCandidate != null
+                                                               && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                                       beginTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                                               } else {
+                                                       // We don't know where to start
+                                                       beginTemplateSequence = null;
+                                               }
+                                       }
+                                       
+                                       if (beginTemplateSequence != null) {
+                                               coords[0].setLocation(beginTemplateSequence.getVertex5());
+                                               coords[0].x *= globalIncreaseFactor;
+                                               coords[0].y *= globalIncreaseFactor;
+                                       }
+                                       
+                               }
+                               
+                               RNATemplateUnpairedSequence endTemplateSequence;
+                               // Try to find our template sequence as the mapped element of last base
+                               RNATemplateElement templateSequenceCandidate = mapping.getPartner(coords.length-1);
+                               if (templateSequenceCandidate != null
+                                               && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                       endTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                               } else {
+                                       // Try other idea: last template element if it is a sequence
+                                       templateSequenceCandidate = element;
+                                       if (templateSequenceCandidate != null
+                                                       && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) {
+                                               endTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate;
+                                       } else {
+                                               // We don't know where to end
+                                               endTemplateSequence = null;
+                                       }
+                               }
+                               
+                               int b1 = howWeGotOutOfLastHelixBaseIndex;
+                               int b2 = coords.length - 1;
+                               
+                               if (endTemplateSequence != null) {
+                                       coords[b2].setLocation(endTemplateSequence.getVertex3());
+                                       coords[b2].x *= globalIncreaseFactor;
+                                       coords[b2].y *= globalIncreaseFactor;
+                               } else {
+                                       // Put b2 at an ideal distance from b1, using the "flat exterior loop" method
+                                       double idealLength = computeStraightLineIdealLength(b1, b2);
+                                       Point2D.Double j = new Point2D.Double();
+                                       if (howWeGotOutOfLastHelix != null) {
+                                               computeHelixEndPointDirections(howWeGotOutOfLastHelix, new Point2D.Double(), j);
+                                       } else {
+                                               j.setLocation(1, 0);
+                                       }
+                                       coords[b2].setLocation(coords[b1].x + j.x*idealLength, coords[b1].y + j.y*idealLength);
+                               }
+
+                               
+                               Point2D.Double P0 = new Point2D.Double();
+                               Point2D.Double P3 = new Point2D.Double();
+                               
+                               P0.setLocation(coords[b1]);
+                               P3.setLocation(coords[b2]);
+                               
+                               Point2D.Double P1, P2;
+                               
+                               if (howWeGotOutOfLastHelix != null
+                                               && howWeGotOutOfLastHelix.getOtherElement() instanceof RNATemplateUnpairedSequence
+                                               && endTemplateSequence != null) {
+                                       // We will draw the bases on a Bezier curve
+                                       P1 = new Point2D.Double();
+                                       computeBezierTangentVectorTarget(howWeGotOutOfLastHelix, P0, P1);
+                                       
+                                       P2 = new Point2D.Double();
+                                       computeBezierTangentVectorTarget(endTemplateSequence.getOut(), P3, P2);
+                               } else if (lastMappedHelix == null
+                                               && beginTemplateSequence != null
+                                               && endTemplateSequence != null) {
+                                       // We will draw the bases on a Bezier curve
+                                       P1 = new Point2D.Double();
+                                       computeBezierTangentVectorTarget(beginTemplateSequence.getIn(), P0, P1);
+                                       
+                                       P2 = new Point2D.Double();
+                                       computeBezierTangentVectorTarget(endTemplateSequence.getOut(), P3, P2);
+                               } else {
+                                       // We will draw the bases on a straight line between P0 and P3
+                                       P1 = null;
+                                       P2 = null;
+                               }
+                               
+                               drawAlongCurve(b1, b2, P0, P1, P2, P3, coords, centers, angles, curveMethod, lastMappedHelix != null ? lastMappedHelix.isFlipped() : false, straightBulges);
+                       
+                       }
+                       
+                       
+                       if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.NOINTERSECT && coords.length > 3) {
+                               // Are we happy with this value of globalIncreaseFactor?
+                               Line2D.Double[] lines = new Line2D.Double[coords.length-1];
+                               for (int i=0; i<coords.length-1; i++) {
+                                       lines[i] = new Line2D.Double(coords[i], coords[i+1]);
+                               }
+                               int intersectLines = 0;
+                               for (int i=0; i<lines.length; i++) {
+                                       for (int j=i+2; j<lines.length; j++) {
+                                               if (LinesIntersect.linesIntersect(lines[i], lines[j])) {
+                                                       intersectLines++;
+                                               }
+                                       }
+                               }
+                               // If no intersection we keep this globalIncreaseFactor value
+                               if (intersectLines > 0) {
+                                       // Don't increase more than a maximum value
+                                       if (globalIncreaseFactor < 3) {
+                                               globalIncreaseFactor += 0.1;
+                                               //System.out.println("globalIncreaseFactor increased to " + globalIncreaseFactor);
+                                               // Compute the drawing again
+                                               computeCoords = true;
+                                       }
+                               }
+                       }
+                       
+               }
+               
+               // debug
+               if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.MAXSCALINGFACTOR
+                               || helixLengthAdjustmentMethod == DrawRNATemplateMethod.NOINTERSECT) {
+                       //System.out.println("globalIncreaseFactor = " + globalIncreaseFactor);
+               }
+               
+               // Now we actually move the bases, according to arrays coords and centers
+               // and taking in account the space between bases parameter.
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       _listeBases.get(i).setCoords(
+                                       new Point2D.Double(coords[i].x * conf._spaceBetweenBases,
+                                                       coords[i].y * conf._spaceBetweenBases));
+                       _listeBases.get(i).setCenter(
+                                       new Point2D.Double(centers[i].x * conf._spaceBetweenBases,
+                                                       centers[i].y * conf._spaceBetweenBases));
+               }
+       
+       }
+       
+       
+       
+       
+       
+       
+       /**
+        * IN: Argument helixEndPoint is an IN argument (will be read),
+        * and must contain an helix edge endpoint.
+        * 
+        * The other arguments are OUT arguments
+        * (must be existing objects, content will be overwritten).
+        * 
+        * OUT: The i argument will contain a vector colinear to the vector
+        * from the helix startPosition to endPosition or the opposite
+        * depending on there the endpoint is (the endpoint will be on the
+        * destination side of the vector). ||i|| = 1
+        * 
+        * OUT: The j vector will contain an vector that is colinear
+        * to the last/first base pair connection on the side of this endpoint.
+        * The vector will be oriented to the side of the given endpoint.
+        * ||j|| = 1
+        */
+       private void computeHelixEndPointDirections(
+                       EdgeEndPoint helixEndPoint, // IN
+                       Point2D.Double i, // OUT
+                       Point2D.Double j // OUT
+                       ) {
+               RNATemplateHelix helix = (RNATemplateHelix) helixEndPoint.getElement();
+               Point2D.Double startpos = helix.getStartPosition();
+               Point2D.Double endpos = helix.getEndPosition();
+               Point2D.Double helixVector = new Point2D.Double();
+               switch (helixEndPoint.getPosition()) {
+               case IN1:
+               case OUT2:
+                       helixVector.x = startpos.x - endpos.x;
+                       helixVector.y = startpos.y - endpos.y;
+                       break;
+               case IN2:
+               case OUT1:
+                       helixVector.x = endpos.x - startpos.x;
+                       helixVector.y = endpos.y - startpos.y;
+                       break;
+               }
+               double helixVectorLength = Math.hypot(helixVector.x, helixVector.y);
+               // i is the vector which is colinear to helixVector and such that ||i|| = 1
+               i.x = helixVector.x / helixVectorLength;
+               i.y = helixVector.y / helixVectorLength;
+               // Find j such that it is orthogonal to i, ||j|| = 1
+               // and j goes to the side where the sequence will be connected
+               switch (helixEndPoint.getPosition()) {
+               case IN1:
+               case IN2:
+                       // rotation of +pi/2
+                       j.x = - i.y;
+                       j.y =   i.x;
+                       break;
+               case OUT1:
+               case OUT2:
+                       // rotation of -pi/2
+                       j.x =   i.y;
+                       j.y = - i.x;
+                       break;
+               }
+               if (helix.isFlipped()) {
+                       j.x = - j.x;
+                       j.y = - j.y;
+               }
+
+       }
+       
+       /**
+        * A cubic Bezier curve can be defined by 4 points,
+        * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves
+        * For each of the curve end points, there is the last/first point of the
+        * curve and a point which gives the direction and length of the tangent
+        * vector on that side. This two points are respectively curveEndPoint
+        * and curveVectorOtherPoint.
+        * IN:  Argument helixVector is the vector formed by the helix,
+        *      in the right direction for our sequence.
+        * IN:  Argument curveEndPoint is the position of the endpoint on the helix.
+        * OUT: Argument curveVectorOtherPoint must be allocated
+        *      and the values will be modified.
+        */
+       private void computeBezierTangentVectorTarget(
+                       EdgeEndPoint endPoint,
+                       Point2D.Double curveEndPoint,
+                       Point2D.Double curveVectorOtherPoint)
+                       throws RNATemplateDrawingAlgorithmException {
+               
+               boolean sequenceEndPointIsIn;
+               RNATemplateUnpairedSequence sequence;
+               
+               if (endPoint.getElement() instanceof RNATemplateHelix) {
+                       sequence = (RNATemplateUnpairedSequence) endPoint.getOtherElement();
+                       EdgeEndPointPosition endPointPositionOnHelix = endPoint.getPosition();
+                       switch (endPointPositionOnHelix) {
+                       case IN1:
+                       case IN2:
+                               sequenceEndPointIsIn = false;
+                               break;
+                       default:
+                               sequenceEndPointIsIn = true;
+                       }
+                       
+                       EdgeEndPoint endPointOnHelix =
+                               sequenceEndPointIsIn ?
+                                               sequence.getIn().getOtherEndPoint() :
+                                               sequence.getOut().getOtherEndPoint();
+                       if (endPointOnHelix == null) {
+                               throw (new RNATemplateDrawingAlgorithmException("Sequence is not connected to an helix."));
+                       }
+               } else {
+                       // The endpoint is on an unpaired sequence.
+                       sequence = (RNATemplateUnpairedSequence) endPoint.getElement();
+                       if (endPoint == sequence.getIn()) {
+                               // endpoint is 5'
+                               sequenceEndPointIsIn = true;
+                       } else {
+                               sequenceEndPointIsIn = false;
+                       }
+               }
+
+               double l =
+                       sequenceEndPointIsIn ?
+                               sequence.getInTangentVectorLength() :
+                               sequence.getOutTangentVectorLength();
+               
+               // Compute the absolute angle our line makes to the helix
+               double theta =
+                       sequenceEndPointIsIn ?
+                               sequence.getInTangentVectorAngle() :
+                               sequence.getOutTangentVectorAngle();
+               
+               // Compute v, the tangent vector of the Bezier curve
+               Point2D.Double v = new Point2D.Double();
+               v.x = l * Math.cos(theta);
+               v.y = l * Math.sin(theta);
+               curveVectorOtherPoint.x = curveEndPoint.x + v.x;
+               curveVectorOtherPoint.y = curveEndPoint.y + v.y;
+       }
+       
+
+       /**
+        * Compute (actual helix length / helix length in template).
+        * @param straightBulges 
+        */
+       private double computeLengthIncreaseFactor(
+                       int[] basesInHelixArray,  // IN
+                       RNATemplateHelix helix,    // IN
+                       boolean straightBulges
+                       ) {
+               double templateLength = computeHelixTemplateLength(helix);
+               double realLength = computeHelixRealLength(basesInHelixArray,straightBulges);
+               return realLength / templateLength;
+       }
+       
+       /**
+        * Compute (actual helix vector - helix vector in template).
+        */
+       private Point2D.Double computeLengthIncreaseDelta(
+                       int[] basesInHelixArray,  // IN
+                       RNATemplateHelix helix,   // IN
+                       boolean straightBulges
+                       ) {
+               double templateLength = computeHelixTemplateLength(helix);
+               double realLength = computeHelixRealLength(basesInHelixArray,straightBulges);
+               Point2D.Double i = new Point2D.Double();
+               computeTemplateHelixVectors(helix, null, i, null);
+               return new Point2D.Double(i.x*(realLength-templateLength), i.y*(realLength-templateLength));
+       }
+       
+       /**
+        * Compute helix interesting vectors from template helix.
+        * @param helix The template helix you want to compute the vectors from.
+        * @param o This point coordinates will be set the origin of the helix (or not if null),
+        *          ie. the point in the middle of the base pair with the two most extreme bases.
+        * @param i Will be set to the normalized helix vector. (nothing done if null)
+        * @param j Will be set to the normalized helix base pair vector (5' -> 3'). (nothing done if null)
+        */
+       private void computeTemplateHelixVectors(
+                       RNATemplateHelix helix,  // IN
+                       Point2D.Double o,        // OUT
+                       Point2D.Double i,        // OUT
+                       Point2D.Double j         // OUT
+                       ) {
+               Point2D.Double startpos, endpos;
+               if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) {
+                       startpos = helix.getStartPosition();
+                       endpos = helix.getEndPosition();
+               } else {
+                       endpos = helix.getStartPosition();
+                       startpos = helix.getEndPosition();
+               }
+               if (o != null) {
+                       o.x = startpos.x;
+                       o.y = startpos.y;
+               }
+               if (i != null || j != null) {
+                       // (i_x,i_y) is the vector between two consecutive bases of the same side of an helix
+                       if (i == null)
+                               i = new Point2D.Double();
+                       i.x = (endpos.x - startpos.x);
+                       i.y = (endpos.y - startpos.y);
+                       double i_original_norm = Math.hypot(i.x, i.y);
+                       // change its norm to 1
+                       i.x = i.x / i_original_norm;
+                       i.y = i.y / i_original_norm;
+                       if (j != null) {
+                               j.x = - i.y;
+                               j.y =   i.x;
+                               if (helix.isFlipped()) {
+                                       j.x = - j.x;
+                                       j.y = - j.y;
+                               }
+                               double j_original_norm = Math.hypot(j.x, j.y);
+                               // change (j_x,j_y) so that its norm is 1
+                               j.x = j.x / j_original_norm;
+                               j.y = j.y / j_original_norm;
+                       }
+               }
+       }
+       
+       
+       /**
+        * Estimate bulge arc length.
+        */
+       private double estimateBulgeArcLength(int firstBase, int lastBase) {
+               if (firstBase + 1 == lastBase)
+                       return RNA.LOOP_DISTANCE; // there is actually no bulge
+               double len = 0.0;
+               int k = firstBase;
+               while (k < lastBase) {
+                       int l = _listeBases.get(k).getElementStructure();
+                       if (k < l && l < lastBase) {
+                               len += RNA.BASE_PAIR_DISTANCE;
+                               k = l;
+                       } else {
+                               len += RNA.LOOP_DISTANCE;
+                               k++;
+                       }
+               }
+               return len;
+       }
+       
+       
+       /**
+        * Estimate bulge width, the given first and last bases must be those in the helix.
+        */
+       private double estimateBulgeWidth(int firstBase, int lastBase) {
+               double len = estimateBulgeArcLength(firstBase, lastBase);
+               return 2 * (len / Math.PI);
+       }
+       
+       
+       /**
+        * Get helix length in template.
+        */
+       private double computeHelixTemplateLength(RNATemplateHelix helix) {
+               return Math.hypot(helix.getStartPosition().x - helix.getEndPosition().x,
+                               helix.getStartPosition().y - helix.getEndPosition().y);
+       }
+       
+       
+       /**
+        * Compute helix actual length (as drawHelixLikeTemplateHelix() would draw it).
+        */
+       private double computeHelixRealLength(int[] basesInHelixArray, boolean straightBulges) {
+               return drawHelixLikeTemplateHelix(basesInHelixArray, null, null, null, null, 0, null,straightBulges);
+       }
+       
+       
+       /**
+        * Result type of countUnpairedLine(), see below.
+        */
+       private static class UnpairedLineCounts {
+               public int nBP, nLD, total;
+       }
+       /**
+        * If we are drawing an unpaired region that may contains helices,
+        * and we are drawing it on a line (curve or straight, doesn't matter),
+        * how many intervals should have a base-pair length (start of an helix)
+        * and how many should have a consecutive unpaired bases length?
+        * Returns an array with three elements:
+        * - answer to first question
+        * - answer to second question
+        * - sum of both, ie. total number of intervals
+        *   (this is NOT lastBase-firstBase because bases deep in helices do not count)
+        */
+       private UnpairedLineCounts countUnpairedLine(int firstBase, int lastBase) {
+               UnpairedLineCounts counts = new UnpairedLineCounts();
+               int nBP = 0;
+               int nLD = 0;
+               {
+                       int b = firstBase;
+                       while (b < lastBase) {
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       nBP++;
+                                       b = l;
+                               } else {
+                                       nLD++;
+                                       b++;
+                               }
+                       }
+               }
+               counts.nBP = nBP;
+               counts.nLD = nLD;
+               counts.total = nBP + nLD;
+               return counts;
+       }
+       
+       
+       /**
+        * Draw the given helix (given as a *SORTED* array of indexes)
+        * like defined in the given template helix.
+        * OUT: The bases positions are not changed in fact,
+        *      instead the coords and centers arrays are modified.
+        * IN:  The helix origin position is multiplied by scaleHelixOrigin
+        *      and translateVectors.get(helix) is added.
+        * RETURN VALUE:
+        *      The length of the drawn helix.
+        * @param straightBulges 
+        * 
+        */
+       private double drawHelixLikeTemplateHelix(
+                       int[] basesInHelixArray,  // IN
+                       RNATemplateHelix helix,   // IN  (optional, ie. may be null)
+                       Point2D.Double[] coords,  // OUT (optional, ie. may be null)
+                       Point2D.Double[] centers, // OUT (optional, ie. may be null)
+                       double[] angles,  // OUT 
+                       double scaleHelixOrigin,  // IN
+                       Map<RNATemplateHelix, Point2D.Double> translateVectors // IN (optional, ie. may be null)
+, boolean straightBulges
+                       ) {
+               int n = basesInHelixArray.length / 2;
+               if (n == 0)
+                       return 0;
+                // Default values when not template helix is provided:
+               Point2D.Double o = new Point2D.Double(0, 0);
+               Point2D.Double i = new Point2D.Double(1, 0);
+               Point2D.Double j = new Point2D.Double(0, 1);
+               boolean flipped = false;
+               if (helix != null) {
+                       computeTemplateHelixVectors(helix, o, i, j);
+                       flipped = helix.isFlipped();
+               }
+               Point2D.Double li = new Point2D.Double(i.x*RNA.LOOP_DISTANCE, i.y*RNA.LOOP_DISTANCE);
+               // We want o to be the point where the first base (5' end) is
+               o.x = (o.x - j.x * RNA.BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin;
+               o.y = (o.y - j.y * RNA.BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin;
+               if (translateVectors != null && translateVectors.containsKey(helix)) {
+                       Point2D.Double v = translateVectors.get(helix);
+                       o.x = o.x + v.x;
+                       o.y = o.y + v.y;
+               }
+               
+               // We need this array so that we can store positions even if coords == null
+               Point2D.Double[] helixBasesPositions = new Point2D.Double[basesInHelixArray.length];
+               for (int k=0; k<helixBasesPositions.length; k++) {
+                       helixBasesPositions[k] = new Point2D.Double();  
+               }
+               Point2D.Double accDelta = new Point2D.Double(0, 0);
+               for (int k=0; k<n; k++) {
+                       int kp = 2*n-k-1;
+                       Point2D.Double p1 = helixBasesPositions[k]; // we assign the point *reference*
+                       Point2D.Double p2 = helixBasesPositions[kp];
+                       // Do we have a bulge between previous base pair and this one?
+                       boolean bulge = k >= 1 && (basesInHelixArray[k] != basesInHelixArray[k-1] + 1
+                                                          || basesInHelixArray[kp+1] != basesInHelixArray[kp] + 1);
+                       if (k >= 1) {
+                               if (basesInHelixArray[k] < basesInHelixArray[k-1]
+                                   || basesInHelixArray[kp+1] < basesInHelixArray[kp]) {
+                                       throw new Error("Internal bug: basesInHelixArray must be sorted");
+                               }
+                               if (bulge) {
+                                       // Estimate a good distance (delta) between the previous base pair and this one
+                                       double delta1 = estimateBulgeWidth(basesInHelixArray[k-1], basesInHelixArray[k]);
+                                       double delta2 = estimateBulgeWidth(basesInHelixArray[kp], basesInHelixArray[kp+1]);
+                                       // The biggest bulge defines the width
+                                       double delta = Math.max(delta1, delta2);
+
+                                       if (coords != null) {
+                                               // Now, where do we put the bases that are part of the bulge?
+                                               for (int side=0; side<2; side++) {
+                                                       Point2D.Double pstart = new Point2D.Double();
+                                                       Point2D.Double pend = new Point2D.Double();
+                                                       Point2D.Double bisectVect = new Point2D.Double();
+                                                       Point2D.Double is = new Point2D.Double();
+                                                       int firstBase, lastBase;
+                                                       double alphasign = flipped ? -1 : 1;
+                                                       if (side == 0) {
+                                                               firstBase = basesInHelixArray[k-1];
+                                                               lastBase  = basesInHelixArray[k];
+                                                               pstart.setLocation(o.x + accDelta.x,
+                                                                                  o.y + accDelta.y);
+                                                               pend.setLocation(o.x + accDelta.x + i.x*delta,
+                                                                                o.y + accDelta.y + i.y*delta);
+                                                               bisectVect.setLocation(-j.x, -j.y);
+                                                               is.setLocation(i);
+                                                       } else {
+                                                               firstBase = basesInHelixArray[kp];
+                                                               lastBase  = basesInHelixArray[kp+1];
+                                                               pstart.setLocation(o.x + accDelta.x + i.x*delta + j.x*RNA.BASE_PAIR_DISTANCE,
+                                                                          o.y + accDelta.y + i.y*delta + j.y*RNA.BASE_PAIR_DISTANCE);
+                                                               pend.setLocation(o.x + accDelta.x + j.x*RNA.BASE_PAIR_DISTANCE,
+                                                                                o.y + accDelta.y + j.y*RNA.BASE_PAIR_DISTANCE);
+
+                                                               bisectVect.setLocation(j);
+                                                               is.setLocation(-i.x, -i.y);
+                                                       }
+                                                       double arclen = estimateBulgeArcLength(firstBase, lastBase);
+                                                       double centerOnBisect = ComputeArcCenter.computeArcCenter(delta, arclen);
+                                                       
+                                                       // Should we draw the base on an arc or simply use a line?
+                                                       if (centerOnBisect > -1000) {
+                                                               Point2D.Double center = new Point2D.Double(pstart.x + is.x*delta/2 + bisectVect.x*centerOnBisect,
+                                                                                                          pstart.y + is.y*delta/2 + bisectVect.y*centerOnBisect);
+                                                               int b = firstBase;
+                                                               double len = 0;
+                                                               double r = Math.hypot(pstart.x - center.x, pstart.y - center.y);
+                                                               double alpha0 = MiscGeom.angleFromVector(pstart.x - center.x, pstart.y - center.y);
+                                                               while (b < lastBase) {
+                                                                       int l = _listeBases.get(b).getElementStructure();
+                                                                       if (b < l && l < lastBase) {
+                                                                               len += RNA.BASE_PAIR_DISTANCE;
+                                                                               b = l;
+                                                                       } else {
+                                                                               len += RNA.LOOP_DISTANCE;
+                                                                               b++;
+                                                                       }
+                                                                       if (b < lastBase) {
+                                                                               coords[b].x = center.x + r*Math.cos(alpha0 + alphasign*len/r);
+                                                                               coords[b].y = center.y + r*Math.sin(alpha0 + alphasign*len/r);
+                                                                       }
+                                                               }
+                                                       } else {
+                                                               // Draw on a line
+                                                               
+                                                               // Distance between paired bases cannot be changed
+                                                               // (imposed by helix width) but distance between other
+                                                               // bases can be adjusted.
+                                                               UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+                                                               double LD = Math.max((delta - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+                                                               //System.out.println("nBP=" + nBP + " nLD=" + nLD);
+                                                               double len = 0;
+                                                               {
+                                                                       int b = firstBase;
+                                                                       while (b < lastBase) {
+                                                                               int l = _listeBases.get(b).getElementStructure();
+                                                                               if (b < l && l < lastBase) {
+                                                                                       len += RNA.BASE_PAIR_DISTANCE;
+                                                                                       b = l;
+                                                                               } else {
+                                                                                       len += LD;
+                                                                                       b++;
+                                                                               }
+                                                                               if (b < lastBase) {
+                                                                                       coords[b].x = pstart.x + is.x*len;
+                                                                                       coords[b].y = pstart.y + is.y*len;
+                                                                               }
+                                                                       }
+                                                               }
+                                                               //System.out.println("len=" + len + " delta=" + delta + " d(pstart,pend)=" + Math.hypot(pend.x-pstart.x, pend.y-pstart.y));
+                                                       }
+                                                       
+                                                       // Does the bulge contain an helix?
+                                                       // If so, use drawLoop() to draw it.
+                                                       {
+                                                               int b = firstBase;
+                                                               while (b < lastBase) {
+                                                                       int l = _listeBases.get(b).getElementStructure();
+                                                                       if (b < l && l < lastBase) {
+                                                                               // Helix present in bulge
+                                                                               Point2D.Double b1pos = coords[b];
+                                                                               Point2D.Double b2pos = coords[l];
+                                                                               double beta = MiscGeom.angleFromVector(b2pos.x - b1pos.x, b2pos.y - b1pos.y) - Math.PI / 2 + (flipped ? Math.PI : 0);
+                                                                               Point2D.Double loopCenter = new Point2D.Double((b1pos.x + b2pos.x)/2, (b1pos.y + b2pos.y)/2);
+                                                                               rna.drawLoop(b,
+                                                                                                l,
+                                                                                                loopCenter.x,
+                                                                                                loopCenter.y,
+                                                                                                beta,
+                                                                                                coords,
+                                                                                                centers,
+                                                                                                angles,
+                                                                                                straightBulges);
+                                                                               // If the helix is flipped, we need to compute the symmetric
+                                                                               // of the whole loop.
+                                                                               if (helix.isFlipped()) {
+                                                                                       Point2D.Double v = new Point2D.Double(Math.cos(beta), Math.sin(beta));
+                                                                                       symmetric(loopCenter, v, coords, b, l);
+                                                                                       symmetric(loopCenter, v, centers, b, l);
+                                                                               }
+                                                                               // Continue
+                                                                               b = l;
+                                                                       } else {
+                                                                               b++;
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       
+                                       accDelta.x += i.x*delta;
+                                       accDelta.y += i.y*delta;
+                                       p1.x = o.x + accDelta.x;
+                                       p1.y = o.y + accDelta.y;
+                                       p2.x = p1.x + j.x*RNA.BASE_PAIR_DISTANCE;
+                                       p2.y = p1.y + j.y*RNA.BASE_PAIR_DISTANCE;
+                                       
+                               } else {
+                                       accDelta.x += li.x;
+                                       accDelta.y += li.y;
+                                       p1.x = o.x + accDelta.x;
+                                       p1.y = o.y + accDelta.y;
+                                       p2.x = p1.x + j.x*RNA.BASE_PAIR_DISTANCE;
+                                       p2.y = p1.y + j.y*RNA.BASE_PAIR_DISTANCE;
+                               }
+                       } else {
+                               // First base pair
+                               p1.x = o.x;
+                               p1.y = o.y;
+                               p2.x = p1.x + j.x*RNA.BASE_PAIR_DISTANCE;
+                               p2.y = p1.y + j.y*RNA.BASE_PAIR_DISTANCE;
+                       }
+               }
+               
+               Point2D.Double p1 = helixBasesPositions[0];
+               Point2D.Double p2 = helixBasesPositions[n-1];
+               
+               if (coords != null) {
+                       for (int k=0; k<helixBasesPositions.length; k++) {
+                               coords[basesInHelixArray[k]] = helixBasesPositions[k];
+                       }
+               }
+               
+               return Math.hypot(p2.x-p1.x, p2.y-p1.y);
+       }
+       
+       
+       
+
+       /**
+        * Return ideal length to draw unpaired region from firstBase to lastBase.
+        */
+       private double computeStraightLineIdealLength(int firstBase, int lastBase) {
+               UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+               return RNA.BASE_PAIR_DISTANCE*counts.nBP + RNA.LOOP_DISTANCE*counts.nLD;
+       }
+       
+       /**
+        * Like drawOnBezierCurve(), but on a straight line.
+        */
+       private boolean drawOnStraightLine(
+                       int firstBase,
+                       int lastBase,
+                       Point2D.Double P0,
+                       Point2D.Double P3,
+                       Point2D.Double[] coords,
+                       Point2D.Double[] centers,
+                       boolean cancelIfNotGood) {
+               
+               Point2D.Double vector = new Point2D.Double(P3.x - P0.x, P3.y - P0.y); 
+               double vectorNorm = Math.hypot(vector.x, vector.y);
+
+               UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+               double LD = Math.max((vectorNorm - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+               
+               if (cancelIfNotGood && LD < RNA.LOOP_DISTANCE*0.75) {
+                       // Bases would be drawn "too" near (0.75 is heuristic)
+                       return false;
+               }
+               
+               double len = 0;
+               {
+                       int b = firstBase;
+                       while (b <= lastBase) {
+                               coords[b].x = P0.x + vector.x*len/vectorNorm;
+                               coords[b].y = P0.y + vector.y*len/vectorNorm;
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       len += RNA.BASE_PAIR_DISTANCE;
+                                       b = l;
+                               } else {
+                                       len += LD;
+                                       b++;
+                               }
+                       }
+               }
+               
+               return true;
+       }
+       
+       
+
+
+       /**
+        * A Bezier curve can be defined by four points,
+        * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves
+        * Here we give this four points and an array of bases indexes
+        * (which must be indexes in this RNA sequence) which will be moved
+        * to be on the Bezier curve.
+        * The bases positions are not changed in fact, instead the coords and
+        * centers arrays are modified.
+        * If cancelIfNotGood, draw only if it would look good,
+        * otherwise just return false.
+        */
+       private boolean drawOnBezierCurve(
+                       int firstBase,
+                       int lastBase,
+                       Point2D.Double P0,
+                       Point2D.Double P1,
+                       Point2D.Double P2,
+                       Point2D.Double P3,
+                       Point2D.Double[] coords,
+                       Point2D.Double[] centers,
+                       boolean cancelIfNotGood) {
+               
+               UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+               
+               // Draw the bases of the sequence along a Bezier curve
+               int n = counts.total;
+               
+               // We choose to approximate the Bezier curve by 10*n straight lines.
+               CubicBezierCurve bezier = new CubicBezierCurve(P0, P1, P2, P3, 10*n);
+               double curveLength = bezier.getApproxCurveLength();
+               
+               
+               double LD = Math.max((curveLength - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+               
+               if (cancelIfNotGood && LD < RNA.LOOP_DISTANCE*0.75) {
+                       // Bases would be drawn "too" near (0.75 is heuristic)
+                       return false;
+               }
+               
+               double[] t = new double[n+1];
+
+               {
+                       double len = 0;
+                       int k = 0;
+                       int b = firstBase;
+                       while (b <= lastBase) {
+                               t[k] = len;
+                               k++;
+                               
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       len += RNA.BASE_PAIR_DISTANCE;
+                                       b = l;
+                               } else {
+                                       len += LD;
+                                       b++;
+                               }
+                       }
+               }
+               
+               Point2D.Double[] sequenceBasesCoords = bezier.uniformParam(t);
+               
+               {
+                       int k = 0;
+                       int b = firstBase;
+                       while (b <= lastBase) {
+                               coords[b] = sequenceBasesCoords[k];
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       b = l;
+                               } else {
+                                       b++;
+                               }
+                               k++;
+                       }
+               }
+
+               return true;
+       }
+       
+       
+       
+       private void drawOnEllipse(
+                       int firstBase,
+                       int lastBase,
+                       Point2D.Double P0,
+                       Point2D.Double P3,
+                       Point2D.Double[] coords,
+                       Point2D.Double[] centers,
+                       boolean reverse) {              
+
+               UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+               double halfEllipseLength = RNA.BASE_PAIR_DISTANCE*counts.nBP + RNA.LOOP_DISTANCE*counts.nLD;
+               double fullEllipseLength = halfEllipseLength*2;
+               
+               double axisA = P0.distance(P3)/2;
+               double axisB = ComputeEllipseAxis.computeEllipseAxis(axisA, fullEllipseLength);
+               
+               if (axisB == 0) {
+                       // Ellipse is in fact a line, and it is much faster to draw it as such.
+                       drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, false);
+                       return;
+               }
+               
+               //System.out.println("Ellipse a=" + axisA + " b=" + axisB);
+               
+               int n = counts.total;
+               
+               // We choose to approximate the Bezier curve by 10*n straight lines.
+               HalfEllipse curve = new HalfEllipse(axisA, axisB, 10*n);
+               double curveLength = curve.getApproxCurveLength();
+               
+               
+               double LD = Math.max((curveLength - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+               
+               double[] t = new double[n+1];
+
+               {
+                       double len = 0;
+                       int k = 0;
+                       int b = firstBase;
+                       while (b <= lastBase) {
+                               t[k] = len;
+                               k++;
+                               
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       len += RNA.BASE_PAIR_DISTANCE;
+                                       b = l;
+                               } else {
+                                       len += LD;
+                                       b++;
+                               }
+                       }
+               }
+               
+               // Ellipse with axis = X,Y
+               Point2D.Double[] sequenceBasesCoords = curve.uniformParam(t);
+               
+               // Compute the symmetric half-ellipse
+               if (reverse) {
+                       AffineTransform tranform1 = new AffineTransform();
+                       tranform1.scale(1, -1);
+                       tranform1.transform(sequenceBasesCoords, 0, sequenceBasesCoords, 0, sequenceBasesCoords.length);
+               }
+
+               // Translate + rotate such that ellipse is at the right place
+               AffineTransform tranform = HalfEllipse.matchAxisA(P0, P3);
+               tranform.transform(sequenceBasesCoords, 0, sequenceBasesCoords, 0, sequenceBasesCoords.length);
+               
+               {
+                       int k = 0;
+                       int b = firstBase;
+                       while (b <= lastBase) {
+                               coords[b] = sequenceBasesCoords[k];
+                               //coords[b] = curve.standardParam((double) k/n);
+                               //System.out.println(b + " " + coords[b]);
+                               int l = _listeBases.get(b).getElementStructure();
+                               if (b < l && l < lastBase) {
+                                       b = l;
+                               } else {
+                                       b++;
+                               }
+                               k++;
+                       }
+               }
+
+       }
+       
+       /**
+        * This functions draws the RNA sequence between (including)
+        * firstBase and lastBase along a curve.
+        * The sequence may contain helices.
+        * 
+        * Bezier curve:
+        * A Bezier curve can be defined by four points,
+        * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves
+        * 
+        * Straight line:
+        * If P1 and P2 are null, the bases are drawn on a straight line.
+        * 
+        * OUT: The bases positions are not changed in fact,
+        * instead the coords and centers arrays are modified.
+        * 
+        * Argument reverse says if we should reverse the direction of inserted helices.
+        */
+       private void drawAlongCurve(
+                       int firstBase,
+                       int lastBase,
+                       Point2D.Double P0,
+                       Point2D.Double P1,
+                       Point2D.Double P2,
+                       Point2D.Double P3,
+                       Point2D.Double[] coords,
+                       Point2D.Double[] centers,
+                       double[] angles,
+                       DrawRNATemplateCurveMethod curveMethod,
+                       boolean reverse,
+                       boolean straightBulges) {
+               
+               // First we find the bases which are directly on the Bezier curve
+               ArrayList<Integer> alongBezierCurve = new ArrayList<Integer>();
+               for (int depth=0, i=firstBase; i<=lastBase; i++) {
+                       int k = _listeBases.get(i).getElementStructure();
+                       if (k < 0 || k > lastBase || k < firstBase) {
+                               if (depth == 0) {
+                                       alongBezierCurve.add(i);
+                               }
+                       } else {
+                               if (i < k) {
+                                       if (depth == 0) {
+                                               alongBezierCurve.add(i);
+                                               alongBezierCurve.add(k);
+                                       }
+                                       depth++;
+                               } else {
+                                       depth--;
+                               }
+                       }
+               }
+               // number of bases along the Bezier curve
+               int n = alongBezierCurve.size();
+               int[] alongBezierCurveArray = RNATemplateAlign.intArrayFromList(alongBezierCurve);
+               if (n > 0) {
+                       if (curveMethod == DrawRNATemplateCurveMethod.ALWAYS_REPLACE_BY_ELLIPSES) {
+                               drawOnEllipse(firstBase, lastBase, P0, P3, coords, centers, reverse);
+                       } else if (curveMethod == DrawRNATemplateCurveMethod.SMART) {
+                               boolean passed;
+                               if (P1 != null && P2 != null) {
+                                       passed = drawOnBezierCurve(firstBase, lastBase, P0, P1, P2, P3, coords, centers, true);
+                               } else {
+                                       passed = drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, true);
+                               }
+                               if (!passed) {
+                                       drawOnEllipse(firstBase, lastBase, P0, P3, coords, centers, reverse);
+                               }
+                       } else {
+                               if (P1 != null && P2 != null) {
+                                       drawOnBezierCurve(firstBase, lastBase, P0, P1, P2, P3, coords, centers, false);
+                               } else {
+                                       drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, false);
+                               }
+                       }
+               }
+               // Now use the radiate algorithm to draw the helixes
+               for (int k=0; k<n-1; k++) {
+                       int b1 = alongBezierCurveArray[k];
+                       int b2 = alongBezierCurveArray[k+1];
+                       if (_listeBases.get(b1).getElementStructure() == b2) {
+                               Point2D.Double b1pos = coords[b1];
+                               Point2D.Double b2pos = coords[b2];
+                               double alpha = MiscGeom.angleFromVector(b2pos.x - b1pos.x, b2pos.y - b1pos.y);
+                               rna.drawLoop(b1,
+                                                b2,
+                                                (b1pos.x + b2pos.x)/2,
+                                                (b1pos.y + b2pos.y)/2,
+                                                alpha - Math.PI / 2,
+                                                coords,
+                                                centers,
+                                                angles,
+                                                straightBulges);
+                               if (reverse) {
+                                       Point2D.Double symAxisVect = new Point2D.Double(coords[b2].x - coords[b1].x, coords[b2].y - coords[b1].y); 
+                                       symmetric(coords[b1], symAxisVect, coords, b1, b2);
+                                       symmetric(coords[b1], symAxisVect, centers, b1, b2);
+                               }
+                       }
+               }       
+       }
+       
+       
+       /**
+        * Compute the symmetric of all the points from first to last in the points array
+        * relative to the line that goes through p and has director vector v.
+        * The array is modified in-place.
+        */
+       private static void symmetric(
+                       Point2D.Double p,
+                       Point2D.Double v,
+                       Point2D.Double[] points,
+                       int first,
+                       int last) {
+               // ||v||^2
+               double lv = v.x*v.x + v.y*v.y;
+               for (int i=first; i<=last; i++) {
+                       // A is the coordinates of points[i] after moving the origin at p
+                       Point2D.Double A = new Point2D.Double(points[i].x - p.x, points[i].y - p.y);
+                       // Symmetric of A
+                       Point2D.Double B = new Point2D.Double(
+                                       -(A.x*v.y*v.y - 2*A.y*v.x*v.y - A.x*v.x*v.x) / lv,
+                                        (A.y*v.y*v.y + 2*A.x*v.x*v.y - A.y*v.x*v.x) / lv);
+                       // Change the origin back
+                       points[i].x = B.x + p.x;
+                       points[i].y = B.y + p.y;
+               }
+       }
+       
+       private void computeHelixTranslations(
+                       Tree<RNANodeValueTemplate> tree, // IN
+                       Map<RNATemplateHelix, Point2D.Double> translateVectors, // OUT (must be initialized)
+                       RNATemplateMapping mapping,      // IN
+                       Point2D.Double parentDeltaVector, // IN
+                       boolean straightBulges
+       ) {
+               RNANodeValueTemplate nvt = tree.getValue();
+               Point2D.Double newDeltaVector = parentDeltaVector;
+               if (nvt instanceof RNANodeValueTemplateBasePair) {
+                       RNATemplateHelix helix = ((RNANodeValueTemplateBasePair) nvt).getHelix();
+                       if (! translateVectors.containsKey(helix)) {
+                               translateVectors.put(helix, parentDeltaVector);
+                               int[] basesInHelixArray;
+                               if (mapping.getAncestor(helix) != null) {
+                                       basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix));
+                                       Arrays.sort(basesInHelixArray);
+                               } else {
+                                       basesInHelixArray = new int[0];
+                               }
+                               Point2D.Double helixDeltaVector = computeLengthIncreaseDelta(basesInHelixArray, helix, straightBulges);
+                               newDeltaVector = new Point2D.Double(parentDeltaVector.x+helixDeltaVector.x, parentDeltaVector.y+helixDeltaVector.y);
+                       } 
+               }
+               for (Tree<RNANodeValueTemplate> subtree: tree.getChildren()) {
+                       computeHelixTranslations(subtree, translateVectors, mapping, newDeltaVector, straightBulges);
+               }
+       }
+       
+       private Map<RNATemplateHelix, Point2D.Double> computeHelixTranslations(
+                       Tree<RNANodeValueTemplate> tree, // IN
+                       RNATemplateMapping mapping,       // IN
+                       boolean straightBulges
+       ) {
+               Map<RNATemplateHelix, Point2D.Double> translateVectors = new HashMap<RNATemplateHelix, Point2D.Double>();
+               computeHelixTranslations(tree, translateVectors, mapping, new Point2D.Double(0,0), straightBulges);
+               return translateVectors;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/Mapping.java b/srcjar/fr/orsay/lri/varna/models/rna/Mapping.java
new file mode 100644 (file)
index 0000000..d9b952b
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import fr.orsay.lri.varna.exceptions.MappingException;
+
+public class Mapping implements Serializable {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -3031358968555310380L;
+
+       public static final int UNKNOWN = -1;
+
+       Hashtable<Integer, Integer> _mapping = new Hashtable<Integer, Integer>();
+       Hashtable<Integer, Integer> _invMapping = new Hashtable<Integer, Integer>();
+
+       public Mapping() {
+
+       }
+
+       public void addCouple(int i, int j) throws MappingException {
+               if (_mapping.containsKey(i) || _invMapping.containsKey(j)) {
+                       throw new MappingException(
+                                       MappingException.MULTIPLE_PARTNERS_DEFINITION_ATTEMPT);
+               }
+
+               _mapping.put(new Integer(i), new Integer(j));
+               _invMapping.put(new Integer(j), new Integer(i));
+       }
+
+       public int getPartner(int i) {
+               if (!_mapping.containsKey(i))
+                       return UNKNOWN;
+               else
+                       return _mapping.get(i);
+       }
+
+       public int getAncestor(int j) {
+               if (!_invMapping.containsKey(j))
+                       return UNKNOWN;
+               else
+                       return _invMapping.get(j);
+       }
+
+       public int[] getSourceElems() {
+               int[] elems = new int[_mapping.size()];
+               Enumeration<Integer> en = _mapping.keys();
+               int i = 0;
+               while (en.hasMoreElements()) {
+                       int a = en.nextElement();
+                       elems[i] = a;
+                       i++;
+               }
+               return elems;
+       }
+
+       public int[] getTargetElems() {
+               int[] elems = new int[_invMapping.size()];
+               Enumeration<Integer> en = _invMapping.keys();
+               int i = 0;
+               while (en.hasMoreElements()) {
+                       int a = en.nextElement();
+                       elems[i] = a;
+                       i++;
+               }
+               return elems;
+       }
+
+       public static Mapping readMappingFromAlignment(String m, String n)
+                       throws MappingException {
+               Mapping map = new Mapping();
+               if (m.length() != n.length()) {
+                       throw new MappingException(MappingException.BAD_ALIGNMENT_INPUT);
+               }
+               int i = 0;
+               int j = 0;
+               for (int k = 0; k < m.length(); k++) {
+                       char a = m.charAt(k);
+                       char b = n.charAt(k);
+                       if ((a != '-') && (a != ':') && (b != '-') && (b != ':')) {
+                               map.addCouple(i, j);
+                       }
+
+                       if ((a != '-') && (a != ':')) {
+                               j++;
+                       }
+                       if ((b != '-') && (b != ':')) {
+                               i++;
+                       }
+               }
+               return map;
+       }
+
+       public static Mapping DefaultMapping(int n, int m) {
+               Mapping map = new Mapping();
+               try {
+                       for (int i = 0; i < Math.min(n, m); i++) {
+                               map.addCouple(i, i);
+                       }
+               } catch (MappingException e) {
+                       e.printStackTrace();
+               }
+               return map;
+       }
+
+       public static Mapping DefaultOutermostMapping(int n, int m) {
+               Mapping map = new Mapping();
+               try {
+                       int k = Math.min(n, m);
+                       int i = 0;
+                       int j = 0;
+                       boolean pile = true;
+                       while (i <= (k - 1) - j) {
+                               if (pile) {
+                                       map.addCouple(i, i);
+                                       i++;
+                               } else {
+                                       map.addCouple(n - 1 - j, m - 1 - j);
+                                       j++;
+                               }
+                               pile = !pile;
+                       }
+               } catch (MappingException e) {
+                       e.printStackTrace();
+               }
+               // System.println(map);
+               return map;
+       }
+
+       public String toString() {
+               Enumeration<Integer> en = _mapping.keys();
+               String tmp = "";
+               int l1 = 0;
+               int l2 = 0;
+               int maxIndex = 0;
+               while (en.hasMoreElements()) {
+                       Integer i = en.nextElement();
+                       Integer j = _mapping.get(i);
+                       l1 = Math.max(l1,i);
+                       l2 = Math.max(l2,j);
+                       tmp += "("+i+","+j+")";
+               }
+               maxIndex = Math.max(maxIndex,Math.max(l1,l2));
+               String tmp1 = ""; 
+               String tmp2 = ""; 
+               en = _mapping.keys();
+               
+               int i = l1;
+               int j = l2;
+               while (en.hasMoreElements()) {
+                       Integer a = en.nextElement();
+                       Integer b = _mapping.get(a);
+                       while(a<i)
+                       {
+                               tmp1 = 'x'+tmp1;
+                               tmp2 = '-'+tmp2;
+                               i--;
+                       }
+                       while(b<j)
+                       {
+                               tmp1 = '-'+tmp1;
+                               tmp2 = 'x'+tmp2;
+                               j--;
+                       }
+                       tmp1 = '|'+tmp1;
+                       tmp2 = '|'+tmp2;
+                       i--;j--;
+               }
+               return tmp+"\n"+tmp1+"\n"+tmp2;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModelBaseStyle.java b/srcjar/fr/orsay/lri/varna/models/rna/ModelBaseStyle.java
new file mode 100644 (file)
index 0000000..6d012a3
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+/**
+ * The display Style of a rna base with the base name font, the ouline,
+ * innerline, number and name color
+ * 
+ * @author darty
+ * 
+ */
+public class ModelBaseStyle implements Cloneable, Serializable {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -4331494086323517208L;
+
+       private Color _base_outline_color, _base_inner_color, _base_number_color,
+                       _base_name_color;
+
+       private boolean _selected;
+       
+       
+       public static String XML_ELEMENT_NAME = "basestyle";
+       public static String XML_VAR_OUTLINE_NAME = "outline";
+       public static String XML_VAR_INNER_NAME = "inner";
+       public static String XML_VAR_NUMBER_NAME = "num";
+       public static String XML_VAR_NAME_NAME = "name";
+
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_OUTLINE_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_base_outline_color));
+               atts.addAttribute("","",XML_VAR_INNER_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_base_inner_color));
+               atts.addAttribute("","",XML_VAR_NUMBER_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_base_number_color));
+               atts.addAttribute("","",XML_VAR_NAME_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_base_name_color));
+               
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }       
+       
+       public ModelBaseStyle clone()
+       {
+               ModelBaseStyle result = new ModelBaseStyle();
+               result._base_inner_color  = this._base_inner_color;
+               result._base_name_color  = this._base_name_color;
+               result._base_number_color  = this._base_number_color;
+               result._base_outline_color  = this._base_outline_color;
+               result._selected  = this._selected;
+               return result;
+       }
+       
+       
+       
+       /**
+        * Creates a new base style with default colors and font
+        * 
+        * @see VARNAConfig#BASE_OUTLINE_COLOR_DEFAULT
+        * @see VARNAConfig#BASE_INNER_COLOR_DEFAULT
+        * @see VARNAConfig#BASE_NUMBER_COLOR_DEFAULT
+        * @see VARNAConfig#BASE_NAME_COLOR_DEFAULT
+        * 
+        */
+       public ModelBaseStyle() {
+               _base_outline_color = VARNAConfig.BASE_OUTLINE_COLOR_DEFAULT;
+               _base_inner_color = VARNAConfig.BASE_INNER_COLOR_DEFAULT;
+               _base_number_color = VARNAConfig.BASE_NUMBER_COLOR_DEFAULT;
+               _base_name_color = VARNAConfig.BASE_NAME_COLOR_DEFAULT;
+               _selected = false;
+       }
+
+       /**
+        * Creates a new base style with custom colors and custom font
+        * 
+        * @param outline
+        *            The out line color of the base
+        * @param inner
+        *            The inner line color of the base
+        * @param number
+        *            The number color of the base
+        * @param name
+        *            The name color of the base
+        * @param font
+        *            The name font of the base
+        */
+       public ModelBaseStyle(Color outline, Color inner, Color number,
+                       Color name, Font font) {
+               _base_outline_color = outline;
+               _base_inner_color = inner;
+               _base_number_color = number;
+               _base_name_color = name;
+       }
+
+       public ModelBaseStyle(String parameterValue)
+                       throws ExceptionModeleStyleBaseSyntaxError, ExceptionParameterError {
+               this();
+               assignParameters(parameterValue);
+       }
+
+       public ModelBaseStyle(ModelBaseStyle msb) {
+               _base_outline_color = msb.getBaseOutlineColor();
+               _base_inner_color = msb.getBaseInnerColor();
+               _base_number_color = msb.getBaseNumberColor();
+               _base_name_color = msb.getBaseNameColor();
+       }
+
+       public Color getBaseOutlineColor() {
+               return _base_outline_color;
+       }
+
+       public void setBaseOutlineColor(Color _base_outline_color) {
+               this._base_outline_color = _base_outline_color;
+       }
+
+       public Color getBaseInnerColor() {
+               return _base_inner_color;
+       }
+
+       public void setBaseInnerColor(Color _base_inner_color) {
+               this._base_inner_color = _base_inner_color;
+       }
+
+       public Color getBaseNumberColor() {
+               return _base_number_color;
+       }
+
+       public void setBaseNumberColor(Color _base_numbers_color) {
+               this._base_number_color = _base_numbers_color;
+       }
+
+       public Color getBaseNameColor() {
+               return _base_name_color;
+       }
+
+       public void setBaseNameColor(Color _base_name_color) {
+               this._base_name_color = _base_name_color;
+       }
+
+       public static final String PARAM_INNER_COLOR = "fill";
+       public static final String PARAM_OUTLINE_COLOR = "outline";
+       public static final String PARAM_TEXT_COLOR = "label";
+       public static final String PARAM_NUMBER_COLOR = "number";
+
+       public static Color getSafeColor(String col) {
+               Color result;
+               try {
+                       result = Color.decode(col);
+
+               } catch (NumberFormatException e) {
+
+                       result = Color.getColor(col, Color.green);
+               }
+               return result;
+       }
+
+       public void assignParameters(String parametersValue)
+                       throws ExceptionModeleStyleBaseSyntaxError, ExceptionParameterError {
+               if (parametersValue.equals(""))
+                       return;
+
+               String[] parametersL = parametersValue.split(",");
+
+               ArrayList<String> namesArray = new ArrayList<String>();
+               ArrayList<String> valuesArray = new ArrayList<String>();
+               String[] param;
+               for (int i = 0; i < parametersL.length; i++) {
+                       param = parametersL[i].split("=");
+                       if (param.length != 2)
+                               throw new ExceptionModeleStyleBaseSyntaxError(
+                                               "Bad parameter: '" + param[0] + "' ...");
+                       namesArray.add(param[0].replace(" ", ""));
+                       valuesArray.add(param[1].replace(" ", ""));
+
+               }
+
+               for (int i = 0; i < namesArray.size(); i++) {
+                       if (namesArray.get(i).toLowerCase().equals(PARAM_INNER_COLOR)) {
+                               try {
+                                       setBaseInnerColor(getSafeColor(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad inner color Syntax:" + valuesArray.get(i));
+                               }
+                       } else if (namesArray.get(i).toLowerCase().equals(PARAM_TEXT_COLOR)) {
+                               try {
+                                       setBaseNameColor(getSafeColor(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad name color Syntax:" + valuesArray.get(i));
+                               }
+                       } else if (namesArray.get(i).toLowerCase().equals(
+                                       PARAM_NUMBER_COLOR)) {
+                               try {
+                                       setBaseNumberColor(getSafeColor(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad numbers color Syntax:" + valuesArray.get(i));
+                               }
+                       } else if (namesArray.get(i).toLowerCase().equals(
+                                       PARAM_OUTLINE_COLOR)) {
+                               try {
+                                       setBaseOutlineColor(getSafeColor(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad outline color Syntax:" + valuesArray.get(i));
+                               }
+                       } else
+                               throw new ExceptionModeleStyleBaseSyntaxError(
+                                               "Unknown parameter:" + namesArray.get(i));
+               }
+       }
+
+       /**
+        * Find the font style integer from a string. Return <code>null</code> if
+        * the font style is unknown.
+        * 
+        * @param s
+        *            The <code>string</code> to decode
+        * @return The font style integer as <code>Font.PLAIN<code>.
+        */
+       public static Integer StyleToInteger(String s) {
+               Integer style;
+               if (s.toLowerCase().equals("italic"))
+                       style = Font.ITALIC;
+               else if (s.toLowerCase().equals("bold"))
+                       style = Font.BOLD;
+               else if (s.toLowerCase().equals("plain"))
+                       style = Font.PLAIN;
+               else
+                       style = null;
+               return style;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBP.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBP.java
new file mode 100644 (file)
index 0000000..138edb0
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Random;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.models.VARNAConfig;
+
+
+public class ModeleBP implements Serializable, Comparable<ModeleBP> {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -1344722280822711931L;
+
+       public enum Edge {
+               WC, SUGAR, HOOGSTEEN;
+       }
+       public enum Stericity {
+               CIS, TRANS;
+       }
+
+       private ModeleBase _partner5;
+       private Edge _edge5;
+       private ModeleBase _partner3;
+       private Edge _edge3;
+       private Stericity _stericity;
+       private ModeleBPStyle _style;
+
+       
+       public static String XML_ELEMENT_NAME = "bp";
+       public static String XML_VAR_PARTNER5_NAME = "part5";
+       public static String XML_VAR_EDGE5_NAME = "edge5";
+       public static String XML_VAR_PARTNER3_NAME = "part3";
+       public static String XML_VAR_EDGE3_NAME = "edge3";
+       public static String XML_VAR_STERICITY_NAME = "orient";
+       public static String XML_VAR_SEC_STR_NAME = "secstr";
+
+       public void toXML(TransformerHandler hd, boolean inSecondaryStructure) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_PARTNER5_NAME,"CDATA",""+_partner5.getIndex());
+               atts.addAttribute("","",XML_VAR_PARTNER3_NAME,"CDATA",""+_partner3.getIndex());
+               atts.addAttribute("","",XML_VAR_EDGE5_NAME,"CDATA",""+_edge5);
+               atts.addAttribute("","",XML_VAR_EDGE3_NAME,"CDATA",""+_edge3);
+               atts.addAttribute("","",XML_VAR_STERICITY_NAME,"CDATA",""+_stericity);
+               atts.addAttribute("","",XML_VAR_SEC_STR_NAME,"CDATA",""+inSecondaryStructure);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               _style.toXML(hd);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               toXML(hd, false);
+       }
+       
+
+       
+       public ModeleBP(ModeleBase part5, ModeleBase part3) {
+               this(part5, part3, Edge.WC, Edge.WC, Stericity.CIS);
+       }
+
+       //private static Random rnd = new Random(System.currentTimeMillis());
+
+       public ModeleBP(ModeleBase part5, ModeleBase part3, Edge edge5,
+                       Edge edge3, Stericity ster) {
+               _partner5 = part5;
+               _partner3 = part3;
+               _edge5 = edge5;
+               _edge3 = edge3;
+               _stericity = ster;
+               _style = new ModeleBPStyle();
+       }
+
+       public ModeleBP(String text) throws ExceptionModeleStyleBaseSyntaxError, ExceptionParameterError {
+               _style = new ModeleBPStyle();
+               assignParameters(text);         
+       }
+       
+       public void setStericity(Stericity s) {
+               _stericity = s;
+       }
+
+       public void setEdge5(Edge e) {
+               _edge5 = e;
+       }
+
+       public void setEdge3(Edge e) {
+               _edge3 = e;
+       }
+
+       public void setStyle(ModeleBPStyle e) {
+               _style = e;
+       }
+       
+       public ModeleBPStyle getStyle() {
+               return _style;
+       }
+               
+       public boolean isCanonicalGC() {
+               String si = _partner5.getContent();
+               String sj = _partner3.getContent();
+               if ((si.length() >= 1) && (sj.length() >= 1)) {
+                       char ci = si.toUpperCase().charAt(0);
+                       char cj = sj.toUpperCase().charAt(0);
+                       if (((ci == 'G') && (cj == 'C')) || ((ci == 'C') && (cj == 'G'))) {
+                               return isCanonical() && (getStericity() == Stericity.CIS);
+                       }
+               }
+               return false;
+       }
+
+       public boolean isCanonicalAU() {
+               String si = _partner5.getContent();
+               String sj = _partner3.getContent();
+               if ((si.length() >= 1) && (sj.length() >= 1)) {
+                       char ci = si.toUpperCase().charAt(0);
+                       char cj = sj.toUpperCase().charAt(0);
+                       if (((ci == 'A') && (cj == 'U')) 
+                                       || ((ci == 'U') && (cj == 'A'))
+                                       || ((ci == 'U') && (cj == 'T'))
+                                       || ((ci == 'T') && (cj == 'U'))) {
+                               return isCanonical();
+                       }
+               }
+               return false;
+       }
+
+       public boolean isWobbleUG() {
+               String si = _partner5.getContent();
+               String sj = _partner3.getContent();
+               if ((si.length() >= 1) && (sj.length() >= 1)) {
+                       char ci = si.toUpperCase().charAt(0);
+                       char cj = sj.toUpperCase().charAt(0);
+                       if (((ci == 'G') && (cj == 'U')) || ((ci == 'U') && (cj == 'G'))) {
+                               return (isCanonical());
+                       }
+               }
+               return false;
+       }
+
+       public boolean isCanonical() {
+               return (_edge5 == Edge.WC) && (_edge3 == Edge.WC)
+                               && (_stericity == Stericity.CIS);
+       }
+
+       public Stericity getStericity() {
+               return _stericity;
+       }
+
+       public boolean isCIS() {
+               return (_stericity == Stericity.CIS);
+       }
+
+       public boolean isTRANS() {
+               return (_stericity == Stericity.TRANS);
+       }
+
+       public Edge getEdgePartner5() {
+               return _edge5;
+       }
+
+       public Edge getEdgePartner3() {
+               return _edge3;
+       }
+       
+       public ModeleBase getPartner(ModeleBase mb) {
+               if (mb == _partner3)
+                       return _partner5;
+               else
+                       return _partner3;
+       }
+
+       public ModeleBase getPartner5() {
+               return _partner5;
+       }
+
+       public ModeleBase getPartner3() {
+               return _partner3;
+       }
+
+       public int getIndex5() {
+               return _partner5.getIndex();
+       }
+
+       public int getIndex3() {
+               return _partner3.getIndex();
+       }
+
+       public void setPartner5(ModeleBase mb) {
+               _partner5 = mb;
+       }
+
+       public void setPartner3(ModeleBase mb) {
+               _partner3 = mb;
+       }
+
+
+
+       public static final String PARAM_COLOR = "color";
+       public static final String PARAM_THICKNESS = "thickness";
+       public static final String PARAM_EDGE5 = "edge5";
+       public static final String PARAM_EDGE3 = "edge3";
+       public static final String PARAM_STERICITY = "stericity";
+
+       public static final String VALUE_WATSON_CRICK = "wc";
+       public static final String VALUE_HOOGSTEEN = "h";
+       public static final String VALUE_SUGAR = "s";
+       public static final String VALUE_CIS = "cis";
+       public static final String VALUE_TRANS = "trans";
+
+       public void assignParameters(String parametersValue)
+                       throws ExceptionModeleStyleBaseSyntaxError, ExceptionParameterError {
+               if (parametersValue.equals(""))
+                       return;
+
+               String[] parametersL = parametersValue.split(",");
+
+               ArrayList<String> namesArray = new ArrayList<String>();
+               ArrayList<String> valuesArray = new ArrayList<String>();
+               String[] param;
+               for (int i = 0; i < parametersL.length; i++) {
+                       param = parametersL[i].split("=");
+                       if (param.length != 2)
+                               throw new ExceptionModeleStyleBaseSyntaxError(
+                                               "Bad parameter: '" + param[0] + "' ...");
+                       namesArray.add(param[0].replace(" ", ""));
+                       valuesArray.add(param[1].replace(" ", ""));
+
+               }
+               for (int i = 0; i < namesArray.size(); i++) {
+                       if (namesArray.get(i).toLowerCase().equals(PARAM_COLOR)) {
+                               try {
+                                       _style.setCustomColor(ModelBaseStyle
+                                                       .getSafeColor(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad inner color Syntax:" + valuesArray.get(i));
+                               }
+                       } else if (namesArray.get(i).toLowerCase().equals(PARAM_THICKNESS)) {
+                               try {
+                                       _style.setThickness(Double.parseDouble(valuesArray.get(i)));
+                               } catch (NumberFormatException e) {
+                                       throw new ExceptionParameterError(e.getMessage(),
+                                                       "Bad value for bp thickness:" + valuesArray.get(i));
+                               }
+                       } else if (namesArray.get(i).toLowerCase().equals(PARAM_EDGE5)) {
+                               String s = valuesArray.get(i);
+                               if (s.toLowerCase().equals(VALUE_WATSON_CRICK)) {
+                                       setEdge5(Edge.WC);
+                               } else if (s.toLowerCase().equals(VALUE_HOOGSTEEN)) {
+                                       setEdge5(Edge.HOOGSTEEN);
+                               } else if (s.toLowerCase().equals(VALUE_SUGAR)) {
+                                       setEdge5(Edge.SUGAR);
+                               } else
+                                       throw new ExceptionParameterError("Bad value for edge:"
+                                                       + valuesArray.get(i));
+                       } else if (namesArray.get(i).toLowerCase().equals(PARAM_EDGE3)) {
+                               String s = valuesArray.get(i);
+                               if (s.toLowerCase().equals(VALUE_WATSON_CRICK)) {
+                                       setEdge3(Edge.WC);
+                               } else if (s.toLowerCase().equals(VALUE_HOOGSTEEN)) {
+                                       setEdge3(Edge.HOOGSTEEN);
+                               } else if (s.toLowerCase().equals(VALUE_SUGAR)) {
+                                       setEdge3(Edge.SUGAR);
+                               } else
+                                       throw new ExceptionParameterError("Bad value for edge:"
+                                                       + valuesArray.get(i));
+                       } else if (namesArray.get(i).toLowerCase().equals(PARAM_STERICITY)) {
+                               String s = valuesArray.get(i);
+                               if (s.toLowerCase().equals(VALUE_CIS)) {
+                                       setStericity(Stericity.CIS);
+                               } else if (s.toLowerCase().equals(VALUE_TRANS)) {
+                                       setStericity(Stericity.TRANS);
+                               } else
+                                       throw new ExceptionParameterError(
+                                                       "Bad value for stericity:" + valuesArray.get(i));
+                       } else
+                               throw new ExceptionModeleStyleBaseSyntaxError(
+                                               "Unknown parameter:" + namesArray.get(i));
+               }
+       }
+       
+       public String toString() {
+               String result = "";
+               result += "(" + _partner5.getIndex() + "," + _partner3.getIndex() + ")";
+               //result += " [" + _partner5.getElementStructure() + ","
+               //              + _partner3.getElementStructure() + "]\n";
+               //result += "  5':" + _partner5 + "\n";
+               //result += "  3':" + _partner3;
+               return result;
+       }
+
+
+       public int compareTo(ModeleBP mb) {
+               if (getIndex5()!=mb.getIndex5())
+               {  return getIndex5()-mb.getIndex5();  }
+               return getIndex3()-mb.getIndex3(); 
+               
+       }
+       
+       
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBPStyle.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBPStyle.java
new file mode 100644 (file)
index 0000000..f5b7398
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Random;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
+import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+
+public class ModeleBPStyle implements Serializable {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 3006493290669550139L;
+       /**
+        * 
+        */
+       private boolean _isCustomColored = false;
+       private Color _color = VARNAConfig.DEFAULT_BOND_COLOR;
+
+       private double _thickness = -1.0;
+       private double _bent = 0.0;
+       
+       public static String XML_ELEMENT_NAME = "BPstyle";
+       public static String XML_VAR_CUSTOM_STYLED_NAME = "custom";
+       public static String XML_VAR_COLOR_NAME = "color";
+       public static String XML_VAR_THICKNESS_NAME = "thickness";
+       public static String XML_VAR_BENT_NAME = "bent";
+
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_CUSTOM_STYLED_NAME,"CDATA",""+_isCustomColored);
+               atts.addAttribute("","",XML_VAR_COLOR_NAME,"CDATA",XMLUtils.toHTMLNotation(_color));
+               atts.addAttribute("","",XML_VAR_THICKNESS_NAME,"CDATA",""+_thickness);
+               atts.addAttribute("","",XML_VAR_BENT_NAME,"CDATA",""+_bent);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+       
+       public double getBent()
+       {
+               return _bent;
+       }
+
+       
+       public boolean isBent()
+       {
+               return (_bent!=0.0);
+       }
+
+       public void setBent(double b)
+       {
+               _bent = b;
+       }
+
+       
+       public ModeleBPStyle() {
+       }
+
+
+       public void setCustomColor(Color c) {
+               _isCustomColored = true;
+               _color = c;
+       }
+
+       public void useDefaultColor() {
+               _isCustomColored = false;
+       }
+
+       public boolean isCustomColored() {
+               return _isCustomColored;
+       }
+
+       public Color getCustomColor() {
+               return _color;
+       }
+
+       /**
+        * Returns the current custom color if such a color is defined to be used
+        * (through setCustomColor), or returns the default color.
+        * 
+        * @param def
+        *            - The default color is no custom color is defined
+        * @return The color to be used to draw this base-pair
+        */
+       public Color getColor(Color def) {
+               if (isCustomColored()) {
+                       return _color;
+               } else {
+                       return def;
+               }
+       }
+
+       public double getThickness(double def) {
+               if (_thickness > 0)
+                       return _thickness;
+               else
+                       return def;
+       }
+
+       public void setThickness(double thickness) {
+               _thickness = thickness;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBackbone.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBackbone.java
new file mode 100644 (file)
index 0000000..caa4b5f
--- /dev/null
@@ -0,0 +1,75 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement.BackboneType;
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+public class ModeleBackbone  implements Serializable{
+
+       /**
+        * 
+        */
+       private Hashtable<Integer,ModeleBackboneElement> elems = new Hashtable<Integer,ModeleBackboneElement>();
+       
+       private static final long serialVersionUID = -614968737102943216L;
+
+       
+       
+       public static String XML_ELEMENT_NAME = "backbone";
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               for (ModeleBackboneElement bck:elems.values())
+               {
+                       bck.toXML(hd);
+               }
+               hd.endElement("","",XML_ELEMENT_NAME);
+               atts.clear();
+       }
+
+       public void addElement(ModeleBackboneElement mbe)
+       {
+               elems.put(mbe.getIndex(),mbe);
+       }
+
+        public BackboneType getTypeBefore(int indexBase)
+        {
+                return getTypeAfter(indexBase-1);
+        }
+       
+        public BackboneType getTypeAfter(int indexBase)
+        {
+                if (elems.containsKey(indexBase))
+                        return elems.get(indexBase).getType();
+                else
+                        return BackboneType.SOLID_TYPE;
+        }
+
+        public Color getColorBefore(int indexBase, Color defCol)
+        {
+                return getColorAfter(indexBase-1,defCol);
+        }
+       
+        public Color getColorAfter(int indexBase, Color defCol)
+        {
+                if (elems.containsKey(indexBase))
+                {
+                        Color c = elems.get(indexBase).getColor();
+                        if (c != null)
+                                return c;
+                }
+                return defCol;
+        }
+        
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBackboneElement.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBackboneElement.java
new file mode 100644 (file)
index 0000000..6b16a05
--- /dev/null
@@ -0,0 +1,106 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.io.Serializable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+public class ModeleBackboneElement implements Serializable{
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -614968737102943216L;
+
+       public ModeleBackboneElement(int index, BackboneType t)
+       {
+               _index=index;
+               if (t==BackboneType.CUSTOM_COLOR)
+               {
+                       throw new IllegalArgumentException("Error: Missing Color while constructing Backbone");
+               }
+               _type=t;
+       }
+       
+       public ModeleBackboneElement(int index, Color c)
+       {
+               _index=index;
+               _type=BackboneType.CUSTOM_COLOR;
+               _color = c;
+       }
+       
+       public enum BackboneType{
+               SOLID_TYPE ("solid"),
+               DISCONTINUOUS_TYPE ("discontinuous"),
+               MISSING_PART_TYPE ("missing"),
+               CUSTOM_COLOR ("custom");
+               
+               private String label;
+               
+               BackboneType(String s)
+               {
+                       label = s;
+               }
+               
+               public String getLabel()
+               {
+                       return label;
+               }
+               
+               
+               public static BackboneType getType(String lbl)
+               {
+                       BackboneType[] vals = BackboneType.values();
+                       for(int i=0;i<vals.length;i++)
+                       {
+                               if (vals[i].equals(lbl))
+                                       return vals[i];
+                       }
+                       return null;
+               }
+               
+       };
+       
+       private BackboneType _type;
+       private Color _color = null;
+       private int _index;
+
+       
+       public BackboneType getType()
+       {
+               return _type;
+       }
+       
+       public int getIndex()
+       {
+               return _index;
+       }
+       
+       public Color getColor()
+       {
+               return _color;
+       }
+       
+       public static String XML_ELEMENT_NAME = "BackboneElement";
+       public static String XML_VAR_INDEX_NAME = "index";
+       public static String XML_VAR_TYPE_NAME = "type";
+       public static String XML_VAR_COLOR_NAME = "color";      
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_INDEX_NAME,"CDATA",""+_index);
+               atts.addAttribute("","",XML_VAR_TYPE_NAME,"CDATA",""+_type.getLabel());
+               if(_type==BackboneType.CUSTOM_COLOR){
+                       atts.addAttribute("","",XML_VAR_COLOR_NAME,"CDATA",""+XMLUtils.toHTMLNotation(_color)); 
+               }
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBase.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBase.java
new file mode 100644 (file)
index 0000000..9491ed8
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+
+
+/**
+ * The abstract rna base model
+ * 
+ * @author darty
+ * 
+ */
+public abstract class ModeleBase implements Serializable, java.lang.Comparable<ModeleBase> {
+
+       private ModeleBP _BP;
+       /**
+        * The base style.
+        */
+       protected ModelBaseStyle _styleBase = new ModelBaseStyle();
+       /**
+        * TRUE if this InterfaceBase has to be colored, else FALSE.
+        */
+       protected Boolean _colorie = new Boolean(true);
+       /**
+        * The coordinate representation of this InterfaceBase on the final graphic.
+        */
+       protected VARNAPoint _coords = new VARNAPoint();
+       /**
+        * The nearest loop center of this InterfaceBase.
+        */
+       protected VARNAPoint _center = new VARNAPoint();
+
+       /**
+        * The label of this base.
+        */
+       protected String _label = "";
+
+       protected double _value;
+       protected int _realIndex = -1;
+
+       public abstract void toXML(TransformerHandler hd) throws SAXException;
+       
+       
+       
+       /**
+        * The internal index for this Base
+        */
+       public abstract int getIndex();
+
+       public abstract String getContent();
+       public abstract void setContent(String s);
+
+       /**
+        * Gets this InterfaceBase style.
+        * 
+        * @return this InterfaceBase style.
+        */
+       public ModelBaseStyle getStyleBase() {
+               return _styleBase;
+       }
+
+       public double getValue()
+       {
+               return _value;
+       }
+
+       public void setValue(double d)
+       {
+               _value = d;
+       }
+       
+       
+       /**
+        * Sets this InterfaceBase style.
+        * 
+        * @param base
+        *            - This InterfaceBase new style.
+        */
+       public void setStyleBase(ModelBaseStyle base) {
+               _styleBase = new ModelBaseStyle(base);
+       }
+
+       /**
+        * Gets this InterfaceBase color statement.
+        * 
+        * @return TRUE if this InterfaceBase has to be colored, else FALSE.
+        */
+       public final Boolean getColorie() {
+               return _colorie;
+       }
+
+       /**
+        * Sets this InterfaceBase color statement.
+        * 
+        * @param _colorie
+        *            - TRUE if you want this InterfaceBase to be colored, else
+        *            FALSE
+        */
+       public final void setColorie(Boolean _colorie) {
+               this._colorie = _colorie;
+       }
+
+
+       /**
+        * Gets this InterfaceBase associated structure element.
+        * 
+        * @return this InterfaceBase associated structure element.
+        */
+       public int getElementStructure() {
+               if (_BP==null)
+                       return -1;
+               else
+               {
+                 if (_BP.getPartner5()==this)
+                         return _BP.getPartner3().getIndex();
+                 else
+                         return _BP.getPartner5().getIndex();
+               }
+       }
+
+
+       /**
+        * Sets this InterfaceBase assiocated structure element.
+        * 
+        * @param structure
+        *            - This new assiocated structure element.
+       
+       public void setElementStructure(int structure) {
+               setElementStructure(structure, new ModeleBP());
+       } */
+
+       /**
+        * Sets this InterfaceBase associated structure element.
+        * 
+        * @param structure
+        *            - This new associated structure element.
+        * @param type
+        *            - The type of this base pair.
+        */
+       public void setElementStructure(int structure, ModeleBP type) {
+//             _elementStructure = structure;
+               _BP = type;
+
+       }
+
+       public void removeElementStructure() {
+//             _elementStructure = -1;
+               _BP = null;
+       }
+       
+       
+       /**
+        * Gets the base pair type for this element.
+        * 
+        * @return the base pair type for this element.
+        */
+       public ModeleBP getStyleBP() {
+               return _BP;
+       }
+
+       /**
+        * Sets the base pair type for this element.
+        * 
+        * @param type
+        *            - The new base pair type for this element.
+        */
+       public void setStyleBP(ModeleBP type) {
+               _BP = type;
+       }
+
+       public int getBaseNumber() {
+               return _realIndex;
+       }
+
+       public void setBaseNumber(int bn) {
+               _realIndex = bn;
+       }
+       
+       public Point2D.Double getCoords() {
+               return new Point2D.Double(_coords.x,_coords.y);
+       }
+
+       public void setCoords(Point2D.Double coords) {
+               this._coords.x = coords.x;
+               this._coords.y = coords.y;
+       }
+
+       public Point2D.Double getCenter() {
+               return new Point2D.Double(_center.x,_center.y);
+       }
+
+       public void setCenter(Point2D.Double center) {
+               this._center.x = center.x;
+               this._center.y = center.y;
+       }
+
+       public String getLabel() {
+               if (_label==null || _label.equals(""))
+               {
+                       return ""+this.getBaseNumber();
+               }
+               else
+               {
+                       return _label;
+               }
+                       
+       }
+
+       public void setLabel(String s) {
+               _label= s;
+       }
+
+       public void setLabel(Point2D.Double center) {
+               this._center.x = center.x;
+               this._center.y = center.y;
+       }
+
+
+       public int compareTo(ModeleBase other) { 
+           int nombre1 = ((ModeleBase) other).getIndex(); 
+           int nombre2 = this.getIndex(); 
+           if (nombre1 > nombre2)  return -1; 
+           else if(nombre1 == nombre2) return 0; 
+           else return 1; 
+       } 
+       
+       public static String XML_VAR_TYPE_NAME = "type";
+       public static String XML_VAR_INDEX_NAME = "index";
+       public static String XML_VAR_LABEL_NAME = "label";
+       public static String XML_VAR_VALUE_NAME = "val";
+       public static String XML_VAR_POSITION_NAME = "pos";
+       public static String XML_VAR_CENTER_NAME = "center";
+       public static String XML_VAR_NUMBER_NAME = "num";
+       public static String XML_VAR_CUSTOM_DRAWN_NAME = "custom";
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBaseNucleotide.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBaseNucleotide.java
new file mode 100644 (file)
index 0000000..ad0c5c1
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+
+/**
+ * The rna base model with the first character of the nitrogenous base and it
+ * display
+ * 
+ * @author darty
+ * 
+ */
+public class ModeleBaseNucleotide extends ModeleBase {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -5493938366569588113L;
+       private String _c;
+       private int _index;
+       
+
+       
+       public static String XML_ELEMENT_NAME = "nt";
+       public static String XML_VAR_CONTENT_NAME = "base";
+       
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_INDEX_NAME,"CDATA",""+_index);
+               atts.addAttribute("","",XML_VAR_NUMBER_NAME,"CDATA",""+_realIndex);
+               atts.addAttribute("","",XML_VAR_CUSTOM_DRAWN_NAME,"CDATA",""+_colorie);
+               atts.addAttribute("","",XML_VAR_VALUE_NAME,"CDATA",""+_value);
+               atts.addAttribute("","",XML_VAR_LABEL_NAME,"CDATA",""+_label);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               atts.clear();
+               hd.startElement("","",XML_VAR_CONTENT_NAME,atts);
+               XMLUtils.exportCDATAString(hd, _c);
+               hd.endElement("","",XML_VAR_CONTENT_NAME);
+
+               _coords.toXML(hd,XML_VAR_POSITION_NAME);
+               _center.toXML(hd,XML_VAR_CENTER_NAME);
+               if (_colorie)
+               { _styleBase.toXML(hd); }
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }
+
+       /**
+        * Creates a new rna base with the default display style and a space as
+        * nitrogenous base
+        * @param index The index of this base
+        */
+       public ModeleBaseNucleotide(int index) {
+               this(" ", index);
+       }
+
+       /**
+        * Creates a new rna base with the nitrogenous base
+        * 
+        * @param s
+        *            The code of this base
+        * @param index The index of this base
+        */
+       public ModeleBaseNucleotide(String s, int index) {
+               this(s, new ModelBaseStyle(), index);
+       }
+
+
+       /**
+        * Creates a new rna base with the nitrogenous base
+        * 
+        * @param s
+        *            The full label, potentially requiring further decoding
+        * @param index The index of this base
+        * @param baseNumber The number of this base, which may differ from the index (e.g. discontinuous numbering)
+        */
+       public ModeleBaseNucleotide(String s, int index, int baseNumber) {
+               this(s, new ModelBaseStyle(), index);
+               _realIndex = baseNumber;
+       }
+
+       /**
+        * Creates a new rna base with the nitrogenous base and the display style
+        * 
+        * @param s
+        *            The full label, potentially requiring further decoding
+        * @param msb
+        *            The display style
+        * @param index The index of this base
+        */
+       public ModeleBaseNucleotide(String s, ModelBaseStyle msb, int index) {
+               this(new Point2D.Double(), new Point2D.Double(), true, s, msb, -1,
+                               index);
+       }
+
+       /**
+        * Creates a new rna base with a display style
+        * 
+        * @param msb
+        *            The display style
+        */
+       public ModeleBaseNucleotide(ModelBaseStyle msb, int index, int baseNumber) {
+               this("", msb, index);
+               _realIndex = baseNumber;
+       }
+
+       /**
+        * Creates a new rna base with a space as the nitrogenous base and the
+        * display style
+        * 
+        * @param coord
+        * @param index
+        */
+       public ModeleBaseNucleotide(Point2D.Double coord, int index) {
+               this(new Point2D.Double(coord.getX(), coord.getY()),
+                               new Point2D.Double(), true, "", new ModelBaseStyle(), -1,
+                               index);
+       }
+
+       /**
+        * Creates a new rna base from another one with the same attributes
+        * 
+        * @param mb
+        *            The base to copy
+        */
+       public ModeleBaseNucleotide(ModeleBaseNucleotide mb, int index) {
+               this(
+                               new Point2D.Double(mb.getCoords().getX(), mb.getCoords()
+                                               .getY()), new Point2D.Double(mb.getCenter().getX(), mb
+                                               .getCenter().getY()), true, mb.getBase(), mb
+                                               .getStyleBase(), mb.getElementStructure(), index);
+       }
+
+       public ModeleBaseNucleotide(Point2D.Double coords, Point2D.Double center,
+                       boolean colorie, String label, ModelBaseStyle mb, int elementStruct,
+                       int index) {
+               _colorie = colorie;
+               _c = label;
+               _styleBase = mb;
+               _coords = new VARNAPoint(coords);
+               _center = new VARNAPoint(center);
+               _index = index;
+               _realIndex = index + 1;
+               _value = 0.0;
+       }
+
+       
+       public ModelBaseStyle getStyleBase() {
+               if (_colorie)
+                       return _styleBase;
+               return new ModelBaseStyle();
+       }
+
+       public String getBase() {
+               return decode(_c);
+       }
+
+       public void setBase(String _s) {
+               this._c = _s;
+       }
+
+
+
+       public String getContent() {
+               return getBase();
+       }
+
+       public void setContent(String s) {
+               setBase(s);
+       }
+
+       public int getIndex() {
+               return _index;
+       }
+       
+       public String toString()
+       {
+               return ""+this._realIndex+" ("+_index+") (x,y):"+this._coords +" C:"+_center;
+       }
+
+       
+       private enum STATE_SPECIAL_CHARS_STATES{ NORMAL,SUBSCRIPT, SUPERSCRIPT, COMMAND};
+
+       private static HashMap<Character,Character> _subscripts = new HashMap<Character,Character>();
+       private static HashMap<Character,Character> _superscripts = new HashMap<Character,Character>();
+       private static HashMap<String,Character> _commands = new HashMap<String,Character>();
+       {
+               _subscripts.put('0', '\u2080');
+               _subscripts.put('1', '\u2081');
+               _subscripts.put('2', '\u2082');
+               _subscripts.put('3', '\u2083');
+               _subscripts.put('4', '\u2084');
+               _subscripts.put('5', '\u2085');
+               _subscripts.put('6', '\u2086');
+               _subscripts.put('7', '\u2087');
+               _subscripts.put('8', '\u2088');
+               _subscripts.put('9', '\u2089');
+               _subscripts.put('+', '\u208A');
+               _subscripts.put('-', '\u208B');
+               _subscripts.put('a', '\u2090');
+               _subscripts.put('e', '\u2091');
+               _subscripts.put('o', '\u2092');
+               _subscripts.put('i', '\u1D62');
+               _subscripts.put('r', '\u1D63');
+               _subscripts.put('u', '\u1D64');
+               _subscripts.put('v', '\u1D65');
+               _subscripts.put('x', '\u2093');
+               _superscripts.put('0', '\u2070');
+               _superscripts.put('1', '\u00B9');
+               _superscripts.put('2', '\u00B2');
+               _superscripts.put('3', '\u00B3');
+               _superscripts.put('4', '\u2074');
+               _superscripts.put('5', '\u2075');
+               _superscripts.put('6', '\u2076');
+               _superscripts.put('7', '\u2077');
+               _superscripts.put('8', '\u2078');
+               _superscripts.put('9', '\u2079');
+               _superscripts.put('+', '\u207A');
+               _superscripts.put('-', '\u207B');
+               _superscripts.put('i', '\u2071');
+               _superscripts.put('n', '\u207F');
+               _commands.put("alpha",  '\u03B1');
+               _commands.put("beta",   '\u03B2');
+               _commands.put("gamma",  '\u03B3');
+               _commands.put("delta",  '\u03B4');
+               _commands.put("epsilon",'\u03B5');
+               _commands.put("zeta",   '\u03B6');
+               _commands.put("eta",    '\u03B7');
+               _commands.put("theta",  '\u03B8');
+               _commands.put("iota",   '\u03B9');
+               _commands.put("kappa",  '\u03BA');
+               _commands.put("lambda", '\u03BB');
+               _commands.put("mu",     '\u03BC');
+               _commands.put("nu",     '\u03BD');
+               _commands.put("xi",     '\u03BE');
+               _commands.put("omicron",'\u03BF');
+               _commands.put("pi",     '\u03C1');
+               _commands.put("rho",    '\u03C2');
+               _commands.put("sigma",  '\u03C3');
+               _commands.put("tau",    '\u03C4');
+               _commands.put("upsilon",'\u03C5');
+               _commands.put("phi",    '\u03C6');
+               _commands.put("chi",    '\u03C7');
+               _commands.put("psi",    '\u03C8');
+               _commands.put("omega",  '\u03C9');
+               _commands.put("Psi",    '\u03A8');
+               _commands.put("Phi",    '\u03A6');
+               _commands.put("Sigma",  '\u03A3');
+               _commands.put("Pi",     '\u03A0');
+               _commands.put("Theta",  '\u0398');
+               _commands.put("Omega",  '\u03A9');
+               _commands.put("Gamma",  '\u0393');
+               _commands.put("Delta",  '\u0394');
+               _commands.put("Lambda", '\u039B');
+       }
+
+       
+       
+       private static String decode(String s)
+       {
+               if (s.length()<=1)
+               {
+                       return s;
+               }
+               STATE_SPECIAL_CHARS_STATES state = STATE_SPECIAL_CHARS_STATES.NORMAL;
+               
+               String result = "";
+               String buffer = ""; 
+               for(int i=0;i<s.length();i++)
+               {
+                       char c = s.charAt(i);
+                       switch (state)
+                       {
+                               case NORMAL:
+                               {
+                                       switch(c)
+                                       {
+                                               case '_':
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUBSCRIPT;
+                                               break;
+                                               case '^':
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUPERSCRIPT;
+                                               break;
+                                               case '\\':
+                                                       buffer = "";
+                                                       state = STATE_SPECIAL_CHARS_STATES.COMMAND;
+                                               break;
+                                               default:
+                                                       result += c;
+                                                       state = STATE_SPECIAL_CHARS_STATES.NORMAL;
+                                               break;
+                                       }
+                               }
+                               break;
+                               case SUBSCRIPT:
+                               case SUPERSCRIPT:
+                               {
+                                       switch(c)
+                                       {
+                                               case '_':
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUBSCRIPT;
+                                               break;
+                                               case '^':
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUPERSCRIPT;
+                                               break;
+                                               case '\\':
+                                                       buffer = "";
+                                                       state = STATE_SPECIAL_CHARS_STATES.COMMAND;
+                                               break;
+                                               default:
+                                                       if ((state==STATE_SPECIAL_CHARS_STATES.SUBSCRIPT) && _subscripts.containsKey(c))
+                                                               result += _subscripts.get(c); 
+                                                       else if ((state==STATE_SPECIAL_CHARS_STATES.SUPERSCRIPT) && _superscripts.containsKey(c))
+                                                               result += _superscripts.get(c); 
+                                                       else
+                                                               result += c;
+                                                       state = STATE_SPECIAL_CHARS_STATES.NORMAL;
+                                               break;
+                                       }
+                               }
+                               break;
+                               case COMMAND:
+                               {
+                                       switch(c)
+                                       {
+                                               case '_':
+                                                       if (_commands.containsKey(buffer))
+                                                       { result += _commands.get(buffer); }
+                                                       else
+                                                       { result += buffer; }
+                                                       buffer = "";
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUBSCRIPT;
+                                               break;
+                                               case '^':
+                                                       if (_commands.containsKey(buffer))
+                                                       { result += _commands.get(buffer); }
+                                                       else
+                                                       { result += buffer; }
+                                                       buffer = "";
+                                                       state = STATE_SPECIAL_CHARS_STATES.SUPERSCRIPT;
+                                               break;
+                                               case '\\':
+                                                       if (_commands.containsKey(buffer))
+                                                       { result += _commands.get(buffer); }
+                                                       else
+                                                       { result += buffer; }
+                                                       buffer = "";
+                                                       state = STATE_SPECIAL_CHARS_STATES.COMMAND;
+                                               break;
+                                               case ' ':
+                                                       state = STATE_SPECIAL_CHARS_STATES.NORMAL;
+                                                       if (_commands.containsKey(buffer))
+                                                       { 
+                                                               result += _commands.get(buffer);
+                                                       }
+                                                       else
+                                                       {
+                                                               result += buffer;
+                                                       }
+                                                       buffer = "";
+                                               break;
+                                               default:
+                                                       buffer += c;
+                                               break;
+                                       }
+                               }
+                               break;
+                       }
+               }
+               if (_commands.containsKey(buffer))
+               { 
+                       result += _commands.get(buffer);
+               }
+               else
+               {
+                       result += buffer;
+               }
+               return result;
+       }
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleBasesComparison.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleBasesComparison.java
new file mode 100644 (file)
index 0000000..6e9fec7
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.awt.geom.Point2D;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.utils.XMLUtils;
+
+
+/**
+ * <b>The RNA base comparison model</b>. In each bases we'll place <b>two
+ * characters</b> representing nitrogenous bases of both RNA that have to be
+ * compared. So, in each base in the comparison model, we'll have a <b>couple of
+ * bases</b>, with the same coordinates on the final drawing.
+ * 
+ * @author Masson
+ * 
+ */
+public class ModeleBasesComparison extends ModeleBase {
+
+       /*
+        * LOCAL FIELDS
+        */
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -2733063250714562463L;
+
+       /**
+        * The base of the first RNA associated with the base of the second RNA.
+        */
+       private Character _base1;
+
+       /**
+        * The base of the second RNA associated with the base of the first RNA.
+        */
+       private Character _base2;
+
+       /**
+        * This ModeleBasesComparison owning statement. It's value will be 0 if this
+        * base is common for both RNA that had been compared, 1 if this base is
+        * related to the first RNA, 2 if related to the second. Default is -1.
+        */
+       private int _appartenance = -1;
+
+       /**
+        * This base's offset in the sequence
+        */
+       private int _index;
+
+       public static String XML_ELEMENT_NAME = "NTPair";
+       public static String XML_VAR_FIRST_CONTENT_NAME = "base1";
+       public static String XML_VAR_SECOND_CONTENT_NAME = "base2";
+       public static String XML_VAR_MEMBERSHIP_NAME = "type";
+
+       
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("","",XML_VAR_INDEX_NAME,"CDATA",""+_index);
+               atts.addAttribute("","",XML_VAR_NUMBER_NAME,"CDATA",""+_realIndex);
+               atts.addAttribute("","",XML_VAR_CUSTOM_DRAWN_NAME,"CDATA",""+_colorie);
+               atts.addAttribute("","",XML_VAR_LABEL_NAME,"CDATA",""+_label);
+               atts.addAttribute("","",XML_VAR_MEMBERSHIP_NAME,"CDATA",""+_appartenance);
+               atts.addAttribute("","","VALUE","CDATA",""+_value);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               atts.clear();
+               hd.startElement("","",XML_VAR_FIRST_CONTENT_NAME,atts);
+               XMLUtils.exportCDATAString(hd, ""+_base1);
+               hd.endElement("","",XML_VAR_FIRST_CONTENT_NAME);
+               atts.clear();
+               hd.startElement("","",XML_VAR_SECOND_CONTENT_NAME,atts);
+               XMLUtils.exportCDATAString(hd, ""+_base2);
+               hd.endElement("","",XML_VAR_SECOND_CONTENT_NAME);
+               
+               _coords.toXML(hd,XML_VAR_POSITION_NAME);
+               _center.toXML(hd,XML_VAR_CENTER_NAME);
+               if (_colorie)
+               { _styleBase.toXML(hd); }
+               hd.endElement("","",XML_ELEMENT_NAME);
+
+       }
+
+
+       /*
+        * -> END LOCAL FIELDS <--
+        */
+
+       public static Color FIRST_RNA_COLOR = Color.decode("#FFDD99");
+       public static Color SECOND_RNA_COLOR = Color.decode("#99DDFF");
+       public static Color BOTH_RNA_COLOR = Color.decode("#99DD99");
+       public static Color DEFAULT_RNA_COLOR = Color.white;
+
+       /*
+        * CONSTRUCTORS
+        */
+
+       /**
+        * Creates a new comparison base with the default display style and no
+        * nitrogenous bases.
+        */
+       public ModeleBasesComparison(int index) {
+               this(' ', ' ', index);
+       }
+
+       /**
+        * Creates a new comparison base at the specified coordinates, with the
+        * default display style and no nitrogenous bases.
+        * 
+        * @param coords
+        *            - The coordinates in which the comparison base has to be
+        *            placed.
+        */
+       public ModeleBasesComparison(Point2D coords, int index) {
+               this(' ', ' ', new Point2D.Double(coords.getX(), coords.getY()), index);
+       }
+
+       /**
+        * Creates a new comparison base with the specified nitrogenous bases.
+        * 
+        * @param base1
+        *            - The first RNA' nitrogenous base
+        * @param base2
+        *            - The second RNA' nitrogenous base
+        */
+       public ModeleBasesComparison(char base1, char base2, int index) {
+               this(base1, base2, -1, index);
+       }
+
+       /**
+        * Creates a new comparison base with the specified nitrogenous bases, at
+        * the specified coordinates.
+        * 
+        * @param base1
+        *            - The first RNA' nitrogenous base
+        * @param base2
+        *            - The second RNA' nitrogenous base
+        * @param coords
+        *            - The coordinates in which the comparison base has to be
+        *            placed.
+        */
+       public ModeleBasesComparison(char base1, char base2, Point2D coords,
+                       int index) {
+               this(new Point2D.Double(coords.getX(), coords.getY()), base1, base2,
+                               true, new ModelBaseStyle(), -1, index);
+       }
+
+       /**
+        * Creates a new comparison base with the specified nitrogenous bases.
+        * 
+        * @param base1
+        *            - The first RNA' nitrogenous base
+        * @param base2
+        *            - The second RNA' nitrogenous base
+        */
+       public ModeleBasesComparison(char base1, char base2, int elementStructure,
+                       int index) {
+               this(new Point2D.Double(), base1, base2, true, new ModelBaseStyle(),
+                               elementStructure, index);
+       }
+
+       /**
+        * Creates a new comparison base with the specified nitrogenous bases.
+        * 
+        * @param coords
+        *            - This base's XY coordinates
+        * @param base1
+        *            - The first RNA' nitrogenous base
+        * @param base2
+        *            - The second RNA' nitrogenous base
+        * @param colorie
+        *            - Whether or not this base will be drawn
+        * @param mb
+        *            - The drawing style for this base
+        * @param elementStructure
+        *            - The index of a bp partner in the secondary structure
+        * @param index
+        *            - Index of this base in its initial sequence
+        */
+       public ModeleBasesComparison(Point2D coords, char base1, char base2,
+                       boolean colorie, ModelBaseStyle mb, int elementStructure, int index) {
+               _colorie = colorie;
+               _base1 = base1;
+               _base2 = base2;
+               _styleBase = mb;
+               _coords = new VARNAPoint(coords.getX(), coords.getY());
+               _index = index;
+       }
+
+       /*
+        * -> END CONSTRUCTORS <--
+        */
+
+       /*
+        * GETTERS & SETTERS
+        */
+
+       /**
+        * Return the display style associated to this comparison base.
+        * 
+        * @return The display style associated to this comparison base.
+        */
+       public ModelBaseStyle getStyleBase() {
+               if (_colorie)
+                       return _styleBase;
+               return new ModelBaseStyle();
+       }
+
+       /**
+        * Allows to know if this comparison base is colored.
+        * 
+        * @return TRUE if this comparison base is colored, else FALSE.
+        */
+       public Boolean getColored() {
+               return _colorie;
+       }
+
+       /**
+        * Sets the coloration authorization of this comparison base.
+        * 
+        * @param colored
+        *            - TRUE if this comparison base has to be colored, else FALSE.
+        */
+       public void set_colored(Boolean colored) {
+               this._colorie = colored;
+       }
+
+
+       /**
+        * Return the base of the first RNA in this comparison base.
+        * 
+        * @return The base of the first RNA in this comparison base.
+        */
+       public Character getBase1() {
+               return _base1;
+       }
+
+       /**
+        * Sets the base of the first RNA in this comparison base.
+        * 
+        * @param _base1
+        *            - The base of the first RNA in this comparison base.
+        */
+       public void setBase1(Character _base1) {
+               this._base1 = _base1;
+       }
+
+       /**
+        * Return the base of the second RNA in this comparison base.
+        * 
+        * @return The base of the second RNA in this comparison base.
+        */
+       public Character getBase2() {
+               return _base2;
+       }
+
+       /**
+        * Sets the base of the second RNA in this comparison base.
+        * 
+        * @param _base2
+        *            - The base of the second RNA in this comparison base.
+        */
+       public void setBase2(Character _base2) {
+               this._base2 = _base2;
+       }
+
+       /*
+        * --> END GETTERS & SETTERS <--
+        */
+
+       /**
+        * Gets the string representation of the two bases in this
+        * ModeleBasesComparison.
+        * 
+        * @return the string representation of the two bases in this
+        *         ModeleBasesComparison.
+        */
+       public String getBases() {
+               return String.valueOf(_base1) + String.valueOf(_base2);
+       }
+
+       public String getContent() {
+               return getBases();
+       }
+
+
+       /**
+        * Gets this base's related RNA.
+        * 
+        * @return 0 if this base is common for both RNA<br>
+        *         1 if this base is related to the first RNA<br>
+        *         2 if this base is related to the second RNA
+        */
+       public int get_appartenance() {
+               return _appartenance;
+       }
+
+       /**
+        * Sets this base's related RNA.
+        * 
+        * @param _appartenance
+        *            : 0 if this base is common for both RNA<br>
+        *            1 if this base is related to the first RNA<br>
+        *            2 if this base is related to the second RNA.
+        */
+       public void set_appartenance(int _appartenance) {
+               if (_appartenance == 0) {
+                       this.getStyleBase().setBaseInnerColor(BOTH_RNA_COLOR);
+               } else if (_appartenance == 1) {
+                       this.getStyleBase().setBaseInnerColor(FIRST_RNA_COLOR);
+               } else if (_appartenance == 2) {
+                       this.getStyleBase().setBaseInnerColor(SECOND_RNA_COLOR);
+               } else {
+                       this.getStyleBase().setBaseInnerColor(DEFAULT_RNA_COLOR);
+               }
+               this._appartenance = _appartenance;
+       }
+
+
+
+       public int getIndex() {
+               return _index;
+       }
+
+       @Override
+       public void setContent(String s) {
+               this.setBase1(s.charAt(0));
+               this.setBase2(s.charAt(1));
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleColorMap.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleColorMap.java
new file mode 100644 (file)
index 0000000..9570ad4
--- /dev/null
@@ -0,0 +1,357 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.Vector;
+
+public class ModeleColorMap implements Cloneable, Serializable{
+  /**
+        * 
+        */
+       private static final long serialVersionUID = 4055062096061553106L;
+private Vector<Color> _map;
+  private Vector<Double> _values;
+  
+  public static final Color DEFAULT_COLOR = Color.GREEN; 
+  
+  public enum NamedColorMapTypes {
+         RED ("red",ModeleColorMap.redColorMap()),
+         BLUE ("blue",ModeleColorMap.blueColorMap()),
+         GREEN ("green",ModeleColorMap.greenColorMap()),
+         HEAT ("heat",ModeleColorMap.heatColorMap()),
+         ENERGY ("energy",ModeleColorMap.energyColorMap()),
+         ROCKNROLL ("rocknroll",ModeleColorMap.rockNRollColorMap()),
+         VIENNA ("vienna",ModeleColorMap.viennaColorMap()),
+         BW ("bw",ModeleColorMap.bwColorMap());
+         
+         String _id;
+         ModeleColorMap _cm;
+         
+         private NamedColorMapTypes(String id, ModeleColorMap cm)
+         {
+               _id = id;
+               _cm = cm;
+         }
+         
+         public String getId()
+         {
+                 return _id;
+         }
+         public ModeleColorMap getColorMap()
+         {
+                 return _cm;
+         }
+         public String toString()
+         {
+               return _id; 
+         }
+  }
+  
+  
+  public ModeleColorMap()
+  {
+    this(new Vector<Color>(),new Vector<Double>());
+  }
+
+  
+  public ModeleColorMap(Vector<Color> map,
+                 Vector<Double> values)
+  {
+         _map = map;
+         _values = values;
+  }
+  
+  public void addColor(double val, Color col)
+  {
+         int offset = Arrays.binarySearch(_values.toArray(), val) ;
+         if (offset<0)
+         {
+                 int inspoint = (-offset)-1;
+                 _map.insertElementAt(col, inspoint);            
+                 _values.insertElementAt(val,inspoint);                  
+         }
+  }
+
+  public double getMinValue()
+  {
+         if (_values.size()>0)
+           return _values.get(0);
+         return 0.0;
+  }
+  public double getMaxValue()
+  {
+         if (_values.size()>0)
+           return _values.get(_values.size()-1);
+         return 0.0;
+  }
+  
+  public Color getMinColor()
+  {
+         if (_map.size()>0)
+           return _map.get(0);
+         return DEFAULT_COLOR;
+  }
+
+  public Color getMaxColor()
+  {
+         if (_map.size()>0)
+           return _map.get(_map.size()-1);
+         return DEFAULT_COLOR;
+  }
+
+  public int getNumColors()
+  {
+         return (_map.size());
+  }
+  public Color getColorAt(int i)
+  {
+         return (_map.get(i));
+  }
+
+  public Double getValueAt(int i)
+  {
+         return (_values.get(i));
+  }
+  
+  public Color getColorForValue(double val)
+  {
+         Color result; 
+         if (val<=getMinValue())
+         {       result = getMinColor(); }
+         else if (val>=getMaxValue())
+         {
+                 result = getMaxColor();
+         }
+         else
+         {
+         int offset = Arrays.binarySearch(_values.toArray(), val) ;
+         if (offset>=0)
+         {
+                 result =  _map.get(offset);
+         }
+         else
+         {
+                 int inspoint = (-offset)-1;
+                 Color c1 = _map.get(inspoint);
+                 double v1 = _values.get(inspoint);
+                 if (inspoint>0)
+                 {
+                         Color c2 = _map.get(inspoint-1);
+                         double v2 = _values.get(inspoint-1);
+                         double blendCoeff = (v2-val)/(v2-v1);
+                         result =  new Color((int)(blendCoeff*c1.getRed()+(1.0-blendCoeff)*c2.getRed()),
+                                 (int)(blendCoeff*c1.getGreen()+(1.0-blendCoeff)*c2.getGreen()),
+                                 (int)(blendCoeff*c1.getBlue()+(1.0-blendCoeff)*c2.getBlue()));
+                        
+                 }
+                 else
+                 {
+                       result = c1;  
+                 }
+         }
+         }
+         return result;
+  }
+  
+  public static ModeleColorMap energyColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(1.0,new Color(128,50,50).brighter());
+         cm.addColor(0.9,new Color(255,50,50).brighter());
+         cm.addColor(0.65,new Color(255,255,50).brighter());
+         cm.addColor(0.55,new Color(20,255,50).brighter());
+         cm.addColor(0.2,new Color(50,50,255).brighter());
+         cm.addColor(0.0,new Color(50,50,128).brighter());
+         return cm;
+  }
+  
+  public static ModeleColorMap viennaColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,new Color(0,80,220));
+         cm.addColor(0.1,new Color(0,139,220));
+         cm.addColor(0.2,new Color(0,220,218));
+         cm.addColor(0.3,new Color(0,220,123));
+         cm.addColor(0.4,new Color(0,220,49));
+         cm.addColor(0.5,new Color(34,220,0));
+         cm.addColor(0.6,new Color(109,220,0));
+         cm.addColor(0.7,new Color(199,220,0));
+         cm.addColor(0.8,new Color(220,165,0));
+         cm.addColor(0.9,new Color(220,86,0));
+         cm.addColor(1.0,new Color(220,0,0));
+         return cm;
+  }
+  
+  public static ModeleColorMap bwColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.white);
+         cm.addColor(1.0,Color.gray.darker());
+         return cm;
+  }
+
+  public static ModeleColorMap greenColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.gray.brighter().brighter());
+         cm.addColor(1.0,Color.green.darker());
+         return cm;
+  }
+  
+  public static ModeleColorMap blueColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.gray.brighter().brighter());
+         cm.addColor(1.0,Color.blue);
+         return cm;
+  }
+  
+  public static ModeleColorMap redColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.gray.brighter().brighter());
+         cm.addColor(1.0,Color.red);
+         return cm;
+  }
+
+  public static ModeleColorMap heatColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.yellow);
+         cm.addColor(1.0,Color.red);
+         return cm;
+  }
+  
+  public static ModeleColorMap rockNRollColorMap()
+  {
+         ModeleColorMap cm = new ModeleColorMap();
+         cm.addColor(0.0,Color.red.brighter());
+         cm.addColor(1.0,Color.black);
+         cm.addColor(2.0,Color.green.brighter());
+         return cm;      
+  }
+
+  
+  public static ModeleColorMap defaultColorMap()
+  {
+         return energyColorMap();
+  }
+  
+  public static ModeleColorMap parseColorMap(String s)
+  {
+         String[] data = s.split("[;,]");
+         if (data.length==1)
+         {
+                 String name = data[0].toLowerCase();
+                 for (NamedColorMapTypes p : NamedColorMapTypes.values())
+                 {
+                         if (name.equals(p.getId().toLowerCase()))
+                         {
+                                 return p.getColorMap();
+                         }
+                 }
+                 return ModeleColorMap.defaultColorMap();
+         }
+         else
+         {
+                 ModeleColorMap cm = new ModeleColorMap();
+                 
+                 for(int i=0;i<data.length;i++)
+                 {
+                         String[] data2 = data[i].split(":");
+                         if (data2.length==2)
+                         {
+                                 try{
+                                         Double val = Double.parseDouble(data2[0]);
+                                         Color col = Color.decode(data2[1]);
+                                         cm.addColor(val, col);
+                                 }
+                                 catch(Exception e)
+                                 {  
+                                 }
+                         }
+                 }
+                 if (cm.getNumColors()>1)
+                         return cm;
+         }
+         return ModeleColorMap.defaultColorMap();
+  }
+  
+  public void setMinValue(double newMin)
+  {
+         rescale(newMin,getMaxValue());
+  }
+
+  public void setMaxValue(double newMax)
+  {
+         rescale(getMinValue(),newMax);
+  }
+
+  public void rescale(double newMin, double newMax)
+  {
+         double minBck = getMinValue();
+         double maxBck = getMaxValue();
+         double spanBck = maxBck-minBck;
+         if (newMax!=newMin)
+         {
+             newMax = Math.max(newMax,newMin+1.0);
+                 for (int i=0;i<_values.size();i++)
+                 {
+                         double valBck = _values.get(i);
+                         _values.set(i, newMin+(newMax-newMin)*(valBck-minBck)/(spanBck));
+                 }
+         }
+  }
+  
+  public ModeleColorMap clone()
+  {
+         ModeleColorMap cm = new ModeleColorMap(); 
+         cm._map = (Vector<Color>) _map.clone();
+         cm._values = (Vector<Double>)_values.clone();
+         return cm;
+  }
+  
+  public boolean equals(ModeleColorMap cm)
+  {
+         if ( getNumColors()!=cm.getNumColors())
+                 return false;
+         for (int i=0;i<getNumColors();i++)
+         {
+                 if ((!getColorAt(i).equals(cm.getColorAt(i))) || (!getValueAt(i).equals(cm.getValueAt(i))) )
+                                 return false;
+         }
+         return true;
+                 
+  }
+  
+  public String getParamEncoding()
+  {
+         String result = "";
+         Formatter f = new Formatter();
+         for(int i=0;i<getNumColors();i++)
+         {
+                 if (i!=0)
+                         f.format(",");
+                 f.format("%.2f:#%02X%02X%02X", _values.get(i),_map.get(i).getRed(),_map.get(i).getGreen(),_map.get(i).getBlue());
+         }
+         return f.out().toString();
+  }
+  
+
+  
+  public String toString()
+  {
+         return getParamEncoding();
+  }
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/ModeleStrand.java b/srcjar/fr/orsay/lri/varna/models/rna/ModeleStrand.java
new file mode 100644 (file)
index 0000000..1af1454
--- /dev/null
@@ -0,0 +1,79 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.util.ArrayList;
+
+public class ModeleStrand {
+       
+       private ArrayList<ModeleBase> _strand = new ArrayList<ModeleBase>();
+       private boolean hasBeenPlaced = false;
+       private boolean strandLeft = false;
+       private boolean strandRight = false;
+       private int levelPosition;
+       
+       public ModeleStrand(){
+               
+       }
+       
+       public void addBase(ModeleBase mb){
+               this._strand.add(mb);
+       }
+       
+       public void addBase(int index, ModeleBase mb){
+               this._strand.add(index, mb);
+       }
+       
+       public int sizeStrand() {
+               return this._strand.size();
+       }
+       
+       public ModeleBase getMB(int a) {        
+               return this._strand.get(a);     
+       }
+       
+       public ArrayList<ModeleBase> getArrayListMB() { 
+               return this._strand;    
+       }
+       
+       public int getLevelPosition(){
+               return this.levelPosition;
+       }
+       
+       public void setLevelPosition(int a){
+               this.levelPosition=a;
+       }
+       
+       public boolean getStrandRight(){
+               return this.strandRight;
+       }
+       
+       public void setStrandRight(boolean bool){
+               this.strandRight=bool;
+       }
+       
+       public boolean getStrandLeft(){
+               return this.strandLeft;
+       }
+       
+       public void setStrandLeft(boolean bool){
+               this.strandLeft=bool;
+       }
+       
+       public boolean hasBeenPlaced(){
+               return this.hasBeenPlaced;
+       }
+       
+       public void setHasBeenPlaced(boolean bool){
+               this.hasBeenPlaced =bool;
+       }
+       
+       public boolean existInStrand(int a){
+               int size =sizeStrand();
+               boolean exist=false; 
+               for (int i=0; i<size;i++){
+                       if(a==this.getMB(i).getIndex()){
+                               exist=true;
+                       }
+               }
+               return exist;
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/RNA.java b/srcjar/fr/orsay/lri/varna/models/rna/RNA.java
new file mode 100644 (file)
index 0000000..4b530e0
--- /dev/null
@@ -0,0 +1,4164 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.templateEditor.Couple;
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener;
+import fr.orsay.lri.varna.interfaces.InterfaceVARNAObservable;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.VARNAConfig.BP_STYLE;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.export.PSExport;
+import fr.orsay.lri.varna.models.export.SVGExport;
+import fr.orsay.lri.varna.models.export.SecStrDrawingProducer;
+import fr.orsay.lri.varna.models.export.TikzExport;
+import fr.orsay.lri.varna.models.export.XFIGExport;
+import fr.orsay.lri.varna.models.naView.NAView;
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement.BackboneType;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateCurveMethod;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateMethod;
+import fr.orsay.lri.varna.models.templates.RNATemplate;
+import fr.orsay.lri.varna.models.templates.RNATemplateDrawingAlgorithmException;
+import fr.orsay.lri.varna.models.templates.RNATemplateMapping;
+import fr.orsay.lri.varna.utils.RNAMLParser;
+import fr.orsay.lri.varna.utils.XMLUtils;
+import fr.orsay.lri.varna.views.VueUI;
+
+/**
+ * The RNA model which contain the base list and the draw algorithm mode
+ * 
+ * @author darty
+ * 
+ */
+public class RNA extends InterfaceVARNAObservable implements Serializable {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 7541274455751497303L;
+
+       /**
+        * Selects the "Feynman diagram" drawing algorithm that places the bases on
+        * a circle and draws the base-pairings as chords of the circle graph.
+        */
+
+       public static final int DRAW_MODE_CIRCULAR = 1;
+       /**
+        * Selects the "tree drawing" algorithm. Draws each loop on a circle whose
+        * radius depends on the number of bases involved in the loop. As some
+        * helices can be overlapping in the result, basic interaction is provided
+        * so that the user can "disentangle" the drawing by spinning the helices
+        * around the axis defined by their multiloop (bulge or internal loop)
+        * origin. This is roughly the initial placement strategy of RNAViz.
+        * 
+        * @see <a href="http://rnaviz.sourceforge.net/">RNAViz</a>
+        */
+       public static final int DRAW_MODE_RADIATE = 2;
+
+       /**
+        * Selects the NAView algorithm.
+        */
+       public static final int DRAW_MODE_NAVIEW = 3;
+       /**
+        * Selects the linear algorithm.
+        */
+       public static final int DRAW_MODE_LINEAR = 4;
+
+       public static final int DRAW_MODE_VARNA_VIEW = 5;
+
+       /**
+        * Selects the RNAView algorithm.
+        */
+       public static final int DRAW_MODE_MOTIFVIEW = 6;
+
+       public static final int DRAW_MODE_TEMPLATE = 7;
+
+       public static final int DEFAULT_DRAW_MODE = DRAW_MODE_RADIATE;
+
+       public int BASE_RADIUS = 10;
+       public static final double LOOP_DISTANCE = 40.0; // distance between base
+                                                                                                               // pairs in an helix
+       public static final double BASE_PAIR_DISTANCE = 65.0; // distance between
+                                                                                                                       // the two bases of
+                                                                                                                       // a pair
+       public static final double MULTILOOP_DISTANCE = 35.0;
+       public static final double VIRTUAL_LOOP_RADIUS = 40.0;
+
+       public double CHEM_PROB_DIST = 14;
+       public double CHEM_PROB_BASE_LENGTH = 30;
+       public double CHEM_PROB_ARROW_HEIGHT = 10;
+       public double CHEM_PROB_ARROW_WIDTH = 5;
+       public double CHEM_PROB_TRIANGLE_WIDTH = 2.5;
+       public double CHEM_PROB_PIN_SEMIDIAG = 6;
+       public double CHEM_PROB_DOT_RADIUS = 6.;
+       public static double CHEM_PROB_ARROW_THICKNESS = 2.0;
+
+       public static ArrayList<String> NormalBases = new ArrayList<String>();
+       {
+               NormalBases.add("a");
+               NormalBases.add("c");
+               NormalBases.add("g");
+               NormalBases.add("u");
+               NormalBases.add("t");
+       }
+
+       public GeneralPath _debugShape = null;
+
+       /**
+        * The draw algorithm mode
+        */
+       private int _drawMode = DRAW_MODE_RADIATE;
+       private boolean _drawn = false;
+       private String _name = "";
+       private String _id = "";
+       public double _bpHeightIncrement = VARNAConfig.DEFAULT_BP_INCREMENT;
+       /**
+        * the base list
+        */
+       private ArrayList<ModeleBase> _listeBases;
+       /**
+        * the strand list
+        */
+       StructureTemp _listStrands = new StructureTemp();
+       /**
+        * Additional bonds and info can be specified here.
+        */
+       private ArrayList<ModeleBP> _structureAux = new ArrayList<ModeleBP>();
+       private ArrayList<TextAnnotation> _listeAnnotations = new ArrayList<TextAnnotation>();
+       private ArrayList<HighlightRegionAnnotation> _listeRegionHighlights = new ArrayList<HighlightRegionAnnotation>();
+       private ArrayList<ChemProbAnnotation> _chemProbAnnotations = new ArrayList<ChemProbAnnotation>();
+       private ModeleBackbone _backbone = new ModeleBackbone();
+
+       public static String XML_ELEMENT_NAME = "RNA";
+       public static String XML_VAR_BASE_SPACING_NAME = "spacing";
+       public static String XML_VAR_DRAWN_NAME = "drawn";
+       public static String XML_VAR_NAME_NAME = "name";
+       public static String XML_VAR_DRAWN_MODE_NAME = "mode";
+       public static String XML_VAR_ID_NAME = "id";
+       public static String XML_VAR_BP_HEIGHT_NAME = "delta";
+       public static String XML_VAR_BASES_NAME = "bases";
+       public static String XML_VAR_BASEPAIRS_NAME = "BPs";
+       public static String XML_VAR_ANNOTATIONS_NAME = "annotations";
+       public static String XML_VAR_BACKBONE_NAME = "backbone";
+
+       public void toXML(TransformerHandler hd) throws SAXException {
+               AttributesImpl atts = new AttributesImpl();
+               atts.addAttribute("", "", XML_VAR_DRAWN_NAME, "CDATA", "" + _drawn);
+               atts.addAttribute("", "", XML_VAR_DRAWN_MODE_NAME, "CDATA", ""
+                               + _drawMode);
+               atts.addAttribute("", "", XML_VAR_ID_NAME, "CDATA", "" + _id);
+               atts.addAttribute("", "", XML_VAR_BP_HEIGHT_NAME, "CDATA", ""
+                               + _bpHeightIncrement);
+               hd.startElement("", "", XML_ELEMENT_NAME, atts);
+
+               atts.clear();
+               hd.startElement("", "", XML_VAR_NAME_NAME, atts);
+               XMLUtils.exportCDATAString(hd, "" + _name);
+               hd.endElement("", "", XML_VAR_NAME_NAME);
+
+               atts.clear();
+               hd.startElement("", "", XML_VAR_BASES_NAME, atts);
+               for (ModeleBase mb : _listeBases) {
+                       mb.toXML(hd);
+               }
+               hd.endElement("", "", XML_VAR_BASES_NAME);
+               atts.clear();
+
+               hd.startElement("", "", XML_VAR_BASEPAIRS_NAME, atts);
+               for (ModeleBP mbp : getSecStrBPs()) {
+                       mbp.toXML(hd, true);
+               }
+               for (ModeleBP mbp : _structureAux) {
+                       mbp.toXML(hd, false);
+               }
+               hd.endElement("", "", XML_VAR_BASEPAIRS_NAME);
+               atts.clear();
+
+               getBackbone().toXML(hd);
+               atts.clear();
+
+               hd.startElement("", "", XML_VAR_ANNOTATIONS_NAME, atts);
+               for (TextAnnotation ta : _listeAnnotations) {
+                       ta.toXML(hd);
+               }
+               for (HighlightRegionAnnotation hra : _listeRegionHighlights) {
+                       hra.toXML(hd);
+               }
+               for (ChemProbAnnotation cpa : _chemProbAnnotations) {
+                       cpa.toXML(hd);
+               }
+               hd.endElement("", "", XML_VAR_ANNOTATIONS_NAME);
+               hd.endElement("", "", XML_ELEMENT_NAME);
+       }
+
+       public ModeleBackbone getBackbone() {
+               return _backbone;
+       }
+
+       public void setBackbone(ModeleBackbone b) {
+               _backbone = b;
+       }
+
+       transient private ArrayList<InterfaceVARNAListener> _listeVARNAListener = new ArrayList<InterfaceVARNAListener>();
+
+       public RNA() {
+               this("");
+       }
+
+       public RNA(String name) {
+               _name = name;
+               _listeBases = new ArrayList<ModeleBase>();
+               _drawn = false;
+               init();
+       }
+
+       public String toString() {
+               if (_name.equals("")) {
+                       return getStructDBN();
+               } else {
+                       return _name;
+               }
+       }
+
+       public RNA(RNA r) {
+               _drawMode = r._drawMode;
+               _listeBases.addAll(r._listeBases);
+               _listeVARNAListener = (ArrayList<InterfaceVARNAListener>) r._listeVARNAListener;
+               _drawn = r._drawn;
+               init();
+       }
+
+       public void init() {
+       }
+
+       public void saveRNADBN(String path, String title)
+                       throws ExceptionWritingForbidden {
+               try {
+                       FileWriter out = new FileWriter(path);
+                       if (!title.equals("")) {
+                               out.write("> " + title + "\n");
+                       }
+                       out.write(getListeBasesToString());
+                       out.write('\n');
+                       String str = "";
+                       for (int i = 0; i < _listeBases.size(); i++) {
+                               if (_listeBases.get(i).getElementStructure() == -1) {
+                                       str += '.';
+                               } else {
+                                       if (_listeBases.get(i).getElementStructure() > i) {
+                                               str += '(';
+                                       } else {
+                                               str += ')';
+                                       }
+                               }
+                       }
+                       out.write(str);
+                       out.write('\n');
+                       out.close();
+               } catch (IOException e) {
+                       throw new ExceptionWritingForbidden(e.getMessage());
+               }
+       }
+
+       public Color getBaseInnerColor(int i, VARNAConfig conf) {
+               Color result = _listeBases.get(i).getStyleBase().getBaseInnerColor();
+               String res = _listeBases.get(i).getContent();
+               if (conf._drawColorMap) {
+                       result = conf._cm.getColorForValue(_listeBases.get(i).getValue());
+               } else if ((conf._colorDashBases && (res.contains("-")))) {
+                       result = conf._dashBasesColor;
+               } else if ((conf._colorSpecialBases && !NormalBases.contains(res
+                               .toLowerCase()))) {
+                       result = conf._specialBasesColor;
+               }
+               return result;
+       }
+
+       public Color getBaseOuterColor(int i, VARNAConfig conf) {
+               Color result = _listeBases.get(i).getStyleBase()
+                               .getBaseOutlineColor();
+               return result;
+       }
+
+       private static double correctComponent(double c)
+       {
+           c = c / 255.0;
+           if (c <= 0.03928) 
+               c = c/12.92;
+           else 
+               c = Math.pow(((c+0.055)/1.055) , 2.4);
+           return c;
+       }
+       public static double getLuminance(Color c)
+       {
+               return 0.2126 * correctComponent(c.getRed()) + 0.7152 * correctComponent(c.getGreen()) + 0.0722 * correctComponent(c.getBlue());
+       }
+       
+       public static boolean whiteLabelPreferrable(Color c)
+       {
+               if (getLuminance(c) > 0.32)
+                       return false;
+               return true;
+       }
+       
+
+       
+       public Color getBaseNameColor(int i, VARNAConfig conf) {
+               Color result = _listeBases.get(i).getStyleBase().getBaseNameColor();
+               if ( RNA.whiteLabelPreferrable(getBaseInnerColor(i, conf)))
+               {
+                       result=Color.white;
+               }
+
+               return result;
+       }
+
+       public Color getBasePairColor(ModeleBP bp, VARNAConfig conf) {
+               Color bondColor = conf._bondColor;
+               if (conf._useBaseColorsForBPs) {
+                       bondColor = _listeBases.get(bp.getPartner5().getIndex())
+                                       .getStyleBase().getBaseInnerColor();
+               }
+               if (bp != null) {
+                       bondColor = bp.getStyle().getColor(bondColor);
+               }
+               return bondColor;
+       }
+
+       public double getBasePairThickness(ModeleBP bp, VARNAConfig conf) {
+               double thickness = bp.getStyle().getThickness(conf._bpThickness);
+               return thickness;
+       }
+
+       private void drawSymbol(SecStrDrawingProducer out, double posx,
+                       double posy, double normx, double normy, double radius,
+                       boolean isCIS, ModeleBP.Edge e, double thickness) {
+               Color bck = out.getCurrentColor();
+               switch (e) {
+               case WC:
+                       if (isCIS) {
+                               out.fillCircle(posx, posy, (radius / 2.0), thickness, bck);
+                       } else {
+                               out.fillCircle(posx, posy, (radius / 2.0), thickness,
+                                               Color.white);
+                               out.setColor(bck);
+                               out.drawCircle(posx, posy, (radius / 2.0), thickness);
+                       }
+                       break;
+               case HOOGSTEEN: {
+                       double xtab[] = new double[4];
+                       double ytab[] = new double[4];
+                       xtab[0] = posx - radius * normx / 2.0 - radius * normy / 2.0;
+                       ytab[0] = posy - radius * normy / 2.0 + radius * normx / 2.0;
+                       xtab[1] = posx + radius * normx / 2.0 - radius * normy / 2.0;
+                       ytab[1] = posy + radius * normy / 2.0 + radius * normx / 2.0;
+                       xtab[2] = posx + radius * normx / 2.0 + radius * normy / 2.0;
+                       ytab[2] = posy + radius * normy / 2.0 - radius * normx / 2.0;
+                       xtab[3] = posx - radius * normx / 2.0 + radius * normy / 2.0;
+                       ytab[3] = posy - radius * normy / 2.0 - radius * normx / 2.0;
+                       if (isCIS) {
+                               out.fillPolygon(xtab, ytab, bck);
+                       } else {
+                               out.fillPolygon(xtab, ytab, Color.white);
+                               out.setColor(bck);
+                               out.drawPolygon(xtab, ytab, thickness);
+                       }
+               }
+                       break;
+               case SUGAR: {
+                       double ix = radius * normx / 2.0;
+                       double iy = radius * normy / 2.0;
+                       double jx = radius * normy / 2.0;
+                       double jy = -radius * normx / 2.0;
+                       double xtab[] = new double[3];
+                       double ytab[] = new double[3];
+                       xtab[0] = posx - ix + jx;
+                       ytab[0] = posy - iy + jy;
+                       xtab[1] = posx + ix + jx;
+                       ytab[1] = posy + iy + jy;
+                       xtab[2] = posx - jx;
+                       ytab[2] = posy - jy;
+
+                       if (isCIS) {
+                               out.fillPolygon(xtab, ytab, bck);
+                       } else {
+                               out.fillPolygon(xtab, ytab, Color.white);
+                               out.setColor(bck);
+                               out.drawPolygon(xtab, ytab, thickness);
+                       }
+               }
+                       break;
+               }
+               out.setColor(bck);
+       }
+
+       private void drawBasePairArc(SecStrDrawingProducer out, int i, int j,
+                       Point2D.Double orig, Point2D.Double dest, ModeleBP style,
+                       VARNAConfig conf) {
+               double coef;
+               double distance;
+               Point2D.Double center = new Point2D.Double((orig.x + dest.x)/2., (orig.y + dest.y)/2. + BASE_RADIUS); 
+               if (j - i == 1)
+                       coef = _bpHeightIncrement * 2;
+               else
+                       coef = _bpHeightIncrement * 1;
+               distance = (int) Math.round(dest.x - orig.x);
+               if (conf._mainBPStyle != BP_STYLE.LW) {
+                       out.drawArc(center, distance, distance * coef, 180, 0);
+               } else {
+                       double thickness = getBasePairThickness(style, conf);
+                       double radiusCircle = ((BASE_PAIR_DISTANCE - BASE_RADIUS) / 5.0);
+
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((orig.x != dest.x) || (orig.y != dest.y)) {
+                                               out.drawArc(center, distance - BASE_RADIUS / 2.,
+                                                               distance * coef - BASE_RADIUS / 2, 180, 0);
+                                               out.drawArc(center, distance + BASE_RADIUS / 2.,
+                                                               distance * coef + BASE_RADIUS / 2, 180, 0);
+                                       }
+                               } else if (!style.isWobbleUG()) {
+                                       out.drawArc(center, distance, distance * coef, 180, 0);
+                                       drawSymbol(out, center.x, center.y + distance * coef / 2., 180., 0,
+                                                       radiusCircle, style.isCIS(),
+                                                       style.getEdgePartner5(), thickness);
+                               } else {
+                                       out.drawArc(orig, distance, distance * coef, 180, 0);
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               out.drawArc(center, distance, distance * coef, 180, 0);
+                               if (p1 == p2) {
+                                       drawSymbol(out, center.x, center.y + distance * coef / 2., 1., 0,
+                                                       radiusCircle, style.isCIS(),
+                                                       style.getEdgePartner5(), thickness);
+                               } else {
+                                       drawSymbol(out, center.x - BASE_RADIUS,
+                                                       center.y + distance * coef / 2., 1., 0, radiusCircle,
+                                                       style.isCIS(), p1, thickness);
+                                       drawSymbol(out, center.x + BASE_RADIUS,
+                                                       center.y + distance * coef / 2., 1., 0, radiusCircle,
+                                                       style.isCIS(), p2, thickness);
+                               }
+                       }
+               }
+       }
+
+       private void drawBasePair(SecStrDrawingProducer out, Point2D.Double orig,
+                       Point2D.Double dest, ModeleBP style, VARNAConfig conf) {
+               double dx = dest.x - orig.x;
+               double dy = dest.y - orig.y;
+               double dist = Math.sqrt((dest.x - orig.x) * (dest.x - orig.x)
+                               + (dest.y - orig.y) * (dest.y - orig.y));
+               dx /= dist;
+               dy /= dist;
+               double nx = -dy;
+               double ny = dx;
+               orig = new Point2D.Double(orig.x + BASE_RADIUS * dx, orig.y
+                               + BASE_RADIUS * dy);
+               dest = new Point2D.Double(dest.x - BASE_RADIUS * dx, dest.y
+                               - BASE_RADIUS * dy);
+               if (conf._mainBPStyle == VARNAConfig.BP_STYLE.LW) {
+                       double thickness = getBasePairThickness(style, conf);
+                       double radiusCircle = ((BASE_PAIR_DISTANCE - BASE_RADIUS) / 5.0);
+
+                       if (style.isCanonical()) {
+                               if (style.isCanonicalGC()) {
+                                       if ((orig.x != dest.x) || (orig.y != dest.y)) {
+                                               nx *= BASE_RADIUS / 4.0;
+                                               ny *= BASE_RADIUS / 4.0;
+                                               out.drawLine((orig.x + nx), (orig.y + ny),
+                                                               (dest.x + nx), (dest.y + ny), conf._bpThickness);
+                                               out.drawLine((orig.x - nx), (orig.y - ny),
+                                                               (dest.x - nx), (dest.y - ny), conf._bpThickness);
+                                       }
+                               } else if (style.isCanonicalAU()) {
+                                       out.drawLine(orig.x, orig.y, dest.x, dest.y,
+                                                       conf._bpThickness);
+                               } else if (style.isWobbleUG()) {
+                                       double cx = (dest.x + orig.x) / 2.0;
+                                       double cy = (dest.y + orig.y) / 2.0;
+                                       out.drawLine(orig.x, orig.y, dest.x, dest.y,
+                                                       conf._bpThickness);
+                                       drawSymbol(out, cx, cy, nx, ny, radiusCircle, false,
+                                                       ModeleBP.Edge.WC, thickness);
+                               }
+
+                               else {
+                                       double cx = (dest.x + orig.x) / 2.0;
+                                       double cy = (dest.y + orig.y) / 2.0;
+                                       out.drawLine(orig.x, orig.y, dest.x, dest.y,
+                                                       conf._bpThickness);
+                                       drawSymbol(out, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), style.getEdgePartner5(), thickness);
+                               }
+                       } else {
+                               ModeleBP.Edge p1 = style.getEdgePartner5();
+                               ModeleBP.Edge p2 = style.getEdgePartner3();
+                               double cx = (dest.x + orig.x) / 2.0;
+                               double cy = (dest.y + orig.y) / 2.0;
+                               out.drawLine(orig.x, orig.y, dest.x, dest.y, conf._bpThickness);
+                               if (p1 == p2) {
+                                       drawSymbol(out, cx, cy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1, thickness);
+                               } else {
+                                       double vdx = (dest.x - orig.x);
+                                       double vdy = (dest.y - orig.y);
+                                       vdx /= 6.0;
+                                       vdy /= 6.0;
+                                       drawSymbol(out, cx + vdx, cy + vdy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p2, thickness);
+                                       drawSymbol(out, cx - vdx, cy - vdy, nx, ny, radiusCircle,
+                                                       style.isCIS(), p1, thickness);
+                               }
+                       }
+               } else if (conf._mainBPStyle == VARNAConfig.BP_STYLE.RNAVIZ) {
+                       double xcenter = (orig.x + dest.x) / 2.0;
+                       double ycenter = (orig.y + dest.y) / 2.0;
+                       out.fillCircle(xcenter, ycenter, 3.0 * conf._bpThickness,
+                                       conf._bpThickness, out.getCurrentColor());
+               } else if (conf._mainBPStyle == VARNAConfig.BP_STYLE.SIMPLE) {
+                       out.drawLine(orig.x, orig.y, dest.x, dest.y, conf._bpThickness);
+               }
+       }
+
+       private void drawColorMap(VARNAConfig _conf, SecStrDrawingProducer out) {
+               double v1 = _conf._cm.getMinValue();
+               double v2 = _conf._cm.getMaxValue();
+               int x, y;
+               double xSpaceAvail = 0;
+               double ySpaceAvail = 0;
+               double thickness = 1.0;
+               /*
+                * ySpaceAvail =
+                * Math.min((getHeight()-rnabbox.height*scaleFactor-getTitleHeight
+                * ())/2.0,scaleFactor*(_conf._colorMapHeight+VARNAConfig.
+                * DEFAULT_COLOR_MAP_FONT_SIZE)); if ((int)ySpaceAvail==0) { xSpaceAvail
+                * =
+                * Math.min((getWidth()-rnabbox.width*scaleFactor)/2,scaleFactor*(_conf
+                * ._colorMapWidth)+VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH); }
+                */
+               Rectangle2D.Double currentBBox = out.getBoundingBox();
+
+               double xBase = (currentBBox.getMaxX() - _conf._colorMapWidth - _conf._colorMapXOffset);
+               // double yBase = (minY - _conf._colorMapHeight +
+               // _conf._colorMapYOffset);
+               double yBase = (currentBBox.getMinY() - _conf._colorMapHeight - VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE);
+
+               for (int i = 0; i < _conf._colorMapWidth; i++) {
+                       double ratio = (((double) i) / ((double) _conf._colorMapWidth - 1));
+                       double val = v1 + (v2 - v1) * ratio;
+                       Color c = _conf._cm.getColorForValue(val);
+                       x = (int) (xBase + i);
+                       y = (int) yBase;
+                       out.fillRectangle(x, y, VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH,
+                                       _conf._colorMapHeight, c);
+               }
+               out.setColor(VARNAConfig.DEFAULT_COLOR_MAP_OUTLINE);
+               out.drawRectangle(xBase, yBase, (double) _conf._colorMapWidth
+                               + VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH - 1,
+                               _conf._colorMapHeight, thickness);
+
+               out.setColor(VARNAConfig.DEFAULT_COLOR_MAP_FONT_COLOR);
+               out.setFont(out.getCurrentFont(),
+                               VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.5);
+               out.drawText(xBase, yBase + _conf._colorMapHeight
+                               + VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7,
+                               "" + _conf._cm.getMinValue());
+               out.drawText(xBase + VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH
+                               + _conf._colorMapWidth, yBase + _conf._colorMapHeight
+                               + VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7,
+                               "" + _conf._cm.getMaxValue());
+               out.drawText(
+                               xBase
+                                               + (VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH + _conf._colorMapWidth)
+                                               / 2.0, yBase
+                                               - (VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE / 1.7),
+                               _conf._colorMapCaption);
+
+       }
+
+       private void renderRegionHighlights(SecStrDrawingProducer out,
+                       Point2D.Double[] realCoords, Point2D.Double[] realCenters) {
+               for (HighlightRegionAnnotation r : _listeRegionHighlights) {
+                       GeneralPath s = r.getShape(realCoords, realCenters, 1.0);
+                       out.setColor(r.getFillColor());
+                       out.fillPolygon(s, r.getFillColor());
+                       out.setColor(r.getOutlineColor());
+                       out.drawPolygon(s, 1l);
+               }
+
+       }
+
+       private void saveRNA(String path, VARNAConfig conf, double scale,
+                       SecStrDrawingProducer out) throws ExceptionWritingForbidden {
+               out.setScale(scale);
+               // Computing bounding boxes
+               double EPSMargin = 40;
+               double minX = Double.MAX_VALUE;
+               double maxX = Double.MIN_VALUE;
+               double minY = Double.MAX_VALUE;
+               double maxY = Double.MIN_VALUE;
+
+               double x0, y0, x1, y1, xc, yc, xp, yp, dx, dy, norm;
+
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       minX = Math.min(minX, (_listeBases.get(i).getCoords().getX()
+                                       - BASE_RADIUS - EPSMargin));
+                       minY = Math.min(minY, -(_listeBases.get(i).getCoords().getY()
+                                       - BASE_RADIUS - EPSMargin));
+                       maxX = Math.max(maxX, (_listeBases.get(i).getCoords().getX()
+                                       + BASE_RADIUS + EPSMargin));
+                       maxY = Math.max(maxY, -(_listeBases.get(i).getCoords().getY()
+                                       + BASE_RADIUS + EPSMargin));
+               }
+
+               // Rescaling everything
+               Point2D.Double[] coords = new Point2D.Double[_listeBases.size()];
+               Point2D.Double[] centers = new Point2D.Double[_listeBases.size()];
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       xp = (_listeBases.get(i).getCoords().getX() - minX);
+                       yp = -(_listeBases.get(i).getCoords().getY() - minY);
+                       coords[i] = new Point2D.Double(xp, yp);
+
+                       Point2D.Double centerBck = getCenter(i);
+                       if (get_drawMode() == RNA.DRAW_MODE_NAVIEW
+                                       || get_drawMode() == RNA.DRAW_MODE_RADIATE) {
+                               if ((_listeBases.get(i).getElementStructure() != -1)
+                                               && i < _listeBases.size() - 1 && i > 1) {
+                                       ModeleBase b1 = get_listeBases().get(i - 1);
+                                       ModeleBase b2 = get_listeBases().get(i + 1);
+                                       int j1 = b1.getElementStructure();
+                                       int j2 = b2.getElementStructure();
+                                       if ((j1 == -1) ^ (j2 == -1)) {
+                                               // alors la position du nombre associé doit etre
+                                               // décalé
+                                               Point2D.Double a1 = b1.getCoords();
+                                               Point2D.Double a2 = b2.getCoords();
+                                               Point2D.Double c1 = b1.getCenter();
+                                               Point2D.Double c2 = b2.getCenter();
+
+                                               centerBck.x = _listeBases.get(i).getCoords().x
+                                                               + (c1.x - a1.x) / c1.distance(a1)
+                                                               + (c2.x - a2.x) / c2.distance(a2);
+                                               centerBck.y = _listeBases.get(i).getCoords().y
+                                                               + (c1.y - a1.y) / c1.distance(a1)
+                                                               + (c2.y - a2.y) / c2.distance(a2);
+                                       }
+                               }
+                       }
+                       xc = (centerBck.getX() - minX);
+                       yc = -(centerBck.getY() - minY);
+                       centers[i] = new Point2D.Double(xc, yc);
+               }
+
+               // Drawing background
+               if (conf._drawBackground)
+                       out.setBackgroundColor(conf._backgroundColor);
+
+               // Drawing region highlights
+               renderRegionHighlights(out, coords, centers);
+
+               // Drawing backbone
+               if (conf._drawBackbone)
+               {
+                       for (int i = 1; i < _listeBases.size(); i++) {
+                               Point2D.Double p1 = coords[i - 1];
+                               Point2D.Double p2 = coords[i];
+                               x0 = p1.x;
+                               y0 = p1.y;
+                               x1 = p2.x;
+                               y1 = p2.y;
+                               Point2D.Double vn = new Point2D.Double();
+                               double dist = p1.distance(p2);
+                               int a = _listeBases.get(i - 1).getElementStructure();
+                               int b = _listeBases.get(i).getElementStructure();
+                               BackboneType bt = _backbone.getTypeBefore(i);
+                               boolean consecutivePair = (a == i) && (b == i - 1);
+       
+                               if (dist > 0) {
+                                       if (bt != BackboneType.DISCONTINUOUS_TYPE) {
+                                               Color c = _backbone.getColorBefore(i, conf._backboneColor);
+                                               if (bt == BackboneType.MISSING_PART_TYPE) {
+                                                       c.brighter();
+                                               }
+                                               out.setColor(c);
+       
+                                               vn.x = (x1 - x0) / dist;
+                                               vn.y = (y1 - y0) / dist;
+                                               if (consecutivePair
+                                                               &&(getDrawMode() != RNA.DRAW_MODE_LINEAR)
+                                                               && (getDrawMode() != RNA.DRAW_MODE_CIRCULAR)) {
+                                                       int dir = 0;
+                                                       if (i + 1 < coords.length) {
+                                                               dir = (testDirectionality(i - 1, i, i + 1) ? 1 : -1);
+                                                       } else if (i - 2 >= 0) {
+                                                               dir = (testDirectionality(i - 2, i - 1, i) ? 1 : -1);
+                                                       }
+                                                       Point2D.Double centerSeg = new Point2D.Double(
+                                                                       (p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0);
+                                                       double centerDist = RNA.VIRTUAL_LOOP_RADIUS * scale;
+                                                       Point2D.Double centerLoop = new Point2D.Double(
+                                                                       centerSeg.x + centerDist * dir * vn.y,
+                                                                       centerSeg.y - centerDist * dir * vn.x);
+                                                       // Debug crosshair
+                                                       //out.drawLine(centerLoop.x - 5, centerLoop.y,
+                                                       //              centerLoop.x + 5, centerLoop.y, 2.0);
+                                                       //out.drawLine(centerLoop.x, centerLoop.y - 5,
+                                                       //              centerLoop.x, centerLoop.y + 5, 2.0);
+                                                       
+                                                       double radius = centerLoop.distance(p1);
+                                                       double a1 = 360.
+                                                                       * (Math.atan2(p1.y - centerLoop.y, p1.x - centerLoop.x))
+                                                                       / (2. * Math.PI);
+                                                       double a2 = 360.
+                                                                       * (Math.atan2(p2.y - centerLoop.y, p2.x - centerLoop.x))
+                                                                       / (2. * Math.PI);
+                                                       if (dir>0)
+                                                       {
+                                                               double tmp = a1;
+                                                               a1 = a2;
+                                                               a2 = tmp;
+                                                       }
+                                                       if (a1 < 0) {
+                                                               a1 += 360.;
+                                                       }
+                                                       if (a2 < 0) {
+                                                               a2 += 360.;
+                                                       }
+                                                       out.drawArc(centerLoop, 2. * radius, 2. * radius, a1,
+                                                                       a2);
+                                               } else {
+                                                       out.drawLine((x0 + BASE_RADIUS * vn.x),
+                                                                       (y0 + BASE_RADIUS * vn.y), (x1 - BASE_RADIUS
+                                                                                       * vn.x), (y1 - BASE_RADIUS * vn.y), 1.0);
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               // Drawing bonds
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       if (_listeBases.get(i).getElementStructure() > i) {
+                               ModeleBP style = _listeBases.get(i).getStyleBP();
+                               if (style.isCanonical() || conf._drawnNonCanonicalBP) {
+                                       Color bpcol = getBasePairColor(style, conf);
+                                       out.setColor(bpcol);
+
+                                       int j = _listeBases.get(i).getElementStructure();
+                                       x0 = coords[i].x;
+                                       y0 = coords[i].y;
+                                       x1 = coords[j].x;
+                                       y1 = coords[j].y;
+                                       dx = x1 - x0;
+                                       dy = y1 - y0;
+                                       norm = Math.sqrt(dx * dx + dy * dy);
+                                       dx /= norm;
+                                       dy /= norm;
+
+                                       if (_drawMode == DRAW_MODE_CIRCULAR
+                                                       || _drawMode == DRAW_MODE_RADIATE
+                                                       || _drawMode == DRAW_MODE_NAVIEW) {
+                                               drawBasePair(out, new Point2D.Double(x0, y0),
+                                                               new Point2D.Double(x1, y1), style, conf);
+                                       } else if (_drawMode == DRAW_MODE_LINEAR) {
+                                               drawBasePairArc(out, i, j, new Point2D.Double(x0, y0),
+                                                               new Point2D.Double(x1, y1), style, conf);
+                                       }
+                               }
+                       }
+               }
+
+               // Drawing additional bonds
+               if (conf._drawnNonPlanarBP) {
+                       for (int i = 0; i < _structureAux.size(); i++) {
+                               ModeleBP bp = _structureAux.get(i);
+                               out.setColor(getBasePairColor(bp, conf));
+
+                               int a = bp.getPartner5().getIndex();
+                               int b = bp.getPartner3().getIndex();
+
+                               if (bp.isCanonical() || conf._drawnNonCanonicalBP) {
+                                       x0 = coords[a].x;
+                                       y0 = coords[a].y;
+                                       x1 = coords[b].x;
+                                       y1 = coords[b].y;
+                                       dx = x1 - x0;
+                                       dy = y1 - y0;
+                                       norm = Math.sqrt(dx * dx + dy * dy);
+                                       dx /= norm;
+                                       dy /= norm;
+                                       if ((_drawMode == DRAW_MODE_CIRCULAR)
+                                                       || (_drawMode == DRAW_MODE_RADIATE)
+                                                       || _drawMode == DRAW_MODE_NAVIEW) {
+                                               drawBasePair(out, new Point2D.Double(x0, y0),
+                                                               new Point2D.Double(x1, y1), bp, conf);
+                                       } else if (_drawMode == DRAW_MODE_LINEAR) {
+                                               drawBasePairArc(out, a, b, new Point2D.Double(x0, y0),
+                                                               new Point2D.Double(x1, y1), bp, conf);
+                                       }
+                               }
+                       }
+               }
+
+               // Drawing Bases
+               double baseFontSize = (1.5 * BASE_RADIUS);
+               out.setFont(PSExport.FONT_HELVETICA_BOLD, baseFontSize);
+               
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       x0 = coords[i].x;
+                       y0 = coords[i].y;
+                       
+                       Color baseInnerColor = getBaseInnerColor(i, conf);
+                       Color baseOuterColor = getBaseOuterColor(i, conf);
+                       Color baseNameColor = getBaseNameColor(i, conf);
+                       if ( RNA.whiteLabelPreferrable(baseInnerColor))
+                       {
+                               baseNameColor=Color.white;
+                       }
+
+
+                       if (_listeBases.get(i) instanceof ModeleBasesComparison) {
+                               ModeleBasesComparison mb = (ModeleBasesComparison) _listeBases
+                                               .get(i);
+                               if (conf._fillBases) {
+                                       out.fillRectangle(x0 - 1.5 * BASE_RADIUS, y0 - BASE_RADIUS,
+                                                       3 * BASE_RADIUS, 2 * BASE_RADIUS,
+                                                       baseInnerColor);
+                               }
+                               if (conf._drawOutlineBases) {
+                                       out.setColor(baseOuterColor);
+                                       out.drawRectangle(x0 - 1.5 * BASE_RADIUS, y0 - BASE_RADIUS,
+                                                       3 * BASE_RADIUS, 2 * BASE_RADIUS, 1l);
+                                       out.drawLine(x0, y0 - BASE_RADIUS, x0, y0 + BASE_RADIUS, 1l);
+                               }
+                               
+                               out.setColor(baseNameColor);
+                               out.drawText(x0 - .75 * BASE_RADIUS, y0, "" + mb.getBase1());
+                               out.drawText(x0 + .75 * BASE_RADIUS, y0, "" + mb.getBase2());
+                       } else if (_listeBases.get(i) instanceof ModeleBaseNucleotide) {
+                               if (conf._fillBases) {
+                                       out.fillCircle(x0, y0, BASE_RADIUS, 1l,
+                                                       baseInnerColor);
+                               }
+                               if (conf._drawOutlineBases) {
+                                       out.setColor(baseOuterColor);
+                                       out.drawCircle(x0, y0, BASE_RADIUS, 1l);
+                               }
+                               out.setColor(baseNameColor);
+                               out.drawText(x0, y0, _listeBases.get(i).getContent());
+
+                       }
+               }
+
+               // Drawing base numbers
+               double numFontSize = (double) (1.5 * BASE_RADIUS);
+               out.setFont(PSExport.FONT_HELVETICA_BOLD, numFontSize);
+
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       int basenum = _listeBases.get(i).getBaseNumber();
+                       if (basenum == -1) {
+                               basenum = i + 1;
+                       }
+                       ModeleBase mb = _listeBases.get(i);
+                       if (this.isNumberDrawn(mb, conf._numPeriod)) {
+                               out.setColor(mb.getStyleBase()
+                                               .getBaseNumberColor());
+                               x0 = coords[i].x;
+                               y0 = coords[i].y;
+                               x1 = centers[i].x;
+                               y1 = centers[i].y;
+                               dx = x1 - x0;
+                               dy = y1 - y0;
+                               norm = Math.sqrt(dx * dx + dy * dy);
+                               dx /= norm;
+                               dy /= norm;
+                               Point2D.Double vn = VARNAPanel.computeExcentricUnitVector(i,coords,centers);
+                               
+                               out.drawLine((x0 + 1.5 * BASE_RADIUS * vn.x), (y0 + 1.5
+                                               * BASE_RADIUS * vn.y), (x0 + 2.5 * BASE_RADIUS * vn.x),
+                                               (y0 + 2.5 * BASE_RADIUS * vn.y), 1);
+                               out.drawText(
+                                               (x0 + (conf._distNumbers + 1.0) * BASE_RADIUS * vn.x),
+                                               (y0 + (conf._distNumbers + 1.0) * BASE_RADIUS * vn.y), mb.getLabel());
+                       }
+               }
+               renderAnnotations(out, minX, minY, conf);
+
+               // Draw color map
+               if (conf._drawColorMap) {
+                       drawColorMap(conf, out);
+               }
+
+               // Drawing Title
+               Rectangle2D.Double currentBBox = out.getBoundingBox();
+               double titleFontSize = (2.0 * conf._titleFont.getSize());
+               out.setColor(conf._titleColor);
+               out.setFont(PSExport.FONT_HELVETICA, titleFontSize);
+               double yTitle = currentBBox.y - titleFontSize / 2.0;
+               if (!getName().equals("")) {
+                       out.drawText((maxX - minX) / 2.0, yTitle, getName());
+               }
+
+               OutputStreamWriter fout;
+
+               try {
+                       fout = new OutputStreamWriter(new FileOutputStream(path), "UTF-8");
+
+                       fout.write(out.export());
+                       fout.close();
+               } catch (IOException e) {
+                       throw new ExceptionWritingForbidden(e.getMessage());
+               }
+       }
+
+       Point2D.Double buildCaptionPosition(ModeleBase mb, double heightEstimate,
+                       VARNAConfig conf) {
+               double radius = 2.0;
+               if (isNumberDrawn(mb, conf._numPeriod)) {
+                       radius += (conf._distNumbers + 1.0);
+               }
+               Point2D.Double center = mb.getCenter();
+               Point2D.Double p = mb.getCoords();
+               double realDistance = BASE_RADIUS * radius + heightEstimate;
+               return new Point2D.Double(center.getX() + (p.getX() - center.getX())
+                               * ((p.distance(center) + realDistance) / p.distance(center)),
+                               center.getY()
+                                               + (p.getY() - center.getY())
+                                               * ((p.distance(center) + realDistance) / p
+                                                               .distance(center)));
+       }
+
+       public double getBPHeightIncrement() {
+               return this._bpHeightIncrement;
+       }
+
+       public void setBPHeightIncrement(double d) {
+               _bpHeightIncrement = d;
+       }
+
+       private void drawChemProbAnnotation(SecStrDrawingProducer out,
+                       ChemProbAnnotation cpa, Point2D.Double anchor, double minX,
+                       double minY) {
+               out.setColor(cpa.getColor());
+               Point2D.Double v = cpa.getDirVector();
+               Point2D.Double vn = cpa.getNormalVector();
+               Point2D.Double base = new Point2D.Double((anchor.x + CHEM_PROB_DIST
+                               * v.x), (anchor.y + CHEM_PROB_DIST * v.y));
+               Point2D.Double edge = new Point2D.Double(
+                               (base.x + CHEM_PROB_BASE_LENGTH * cpa.getIntensity() * v.x),
+                               (base.y + CHEM_PROB_BASE_LENGTH * cpa.getIntensity() * v.y));
+               double thickness = CHEM_PROB_ARROW_THICKNESS * cpa.getIntensity();
+               switch (cpa.getType()) {
+               case ARROW: {
+                       Point2D.Double arrowTip1 = new Point2D.Double(
+                                       (base.x + cpa.getIntensity()
+                                                       * (CHEM_PROB_ARROW_WIDTH * vn.x + CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.x)),
+                                       (base.y + cpa.getIntensity()
+                                                       * (CHEM_PROB_ARROW_WIDTH * vn.y + CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.y)));
+                       Point2D.Double arrowTip2 = new Point2D.Double(
+                                       (base.x + cpa.getIntensity()
+                                                       * (-CHEM_PROB_ARROW_WIDTH * vn.x + CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.x)),
+                                       (base.y + cpa.getIntensity()
+                                                       * (-CHEM_PROB_ARROW_WIDTH * vn.y + CHEM_PROB_ARROW_HEIGHT
+                                                                       * v.y)));
+                       out.drawLine(base.x - minX, minY - base.y, edge.x - minX, minY
+                                       - edge.y, thickness);
+                       out.drawLine(base.x - minX, minY - base.y, arrowTip1.x - minX, minY
+                                       - arrowTip1.y, thickness);
+                       out.drawLine(base.x - minX, minY - base.y, arrowTip2.x - minX, minY
+                                       - arrowTip2.y, thickness);
+               }
+                       break;
+               case PIN: {
+                       Point2D.Double side1 = new Point2D.Double(
+                                       (edge.x - cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * v.x)),
+                                       (edge.y - cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * v.y)));
+                       Point2D.Double side2 = new Point2D.Double(
+                                       (edge.x - cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * vn.x)),
+                                       (edge.y - cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * vn.y)));
+                       Point2D.Double side3 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * v.x)),
+                                       (edge.y + cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * v.y)));
+                       Point2D.Double side4 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * vn.x)),
+                                       (edge.y + cpa.getIntensity()
+                                                       * (CHEM_PROB_PIN_SEMIDIAG * vn.y)));
+                       GeneralPath p2 = new GeneralPath();
+                       p2.moveTo((float) (side1.x - minX), (float) (minY - side1.y));
+                       p2.lineTo((float) (side2.x - minX), (float) (minY - side2.y));
+                       p2.lineTo((float) (side3.x - minX), (float) (minY - side3.y));
+                       p2.lineTo((float) (side4.x - minX), (float) (minY - side4.y));
+                       p2.closePath();
+                       out.fillPolygon(p2, cpa.getColor());
+                       out.drawLine(base.x - minX, minY - base.y, edge.x - minX, minY
+                                       - edge.y, thickness);
+               }
+                       break;
+               case TRIANGLE: {
+                       Point2D.Double arrowTip1 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity()
+                                                       * (CHEM_PROB_TRIANGLE_WIDTH * vn.x)),
+                                       (edge.y + cpa.getIntensity()
+                                                       * (CHEM_PROB_TRIANGLE_WIDTH * vn.y)));
+                       Point2D.Double arrowTip2 = new Point2D.Double(
+                                       (edge.x + cpa.getIntensity()
+                                                       * (-CHEM_PROB_TRIANGLE_WIDTH * vn.x)),
+                                       (edge.y + cpa.getIntensity()
+                                                       * (-CHEM_PROB_TRIANGLE_WIDTH * vn.y)));
+                       GeneralPath p2 = new GeneralPath();
+                       p2.moveTo((float) (base.x - minX), (float) (minY - base.y));
+                       p2.lineTo((float) (arrowTip1.x - minX),
+                                       (float) (minY - arrowTip1.y));
+                       p2.lineTo((float) (arrowTip2.x - minX),
+                                       (float) (minY - arrowTip2.y));
+                       p2.closePath();
+                       out.fillPolygon(p2, cpa.getColor());
+               }
+                       break;
+               case DOT: {
+                       Double radius = CHEM_PROB_DOT_RADIUS * cpa.getIntensity();
+                       Point2D.Double center = new Point2D.Double((base.x + radius * v.x)
+                                       - minX, minY - (base.y + radius * v.y));
+                       out.fillCircle(center.x, center.y, radius, thickness,
+                                       cpa.getColor());
+               }
+                       break;
+               }
+       }
+
+       private void renderAnnotations(SecStrDrawingProducer out, double minX,
+                       double minY, VARNAConfig conf) {
+               for (TextAnnotation textAnnotation : getAnnotations()) {
+                       out.setColor(textAnnotation.getColor());
+                       out.setFont(PSExport.FONT_HELVETICA_BOLD, 2.0 * textAnnotation
+                                       .getFont().getSize());
+                       Point2D.Double position = textAnnotation.getCenterPosition();
+                       if (textAnnotation.getType() == TextAnnotation.AnchorType.BASE) {
+                               ModeleBase mb = (ModeleBase) textAnnotation.getAncrage();
+                               double fontHeight = Math.ceil(textAnnotation.getFont()
+                                               .getSize());
+                               position = buildCaptionPosition(mb, fontHeight, conf);
+
+                       }
+                       out.drawText(position.x - minX, -(position.y - minY),
+                                       textAnnotation.getTexte());
+               }
+               for (ChemProbAnnotation cpa : getChemProbAnnotations()) {
+                       Point2D.Double anchor = cpa.getAnchorPosition();
+                       drawChemProbAnnotation(out, cpa, anchor, minX, minY);
+               }
+       }
+
+       public boolean isNumberDrawn(ModeleBase mb, int numPeriod) {
+               if (numPeriod <= 0)
+                       return false;
+               return ((mb.getIndex() == 0) || ((mb.getBaseNumber()) % numPeriod == 0) || (mb
+                               .getIndex() == get_listeBases().size() - 1));
+       }
+
+       public void saveRNAEPS(String path, VARNAConfig conf)
+                       throws ExceptionWritingForbidden {
+               PSExport out = new PSExport();
+               saveRNA(path, conf, 0.4, out);
+       }
+
+       public void saveRNAXFIG(String path, VARNAConfig conf)
+                       throws ExceptionWritingForbidden {
+               XFIGExport out = new XFIGExport();
+               saveRNA(path, conf, 20, out);
+       }
+
+       public void saveRNATIKZ(String path, VARNAConfig conf)
+                       throws ExceptionWritingForbidden {
+               TikzExport out = new TikzExport();
+               saveRNA(path, conf, 0.15, out);
+       }
+
+       public void saveRNASVG(String path, VARNAConfig conf)
+                       throws ExceptionWritingForbidden {
+               SVGExport out = new SVGExport();
+               saveRNA(path, conf, 0.5, out);
+       }
+
+       public Rectangle2D.Double getBBox() {
+               Rectangle2D.Double result = new Rectangle2D.Double(10, 10, 10, 10);
+               double minx, maxx, miny, maxy;
+               minx = Double.MAX_VALUE;
+               miny = Double.MAX_VALUE;
+               maxx = -Double.MAX_VALUE;
+               maxy = -Double.MAX_VALUE;
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       minx = Math.min(
+                                       _listeBases.get(i).getCoords().getX() - BASE_RADIUS, minx);
+                       miny = Math.min(
+                                       _listeBases.get(i).getCoords().getY() - BASE_RADIUS, miny);
+                       maxx = Math.max(
+                                       _listeBases.get(i).getCoords().getX() + BASE_RADIUS, maxx);
+                       maxy = Math.max(
+                                       _listeBases.get(i).getCoords().getY() + BASE_RADIUS, maxy);
+               }
+               result.x = minx;
+               result.y = miny;
+               result.width = Math.max(maxx - minx, 1);
+               result.height = Math.max(maxy - miny, 1);
+               if (_drawMode == RNA.DRAW_MODE_LINEAR) {
+                       double realHeight = _bpHeightIncrement * result.width / 2.0;
+                       result.height += realHeight;
+                       result.y -= realHeight;
+               }
+               return result;
+       }
+
+       public void setCoord(int index, Point2D.Double p) {
+               setCoord(index, p.x, p.y);
+       }
+
+       public void setCoord(int index, double x, double y) {
+               if (index < _listeBases.size()) {
+                       _listeBases.get(index).setCoords(new Point2D.Double(x, y));
+               }
+       }
+
+       public Point2D.Double getCoords(int i) {
+               if (i < _listeBases.size() && i >= 0) {
+                       return _listeBases.get(i).getCoords();
+               }
+               return new Point2D.Double();
+       }
+
+       public String getBaseContent(int i) {
+               if ((i >= 0) && (i < _listeBases.size())) {
+                       return _listeBases.get(i).getContent();
+               }
+               return "";
+       }
+
+       public int getBaseNumber(int i) {
+               if ((i >= 0) && (i < _listeBases.size())) {
+                       return _listeBases.get(i).getBaseNumber();
+               }
+               return -1;
+       }
+
+       public Point2D.Double getCenter(int i) {
+               if (i < _listeBases.size()) {
+                       return _listeBases.get(i).getCenter();
+               }
+
+               return new Point2D.Double();
+       }
+
+       public void setCenter(int i, double x, double y) {
+               setCenter(i, new Point2D.Double(x, y));
+       }
+
+       public void setCenter(int i, Point2D.Double p) {
+               if (i < _listeBases.size()) {
+                       _listeBases.get(i).setCenter(p);
+               }
+       }
+
+       public void drawRNACircle(VARNAConfig conf) {
+               _drawn = true;
+               _drawMode = DRAW_MODE_CIRCULAR;
+               int radius = (int) ((3 * (_listeBases.size() + 1) * BASE_RADIUS) / (2 * Math.PI));
+               double angle;
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       angle = -((((double) -(i + 1)) * 2.0 * Math.PI)
+                                       / ((double) (_listeBases.size() + 1)) - Math.PI / 2.0);
+                       _listeBases
+                                       .get(i)
+                                       .setCoords(
+                                                       new Point2D.Double(
+                                                                       (radius * Math.cos(angle) * conf._spaceBetweenBases),
+                                                                       (radius * Math.sin(angle) * conf._spaceBetweenBases)));
+                       _listeBases.get(i).setCenter(new Point2D.Double(0, 0));
+               }
+       }
+
+       public void drawRNAVARNAView(VARNAConfig conf) {
+               _drawn = true;
+               _drawMode = DRAW_MODE_VARNA_VIEW;
+               VARNASecDraw vs = new VARNASecDraw();
+               vs.drawRNA(1, this);
+       }
+
+       public void drawRNALine(VARNAConfig conf) {
+               _drawn = true;
+               _drawMode = DRAW_MODE_LINEAR;
+               for (int i = 0; i < get_listeBases().size(); i++) {
+                       get_listeBases().get(i).setCoords(
+                                       new Point2D.Double(i * conf._spaceBetweenBases * 20, 0));
+                       get_listeBases().get(i).setCenter(
+                                       new Point2D.Double(i * conf._spaceBetweenBases * 20, -10));
+               }
+       }
+
+       public RNATemplateMapping drawRNATemplate(RNATemplate template, boolean straightBulges,
+                       VARNAConfig conf) throws RNATemplateDrawingAlgorithmException {
+               return drawRNATemplate(template, conf,
+                               DrawRNATemplateMethod.getDefault(),
+                               DrawRNATemplateCurveMethod.getDefault(), straightBulges);
+       }
+
+       public RNATemplateMapping drawRNATemplate(RNATemplate template,
+                       VARNAConfig conf, DrawRNATemplateMethod helixLengthAdjustmentMethod, 
+                       boolean straightBulges)
+                       throws RNATemplateDrawingAlgorithmException {
+               return drawRNATemplate(template, conf, helixLengthAdjustmentMethod,
+                               DrawRNATemplateCurveMethod.getDefault(),straightBulges);
+       }
+
+       public RNATemplateMapping drawRNATemplate(RNATemplate template,
+                       VARNAConfig conf,
+                       DrawRNATemplateMethod helixLengthAdjustmentMethod,
+                       DrawRNATemplateCurveMethod curveMethod,
+                       boolean straightBulges)
+                       throws RNATemplateDrawingAlgorithmException {
+               _drawn = true;
+               _drawMode = DRAW_MODE_TEMPLATE;
+
+               DrawRNATemplate drawRNATemplate = new DrawRNATemplate(this);
+               drawRNATemplate.drawRNATemplate(template, conf,
+                               helixLengthAdjustmentMethod, curveMethod, 
+                               straightBulges);
+               return drawRNATemplate.getMapping();
+       }
+
+       private static double objFun(int n1, int n2, double r, double bpdist,
+                       double multidist) {
+               return (((double) n1) * 2.0 * Math.asin(((double) bpdist) / (2.0 * r))
+                               + ((double) n2) * 2.0
+                               * Math.asin(((double) multidist) / (2.0 * r)) - (2.0 * Math.PI));
+       }
+
+       public double determineRadius(int nbHel, int nbUnpaired, double startRadius) {
+               return determineRadius(nbHel, nbUnpaired, startRadius,
+                               BASE_PAIR_DISTANCE, MULTILOOP_DISTANCE);
+       }
+
+       public static double determineRadius(int nbHel, int nbUnpaired,
+                       double startRadius, double bpdist, double multidist) {
+               double xmin = bpdist / 2.0;
+               double xmax = 3.0 * multidist + 1;
+               double x = (xmin + xmax) / 2.0;
+               double y = 10000.0;
+               double ymin = -1000.0;
+               double ymax = 1000.0;
+               int numIt = 0;
+               double precision = 0.00001;
+               while ((Math.abs(y) > precision) && (numIt < 10000)) {
+                       x = (xmin + xmax) / 2.0;
+                       y = objFun(nbHel, nbUnpaired, x, bpdist, multidist);
+                       ymin = objFun(nbHel, nbUnpaired, xmax, bpdist, multidist);
+                       ymax = objFun(nbHel, nbUnpaired, xmin, bpdist, multidist);
+                       if (ymin > 0.0) {
+                               xmax = xmax + (xmax - xmin);
+                       } else if ((y <= 0.0) && (ymax > 0.0)) {
+                               xmax = x;
+                       } else if ((y >= 0.0) && (ymin < 0.0)) {
+                               xmin = x;
+                       } else if (ymax < 0.0) {
+                               xmin = Math.max(xmin - (x - xmin),
+                                               Math.max(bpdist / 2.0, multidist / 2.0));
+                               xmax = x;
+                       }
+                       numIt++;
+               }
+               return x;
+       }
+
+       public void drawRNA(VARNAConfig conf) throws ExceptionNAViewAlgorithm {
+               drawRNA(RNA.DEFAULT_DRAW_MODE, conf);
+       }
+
+       public void drawRNA(int mode, VARNAConfig conf)
+                       throws ExceptionNAViewAlgorithm {
+               _drawMode = mode;
+               switch (get_drawMode()) {
+               case RNA.DRAW_MODE_RADIATE:
+                       drawRNARadiate(conf);
+                       break;
+               case RNA.DRAW_MODE_LINEAR:
+                       drawRNALine(conf);
+                       break;
+               case RNA.DRAW_MODE_CIRCULAR:
+                       drawRNACircle(conf);
+                       break;
+               case RNA.DRAW_MODE_NAVIEW:
+                       drawRNANAView(conf);
+                       break;
+               case RNA.DRAW_MODE_VARNA_VIEW:
+                       drawRNAVARNAView(conf);
+                       break;
+               default:
+                       break;
+               }
+
+       }
+
+       public int getDrawMode() {
+               return _drawMode;
+       }
+       
+       public static double HYSTERESIS_EPSILON = .15;
+       public static final double[] HYSTERESIS_ATTRACTORS = {0.,Math.PI/4.,Math.PI/2.,3.*Math.PI/4.,Math.PI,5.*(Math.PI)/4.,3.*(Math.PI)/2,7.*(Math.PI)/4.};
+       
+       public static double normalizeAngle(double angle)
+       {
+               return normalizeAngle(angle,0.);
+       }
+       
+       public static double normalizeAngle(double angle, double fromVal)
+       {
+               double toVal = fromVal +2.*Math.PI;
+               double result = angle;
+               while(result<fromVal)
+               {
+                       result += 2.*Math.PI;
+               }
+               while(result >= toVal)
+               {
+                       result -= 2.*Math.PI;
+               }
+               return result;          
+       }
+       
+       public static double correctHysteresis(double angle)
+       {
+               double result = normalizeAngle(angle);
+               
+               for (int i=0;i<HYSTERESIS_ATTRACTORS.length;i++)
+               {
+                       double att = HYSTERESIS_ATTRACTORS[i];
+                       if (Math.abs(normalizeAngle(att-result,-Math.PI))<HYSTERESIS_EPSILON)
+                       {
+                               result = att;
+                       }
+               }
+               return result;
+       }
+       
+
+       
+       private void distributeUnpaired(
+                       double radius,
+                       double angle, 
+                       double pHel, 
+                       double base,
+                       Point2D.Double center,
+                       Vector<Integer> bases)
+       {
+                       double mydist = Math.abs(radius*(angle / (bases.size() + 1)));
+                       double addedRadius= 0.;
+                       Point2D.Double PA = new Point2D.Double(center.x + radius * Math.cos(base + pHel),
+                                       center.y + radius * Math.sin(base + pHel));
+                       Point2D.Double PB = new Point2D.Double(center.x + radius * Math.cos(base + pHel+angle),
+                                               center.y + radius * Math.sin(base + pHel+angle));
+                       double dist = PA.distance(PB);
+                       Point2D.Double VN = new Point2D.Double((PB.y-PA.y)/dist,(-PB.x+PA.x)/dist);
+                       if (mydist<2*BASE_RADIUS)
+                       {
+                               addedRadius=Math.min(1.0,(2*BASE_RADIUS-mydist)/4)*computeRadius(mydist, 2.29*(bases.size() + 1)*BASE_RADIUS-mydist);
+                       }
+                       
+                       
+                       ArrayList<Point2D.Double> pos = computeNewAngles(bases.size(),center,VN, angle,base + pHel,radius,addedRadius);
+                       for (int i = 0; i < bases.size(); i++) 
+                       {
+                               int k = bases.get(i);
+                               setCoord(k, pos.get(i));
+                       }                               
+               
+       }
+
+       private double computeRadius(double b, double pobj)
+       {
+               double a=b, aL=a, aU=Double.POSITIVE_INFINITY;
+               double h = (a-b)*(a-b)/((a+b)*(a+b));
+               double p = Math.PI*(a+b)*(1+h/4.+h*h/64.+h*h*h/256.+25.*h*h*h*h/16384.)/2.0;
+               double aold = a+1.;
+               while ((Math.abs(p-pobj)>10e-4)&&(aold!=a)){
+                       aold = a;
+                       if (p<pobj)
+                       {
+                               aL = a;
+                               if (aU==Double.POSITIVE_INFINITY)
+                               {a *= 2.;}
+                               else
+                               { a = (a+aU)/2.; }
+                       }
+                       else
+                       {
+                               aU = a;
+                               a = (a+aL)/2.0;
+                       }
+                       h = (a-b)*(a-b)/((a+b)*(a+b));
+                       p = (Math.PI*(a+b)*(1+h/4.+h*h/64.+h*h*h/256.+25.*h*h*h*h/16384.))/2.0;
+               }
+               return a;
+       }
+       
+       public static double computeAngle(Point2D.Double center, Point2D.Double p) {
+               double dist = center.distance(p);
+               double angle = Math.asin((p.y - center.y) / dist);
+               if (p.x - center.x < 0) {
+                       angle = Math.PI - angle;
+               }
+               return angle;
+       }
+       
+       private Point2D.Double rotatePoint(Point2D.Double center, Point2D.Double p,
+                       double angle) {
+               double dist = p.distance(center);
+               double oldAngle = Math.asin((p.y - center.y) / dist);
+
+               if (p.x - center.x < 0) {
+                       oldAngle = Math.PI - oldAngle;
+               }
+
+               double newX = (center.x + dist * Math.cos(oldAngle + angle));
+               double newY = (center.y + dist * Math.sin(oldAngle + angle));
+
+               return new Point2D.Double(newX, newY);
+       }
+
+
+       
+       private void rotateHelix(Point2D.Double center, int i, int j, double angle) {
+               for (int k = i; k <= j; k++) {
+                       Point2D.Double oldp = getCoords(k);
+                       Point2D.Double newp = rotatePoint(center, oldp, angle);
+                       setCoord(k, newp);
+                       if ((k != i) && (k != j)) {
+
+                               Point2D.Double oldc = get_listeBases().get(k)
+                                               .getCenter();
+                               Point2D.Double newc = rotatePoint(center, oldc, angle);
+                               setCenter(k,newc);
+                       }
+               }
+       }
+       
+
+
+       private void fixUnpairedPositions(boolean isDirect, 
+                       double angleRightPartner, 
+                       double angleLimitLeft, double angleLimitRight, double angleLeftPartner, 
+                       double radius, double base, Point2D.Double center,Vector<Integer> prevBases,Vector<Integer> nextBases)
+       {
+               if (isDirect) {
+                       double anglePrev = normalizeAngle(angleLimitLeft - angleRightPartner);
+                       double angleNext = normalizeAngle(angleLeftPartner - angleLimitRight);
+                       distributeUnpaired(radius,anglePrev, angleRightPartner, base,
+                                       center,prevBases);
+                       distributeUnpaired(radius,-angleNext, angleLeftPartner, base,
+                                       center,nextBases);
+               } else {
+                       double anglePrev = normalizeAngle(angleLeftPartner - angleLimitRight);
+                       double angleNext = normalizeAngle(angleLimitLeft - angleRightPartner);
+                       distributeUnpaired(radius,-anglePrev, angleLeftPartner, base,
+                                       center,prevBases);                      
+                       distributeUnpaired(radius,angleNext, angleRightPartner, base,
+                                       center,nextBases);
+               }
+       
+       }
+       
+       private static Point2D.Double getPoint(double angleLine, double angleBulge, Point2D.Double center,  
+                       Point2D.Double VN,double radius, double addedRadius, double dirBulge)
+       {
+               return new Point2D.Double(
+                               center.x + radius * Math.cos(angleLine)+
+                               dirBulge*addedRadius*Math.sin(angleBulge)*VN.x,
+                               center.y + radius * Math.sin(angleLine)+
+                               dirBulge*addedRadius*Math.sin(angleBulge)*VN.y);
+       }
+       
+
+       private ArrayList<Point2D.Double> computeNewAngles(int numPoints, Point2D.Double center,  
+                       Point2D.Double VN, double angle, double angleBase, double radius, double addedRadius)
+       {
+               ArrayList<Point2D.Double> result = new ArrayList<Point2D.Double>();
+               if (numPoints>0)
+               {
+               ArrayList<Double> factors = new ArrayList<Double>();
+               
+
+               Point2D.Double prevP = new Point2D.Double(
+                               center.x + radius * Math.cos(angleBase),
+                               center.y + radius * Math.sin(angleBase));
+               
+               
+               double fact = 0.;
+               
+               double angleBulge = 0.;
+               double dirBulge = (angle<0)?-1.:1.;
+               double dtarget =2.*BASE_RADIUS; 
+               
+               for (int i = 0; i < numPoints; i++) 
+               {
+                               double lbound = fact;
+                               double ubound = 1.0;
+                               double angleLine = angleBase + angle*fact;
+                               angleBulge = Math.PI*fact;
+                               Point2D.Double currP = getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge);
+
+                               int numIter = 0;
+                               while ((Math.abs(currP.distance(prevP)-dtarget)>0.01)&& (numIter<100))
+                               {
+                                       if (currP.distance(prevP)> dtarget)
+                                       {
+                                               ubound = fact;
+                                               fact = (fact+lbound)/2.0;
+                                       }
+                                       else
+                                       {
+                                               lbound = fact;
+                                               fact = (fact+ubound)/2.0;                                       
+                                       }                               
+                                       angleLine = angleBase + angle*fact;
+                                       angleBulge = Math.PI*fact;
+                                       currP = getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge);
+                                       numIter++;
+                               }
+                               factors.add(fact);
+                               prevP = currP;
+               }
+               
+               
+               double rescale = 1.0/(factors.get(factors.size()-1)+factors.get(0));
+
+               for(int j=0;j<factors.size();j++)
+               {
+                       factors.set(j,factors.get(j)*rescale);
+               }
+               
+                
+               if (addedRadius>0)
+               {
+                       prevP =  getPoint(angleBase, 0, center,VN,radius, addedRadius, dirBulge);
+                       double totDist = 0.0;
+                       for(int j=0;j<factors.size();j++)
+                       {
+                               double newfact = factors.get(j);
+                               double angleLine = angleBase + angle*newfact;
+                               angleBulge = Math.PI*newfact;
+                               Point2D.Double currP = getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge); 
+                               totDist += currP.distance(prevP);
+                               prevP = currP;
+                       }
+                       totDist += getPoint(angleBase+angle, Math.PI, center,VN,radius, addedRadius, dirBulge).distance(prevP);
+                       dtarget = totDist/(numPoints+1);
+                       fact = 0.0;
+                       factors=new ArrayList<Double>();
+                       prevP = new Point2D.Double(
+                                       center.x + radius * Math.cos(angleBase),
+                                       center.y + radius * Math.sin(angleBase));
+                       for (int i = 0; i < numPoints; i++) 
+                       {
+                                       double lbound = fact;
+                                       double ubound = 1.5;
+                                       double angleLine = angleBase + angle*fact;
+                                       angleBulge = Math.PI*fact;
+                                       Point2D.Double currP = getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge);
+
+                                       int numIter = 0;
+                                       while ((Math.abs(currP.distance(prevP)-dtarget)>0.01)&& (numIter<100))
+                                       {
+                                               if (currP.distance(prevP)> dtarget)
+                                               {
+                                                       ubound = fact;
+                                                       fact = (fact+lbound)/2.0;
+                                               }
+                                               else
+                                               {
+                                                       lbound = fact;
+                                                       fact = (fact+ubound)/2.0;                                       
+                                               }                               
+                                               angleLine = angleBase + angle*fact;
+                                               angleBulge = Math.PI*fact;
+                                               currP = getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge);
+                                               numIter++;
+                                       }
+                                       factors.add(fact);
+                                       prevP = currP;
+                       }
+                       rescale = 1.0/(factors.get(factors.size()-1)+factors.get(0));
+                               for(int j=0;j<factors.size();j++)
+                       {
+                               factors.set(j,factors.get(j)*rescale);
+                       }
+               }       
+
+               for(int j=0;j<factors.size();j++)
+               {
+                       double newfact = factors.get(j);
+                       double angleLine = angleBase + angle*newfact;
+                       angleBulge = Math.PI*newfact;
+                       result.add(getPoint(angleLine, angleBulge, center,VN,radius, addedRadius, dirBulge));                   
+               }
+               }       
+               return result;
+       }
+       
+       
+       
+       void drawLoop(int i, int j, double x, double y, double dirAngle,
+                       Point2D.Double[] coords, Point2D.Double[] centers, double[] angles, 
+                       boolean straightBulges) {
+               if (i > j) {
+                       return;
+               }
+
+               // BasePaired
+               if (_listeBases.get(i).getElementStructure() == j) {
+                       double normalAngle = Math.PI / 2.0;
+                       centers[i] = new Point2D.Double(x, y);
+                       centers[j] = new Point2D.Double(x, y);
+                       coords[i].x = (x + BASE_PAIR_DISTANCE
+                                       * Math.cos(dirAngle - normalAngle) / 2.0);
+                       coords[i].y = (y + BASE_PAIR_DISTANCE
+                                       * Math.sin(dirAngle - normalAngle) / 2.0);
+                       coords[j].x = (x + BASE_PAIR_DISTANCE
+                                       * Math.cos(dirAngle + normalAngle) / 2.0);
+                       coords[j].y = (y + BASE_PAIR_DISTANCE
+                                       * Math.sin(dirAngle + normalAngle) / 2.0);
+                       drawLoop(i + 1, j - 1, x + LOOP_DISTANCE * Math.cos(dirAngle), y
+                                       + LOOP_DISTANCE * Math.sin(dirAngle), dirAngle, coords,
+                                       centers, angles, straightBulges);
+               } else {
+                       int k = i;
+                       Vector<Integer> basesMultiLoop = new Vector<Integer>();
+                       Vector<Integer> helices = new Vector<Integer>();
+                       int l;
+                       while (k <= j) {
+                               l = _listeBases.get(k).getElementStructure();
+                               if (l > k) {
+                                       basesMultiLoop.add(new Integer(k));
+                                       basesMultiLoop.add(new Integer(l));
+                                       helices.add(new Integer(k));
+                                       k = l + 1;
+                               } else {
+                                       basesMultiLoop.add(new Integer(k));
+                                       k++;
+                               }
+                       }
+                       int mlSize = basesMultiLoop.size() + 2;
+                       int numHelices = helices.size() + 1;
+                       double totalLength = MULTILOOP_DISTANCE * (mlSize - numHelices)
+                                       + BASE_PAIR_DISTANCE * numHelices;
+                       double multiLoopRadius;
+                       double angleIncrementML;
+                       double angleIncrementBP;
+                       if (mlSize > 3) {
+                               multiLoopRadius = determineRadius(numHelices, mlSize
+                                               - numHelices, (totalLength) / (2.0 * Math.PI),
+                                               BASE_PAIR_DISTANCE, MULTILOOP_DISTANCE);
+                               angleIncrementML = -2.0
+                                               * Math.asin(((float) MULTILOOP_DISTANCE)
+                                                               / (2.0 * multiLoopRadius));
+                               angleIncrementBP = -2.0
+                                               * Math.asin(((float) BASE_PAIR_DISTANCE)
+                                                               / (2.0 * multiLoopRadius));
+                       } 
+                       else {
+                               multiLoopRadius = 35.0;
+                               angleIncrementBP = -2.0
+                                               * Math.asin(((float) BASE_PAIR_DISTANCE)
+                                                               / (2.0 * multiLoopRadius));
+                               angleIncrementML = (-2.0 * Math.PI - angleIncrementBP) / 2.0;
+                       }
+                       // System.out.println("MLr:"+multiLoopRadius+" iBP:"+angleIncrementBP+" iML:"+angleIncrementML);
+
+                       double centerDist = Math.sqrt(Math.max(Math.pow(multiLoopRadius, 2)
+                                       - Math.pow(BASE_PAIR_DISTANCE / 2.0, 2), 0.0))
+                                       - LOOP_DISTANCE;
+                       Point2D.Double mlCenter = new Point2D.Double(
+                                       (x + (centerDist * Math.cos(dirAngle))),
+                                       (y + (centerDist * Math.sin(dirAngle))));
+
+                       // Base directing angle for (multi|hairpin) loop, from the center's
+                       // perspective
+                       double baseAngle = dirAngle
+                                       // U-turn
+                                       + Math.PI
+                                       // Account for already drawn supporting base-pair
+                                       + 0.5 * angleIncrementBP
+                                       // Base cannot be paired twice, so next base is at
+                                       // "unpaired base distance"
+                                       + 1.0 * angleIncrementML;
+                       
+                       ArrayList<Integer> currUnpaired = new ArrayList<Integer>();
+                       Couple<Double,Double> currInterval = new Couple<Double,Double>(0.,baseAngle-1.0 * angleIncrementML);
+                       ArrayList<Couple<ArrayList<Integer>,Couple<Double,Double>>> intervals = new ArrayList<Couple<ArrayList<Integer>,Couple<Double,Double>>>();
+                       
+                       for (k = basesMultiLoop.size() - 1; k >= 0; k--) {
+                               l = basesMultiLoop.get(k).intValue();
+                               //System.out.println(l+" ");
+                               centers[l] = mlCenter;
+                               boolean isPaired = (_listeBases.get(l).getElementStructure() != -1);
+                               boolean isPaired3 = isPaired && (_listeBases.get(l).getElementStructure() < l);
+                               boolean isPaired5 = isPaired && !isPaired3;
+                               if (isPaired3) {
+                                       if ((numHelices == 2) && straightBulges)
+                                       {
+                                               baseAngle = dirAngle-angleIncrementBP/2.;
+                                       }
+                                       else
+                                       {
+                                               baseAngle = correctHysteresis(baseAngle+angleIncrementBP/2.)-angleIncrementBP/2.;
+                                       }
+                                       currInterval.first = baseAngle;
+                                       intervals.add(new Couple<ArrayList<Integer>,Couple<Double,Double>>(currUnpaired,currInterval));
+                                       currInterval = new Couple<Double,Double>(-1.,-1.);  
+                                       currUnpaired = new ArrayList<Integer>();
+                               }
+                               else if (isPaired5)
+                               {
+                                       currInterval.second = baseAngle;
+                               }
+                               else
+                               {
+                                       currUnpaired.add(l);
+                               }
+
+                               angles[l] = baseAngle;
+                               if (isPaired3)
+                               { 
+                                       baseAngle += angleIncrementBP;
+                               }
+                               else {
+                                       baseAngle += angleIncrementML;
+                               }
+                       }
+                       currInterval.first = dirAngle
+                                       - Math.PI
+                                       - 0.5 * angleIncrementBP;
+                       intervals.add(new Couple<ArrayList<Integer>,Couple<Double,Double>>(currUnpaired,currInterval));
+                       //System.out.println("Inc. ML:"+angleIncrementML+" BP:"+angleIncrementBP);
+                       
+                       for(Couple<ArrayList<Integer>,Couple<Double,Double>> inter: intervals)
+                       {
+                               //double mid = inter.second.second;
+                               double mina = inter.second.first;
+                               double maxa = normalizeAngle(inter.second.second,mina);
+                               //System.out.println(""+mina+" " +maxa);
+                               
+                               for (int n=0;n<inter.first.size();n++)
+                               {
+                                       double ratio = (1.+n)/(1.+inter.first.size());
+                                       int b = inter.first.get(n);
+                                       angles[b] = mina + (1.-ratio)*(maxa-mina);
+                               }
+                       }
+                       
+                       
+                       for (k = basesMultiLoop.size() - 1; k >= 0; k--) {
+                               l = basesMultiLoop.get(k).intValue();
+                               coords[l].x = mlCenter.x + multiLoopRadius
+                                               * Math.cos(angles[l]);
+                               coords[l].y = mlCenter.y + multiLoopRadius
+                                               * Math.sin(angles[l]);
+                       }       
+                       
+                       // System.out.println("n1:"+n1+" n2:"+n2);
+                       double newAngle;
+                       int m, n;
+                       for (k = 0; k < helices.size(); k++) {
+                               m = helices.get(k).intValue();
+                               n = _listeBases.get(m).getElementStructure();
+                               newAngle = (angles[m] + angles[n]) / 2.0;
+                               drawLoop(m + 1, n - 1, (LOOP_DISTANCE * Math.cos(newAngle))
+                                               + (coords[m].x + coords[n].x) / 2.0,
+                                               (LOOP_DISTANCE * Math.sin(newAngle))
+                                                               + (coords[m].y + coords[n].y) / 2.0, newAngle,
+                                               coords, centers, angles, straightBulges);
+                       }
+               }
+       }
+
+       private Vector<Integer> getPreviousUnpaired(Point h)
+       {
+               Vector<Integer> prevBases = new Vector<Integer>();
+               boolean over = false;
+               int i = h.y + 1;
+               while (!over) {
+                       if (i >=get_listeBases().size()) {
+                               over = true;
+                       } else {
+                               if (get_listeBases().get(i)
+                                               .getElementStructure() == -1) {
+                                       prevBases.add(new Integer(i));
+                               } else {
+                                       over = true;
+                               }
+                       }
+                       i++;
+               }
+               return prevBases;
+       }
+
+       private Vector<Integer> getNextUnpaired(Point h)
+       {
+               boolean over = false;
+               int i = h.x - 1;
+               Vector<Integer> nextBases = new Vector<Integer>();
+               while (!over) {
+                       if (i < 0) {
+                               over = true;
+                       } else {
+                               if (get_listeBases().get(i)
+                                               .getElementStructure() == -1) {
+                                       nextBases.add(new Integer(i));
+                               } else {
+                                       over = true;
+                               }
+                       }
+                       i--;
+               }
+               return nextBases;
+       }
+
+       
+       public void rotateEverything(double delta, double base, double pLimL, double pLimR, Point h, Point ml, Hashtable<Integer,Point2D.Double> backupPos)
+       {
+               boolean isDirect = testDirectionality(ml.x, ml.y, h.x);
+               Point2D.Double center = get_listeBases().get(h.x).getCenter();
+               for(int k=h.x;k<=h.y;k++)
+               { backupPos.put(k, getBaseAt(k).getCoords()); }
+               rotateHelix(center, h.x, h.y, delta);
+               
+               // Re-assigns unpaired atoms
+               Point2D.Double helixStart = getCoords(h.x);
+               Point2D.Double helixStop = getCoords(h.y);
+               double pHelR,pHelL;
+               if (isDirect) {
+                       pHelR = computeAngle(center, helixStop) - base;
+                       pHelL = computeAngle(center, helixStart) - base;
+               } else {
+                       pHelL = computeAngle(center, helixStop) - base;
+                       pHelR = computeAngle(center, helixStart) - base;
+               }
+
+               Vector<Integer> prevBases = getPreviousUnpaired(h);
+               Vector<Integer> nextBases = getNextUnpaired(h);
+               
+               double radius = center.distance(helixStart);
+
+               for (int j = 0; j < prevBases.size(); j++) 
+               {
+                       int k = prevBases.get(j);
+                       backupPos.put(k, getCoords(k));
+               }
+               for (int j = 0; j < nextBases.size(); j++) 
+               {
+                       int k = nextBases.get(j);
+                       backupPos.put(k, getCoords(k));
+               }
+               fixUnpairedPositions(isDirect, pHelR, pLimL, pLimR, pHelL, radius, base,center,prevBases,nextBases);            
+       }
+       
+       
+       
+       public void drawRNARadiate() {
+               drawRNARadiate(-1.0, VARNAConfig.DEFAULT_SPACE_BETWEEN_BASES, true, true);
+       }
+
+       public void drawRNARadiate(VARNAConfig conf) {
+               drawRNARadiate(-1.0, conf._spaceBetweenBases, conf._flatExteriorLoop, false);
+       }
+
+       public static final double FLAT_RECURSIVE_INCREMENT = 20.;
+       
+       public void drawRNARadiate(double dirAngle, double _spaceBetweenBases,
+                       boolean flatExteriorLoop, boolean straightBulges) {
+               _drawn = true;
+               straightBulges = true;
+               _drawMode = DRAW_MODE_RADIATE;
+               Point2D.Double[] coords = new Point2D.Double[_listeBases.size()];
+               Point2D.Double[] centers = new Point2D.Double[_listeBases.size()];
+               double[] angles = new double[_listeBases.size()];
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       coords[i] = new Point2D.Double(0, 0);
+                       centers[i] = new Point2D.Double(0, 0);
+               }
+               if (flatExteriorLoop) {
+                       dirAngle += 1.0 - Math.PI / 2.0;
+                       int i = 0;
+                       double x = 0.0;
+                       double y = 0.0;
+                       double vx = -Math.sin(dirAngle);
+                       double vy = Math.cos(dirAngle);
+                       while (i < _listeBases.size()) {
+                               coords[i].x = x;
+                               coords[i].y = y;
+                               centers[i].x = x + BASE_PAIR_DISTANCE * vy;
+                               centers[i].y = y - BASE_PAIR_DISTANCE * vx;
+                               int j = _listeBases.get(i).getElementStructure();
+                               if (j > i) {
+                                       double increment = 0.;
+                                       if (i+1<_listeBases.size())
+                                       {
+                                               if (_listeBases.get(i+1).getElementStructure()==-1)
+                                               {
+                                                       //increment = -FLAT_RECURSIVE_INCREMENT;
+                                               }
+                                       }
+                                       drawLoop(i, j, x + (BASE_PAIR_DISTANCE * vx / 2.0), y
+                                                       + (BASE_PAIR_DISTANCE * vy / 2.0)+increment, dirAngle,
+                                                       coords, centers, angles, straightBulges);
+                                       centers[i].x = coords[i].x + BASE_PAIR_DISTANCE * vy;
+                                       centers[i].y = y - BASE_PAIR_DISTANCE * vx;
+                                       i = j;
+                                       x += BASE_PAIR_DISTANCE * vx;
+                                       y += BASE_PAIR_DISTANCE * vy;
+                                       centers[i].x = coords[i].x + BASE_PAIR_DISTANCE * vy;
+                                       centers[i].y = y - BASE_PAIR_DISTANCE * vx;
+                               }
+                               x += MULTILOOP_DISTANCE * vx;
+                               y += MULTILOOP_DISTANCE * vy;
+                               i += 1;
+                       }
+               } else {
+                       drawLoop(0, _listeBases.size() - 1, 0, 0, dirAngle, coords, centers, angles, straightBulges);
+               }
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       _listeBases.get(i).setCoords(
+                                       new Point2D.Double(coords[i].x * _spaceBetweenBases,
+                                                       coords[i].y * _spaceBetweenBases));
+                       _listeBases.get(i).setCenter(
+                                       new Point2D.Double(centers[i].x * _spaceBetweenBases,
+                                                       centers[i].y * _spaceBetweenBases));
+               }
+
+               // TODO
+               // change les centres des bases de la premiere helice vers la boucle la
+               // plus proche
+       }
+
+       public void drawRNANAView(VARNAConfig conf) throws ExceptionNAViewAlgorithm {
+               _drawMode = DRAW_MODE_NAVIEW;
+               _drawn = true;
+
+               ArrayList<Double> X = new ArrayList<Double>(_listeBases.size());
+               ArrayList<Double> Y = new ArrayList<Double>(_listeBases.size());
+               ArrayList<Short> pair_table = new ArrayList<Short>(_listeBases.size());
+
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       pair_table.add(Short.valueOf(String.valueOf(_listeBases.get(i)
+                                       .getElementStructure())));
+               }
+               NAView naView = new NAView();
+               naView.naview_xy_coordinates(pair_table, X, Y);
+
+               // Updating individual base positions
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       _listeBases.get(i).setCoords(
+                                       new Point2D.Double(
+                                                       X.get(i) * 2.5 * conf._spaceBetweenBases, Y.get(i)
+                                                                       * 2.5 * conf._spaceBetweenBases));
+               }
+
+               // Updating centers
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       int indicePartner = _listeBases.get(i).getElementStructure();
+                       if (indicePartner != -1) {
+                               Point2D.Double base = _listeBases.get(i).getCoords();
+                               Point2D.Double partner = _listeBases.get(indicePartner)
+                                               .getCoords();
+                               _listeBases.get(i).setCenter(
+                                               new Point2D.Double((base.x + partner.x) / 2.0,
+                                                               (base.y + partner.y) / 2.0));
+                       } else {
+                               Vector<Integer> loop = getLoopBases(i);
+                               double tmpx = 0.0;
+                               double tmpy = 0.0;
+                               for (int j = 0; j < loop.size(); j++) {
+                                       int partner = loop.elementAt(j);
+                                       Point2D.Double loopmember = _listeBases.get(partner)
+                                                       .getCoords();
+                                       tmpx += loopmember.x;
+                                       tmpy += loopmember.y;
+                               }
+                               _listeBases.get(i).setCenter(
+                                               new Point2D.Double(tmpx / loop.size(), tmpy
+                                                               / loop.size()));
+                       }
+               }
+       }
+
+       /*
+        * public void drawMOTIFView() { _drawn = true; _drawMode =
+        * DRAW_MODE_MOTIFVIEW; int spaceBetweenStrand =0; Motif motif = new
+        * Motif(this,get_listeBases()); motif.listStrand(); for (int i = 0; i <
+        * motif.getListStrand().sizeStruct(); i++ ){ for (int j = 0; j <
+        * motif.getListStrand().getStrand(i).sizeStrand(); j++ ){ int indice =
+        * motif.getListStrand().getStrand(i).getMB(j).getIndex();
+        * get_listeBases().get(indice).setCoords( new Point2D.Double(0,0));
+        * get_listeBases().get(indice).setCenter( new Point2D.Double(0, 0));
+        * 
+        * } } //Recherche du brin central int centralStrand =
+        * motif.getCentralStrand();
+        * 
+        * //Cas o? l'on a un motif en ?toile if(centralStrand!=-1){ //On positionne
+        * le brin central motif.positionneSpecificStrand(centralStrand,
+        * spaceBetweenStrand);
+        * 
+        * //On place les autres brins par rapport a ce brin central
+        * motif.orderStrands(centralStrand); }
+        * 
+        * else { centralStrand = 0; motif.positionneStrand(); motif.ajusteStrand();
+        * } motif.reajustement(); motif.deviationBasePair();
+        * motif.setCenterMotif(); }
+        */
+
+       public ArrayList<ModeleBase> getAllPartners(int indice) {
+               ArrayList<ModeleBase> result = new ArrayList<ModeleBase>();
+               ModeleBase me = this.getBaseAt(indice);
+               int i = me.getElementStructure();
+               if (i != -1) {
+                       result.add(getBaseAt(i));
+               }
+               ArrayList<ModeleBP> msbps = getAuxBPs(indice);
+               for (ModeleBP m : msbps) {
+                       result.add(m.getPartner(me));
+               }
+               return result;
+       }
+
+       public int get_drawMode() {
+               return _drawMode;
+       }
+
+       public void setDrawMode(int drawMode) {
+               _drawMode = drawMode;
+       }
+
+       public Set<Integer> getSeparatorPositions(String s) {
+               HashSet<Integer> result = new HashSet<Integer>();
+               int index = s.indexOf(DBNStrandSep);
+               while (index >= 0) {
+                       result.add(index);
+                       index = s.indexOf(DBNStrandSep, index + 1);
+               }
+               return result;
+       }
+
+       public static String DBNStrandSep = "&";
+
+       public void setRNA(String seq, String str)
+                       throws ExceptionFileFormatOrSyntax,
+                       ExceptionUnmatchedClosingParentheses {
+               ArrayList<String> al = RNA.explodeSequence(seq);
+               Set<Integer> sepPos = getSeparatorPositions(str);
+               ArrayList<String> alRes = new ArrayList<String>();
+               Set<Integer> resSepPos = new HashSet<Integer>();
+               String strRes = "";
+               for (int i = 0; i < al.size(); i++) {
+                       if (sepPos.contains(i) && al.get(i).equals(DBNStrandSep)) {
+                               resSepPos.add(alRes.size() - 1);
+                       } else {
+                               alRes.add(al.get(i));
+                               if (i<str.length())
+                               {
+                                       strRes += str.charAt(i);
+                               }
+                               else
+                               {
+                                       strRes += '.';
+                               }
+                       }
+               }
+               for (int i = al.size(); i < str.length(); i++) {
+                       alRes.add(" ");
+                       strRes += str.charAt(i);
+               }
+               setRNA(alRes, strRes);
+               for (int i : resSepPos) {
+                       _backbone.addElement(new ModeleBackboneElement(i,
+                                       BackboneType.DISCONTINUOUS_TYPE));
+               }
+       }
+
+       public void setRNA(String seq) {
+               ArrayList<String> s = RNA.explodeSequence(seq);
+               int[] str = new int[s.size()];
+               for (int i = 0; i < str.length; i++) {
+                       str[i] = -1;
+               }
+               try {
+                       setRNA(s, str);
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       public void setRNA(String seq, int[] str)
+                       throws ExceptionFileFormatOrSyntax,
+                       ExceptionUnmatchedClosingParentheses {
+               setRNA(RNA.explodeSequence(seq), str);
+       }
+
+       public void setRNA(String[] seq, int[] str)
+                       throws ExceptionFileFormatOrSyntax {
+               setRNA(seq, str, 1);
+       }
+
+       public void setRNA(List<String> seq, int[] str)
+                       throws ExceptionFileFormatOrSyntax {
+               setRNA(seq.toArray(new String[seq.size()]), str, 1);
+       }
+
+       public void setRNA(List<String> seq, int[] str, int baseIndex)
+                       throws ExceptionFileFormatOrSyntax {
+               setRNA(seq.toArray(new String[seq.size()]), str, baseIndex);
+       }
+
+       public void setRNA(String[] seq, int[] str, int baseIndex)
+                       throws ExceptionFileFormatOrSyntax {
+               clearAnnotations();
+               _listeBases = new ArrayList<ModeleBase>();
+               if (seq.length != str.length) {
+                       warningEmition("Sequence length " + seq.length
+                                       + " differs from that of secondary structure " + str.length
+                                       + ". \nAdapting sequence length ...");
+                       if (seq.length < str.length) {
+                               String[] nseq = new String[str.length];
+                               for (int i = 0; i < seq.length; i++) {
+                                       nseq[i] = seq[i];
+                               }
+                               for (int i = seq.length; i < nseq.length; i++) {
+                                       nseq[i] = "";
+                               }
+                               seq = nseq;
+                       } else {
+                               String[] seqTmp = new String[str.length];
+                               for (int i = 0; i < str.length; i++) {
+                                       seqTmp[i] = seq[i];
+                               }
+                               seq = seqTmp;
+                       }
+               }
+               for (int i = 0; i < str.length; i++) {
+                       _listeBases.add(new ModeleBaseNucleotide(seq[i], i, baseIndex + i));
+               }
+               applyStruct(str);
+       }
+
+       /**
+        * Sets the RNA to be drawn. Uses when comparison mode is on. Will draw the
+        * super-structure passed in parameters and apply specials styles to the
+        * bases owning by each RNA alignment and both.
+        * 
+        * @param seq
+        *            - The sequence of the super-structure This sequence shall be
+        *            designed like this:
+        *            <code>firstRNA1stBaseSecondRNA1stBaseFirstRNA2ndBaseSecondRNA2ndBase [...]</code>
+        * <br>
+        *            <b>Example:</b> <code>AAC-GUAGA--UGG</code>
+        * @param struct
+        *            - The super-structure
+        * @param basesOwn
+        *            - The RNA owning bases array (each index will be:0 when common
+        *            base, 1 when first RNA alignment base, 2 when second RNA
+        *            alignment base)
+        * @throws ExceptionUnmatchedClosingParentheses
+        * @throws ExceptionFileFormatOrSyntax
+        */
+       public void setRNA(String seq, String struct, ArrayList<Integer> basesOwn)
+                       throws ExceptionUnmatchedClosingParentheses,
+                       ExceptionFileFormatOrSyntax {
+               clearAnnotations();
+               _listeBases = new ArrayList<ModeleBase>();
+               // On "parse" la structure (repérage des points, tiret et couples
+               // parentheses ouvrante/fermante)
+               int[] array_struct = parseStruct(struct);
+               int size = struct.length();
+               int j = 0;
+               for (int i = 0; i < size; i++) {
+                       ModeleBase mb;
+                       if (seq.charAt(j) != seq.charAt(j + 1)) {
+                               ModeleBasesComparison mbc = new ModeleBasesComparison(
+                                               seq.charAt(j), seq.charAt(j + 1), i);
+                               mbc.set_appartenance(basesOwn.get(i));
+                               mbc.setBaseNumber(i + 1);
+                               mb = mbc;
+                       } else {
+                               mb = new ModeleBaseNucleotide("" + seq.charAt(j), i, i + 1);
+
+                       }
+                       _listeBases.add(mb);
+                       j += 2;
+               }
+               for (int i = 0; i < size; i++) {
+                       if (array_struct[i] != -1) {
+                               this.addBPNow(i, array_struct[i]);
+                       }
+
+                       j += 2;
+               }
+       }
+
+       public void setRNA(List<String> seq, String dbnStr)
+                       throws ExceptionUnmatchedClosingParentheses,
+                       ExceptionFileFormatOrSyntax {
+               clearAnnotations();
+               int[] finStr = RNAFactory.parseSecStr(dbnStr);
+               setRNA(seq, finStr);
+       }
+
+       public static ArrayList<String> explodeSequence(String seq) {
+               ArrayList<String> analyzedSeq = new ArrayList<String>();
+               int i = 0;
+               while (i < seq.length()) {
+                       if (seq.charAt(i) == '{') {
+                               boolean found = false;
+                               String buf = "";
+                               i++;
+                               while (!found & (i < seq.length())) {
+                                       if (seq.charAt(i) != '}') {
+                                               buf += seq.charAt(i);
+                                               i++;
+                                       } else {
+                                               found = true;
+                                       }
+                               }
+                               analyzedSeq.add(buf);
+                       } else {
+                               analyzedSeq.add("" + seq.charAt(i));
+                       }
+                       i++;
+               }
+               return analyzedSeq;
+       }
+
+       public int[] parseStruct(String str)
+                       throws ExceptionUnmatchedClosingParentheses,
+                       ExceptionFileFormatOrSyntax {
+               int[] result = new int[str.length()];
+               int unexpectedChar = -1;
+               Stack<Integer> p = new Stack<Integer>();
+               for (int i = 0; i < str.length(); i++) {
+                       char c = str.charAt(i);
+                       if (c == '(') {
+                               p.push(new Integer(i));
+                       } else if (c == '.' || c == '-' || c == ':') {
+                               result[i] = -1;
+                       } else if (c == ')') {
+                               if (p.size() == 0) {
+                                       throw new ExceptionUnmatchedClosingParentheses(i + 1);
+                               }
+                               int j = p.pop().intValue();
+                               result[i] = j;
+                               result[j] = i;
+                       } else {
+                               if (unexpectedChar == -1)
+                                       unexpectedChar = i;
+                               break;
+                       }
+               }
+
+               if (unexpectedChar != -1) {
+                       // warningEmition("Unexpected Character at index:" +
+                       // unexpectedChar);
+               }
+
+               if (p.size() != 0) {
+                       throw new ExceptionUnmatchedClosingParentheses(
+                                       p.pop().intValue() + 1);
+               }
+
+               return result;
+       }
+
+       public Point getHelixInterval(int index) {
+               if ((index < 0) || (index >= _listeBases.size())) {
+                       return new Point(index, index);
+               }
+               int j = _listeBases.get(index).getElementStructure();
+               if (j != -1) {
+                       int minH = index;
+                       int maxH = index;
+                       if (j > index) {
+                               maxH = j;
+                       } else {
+                               minH = j;
+                       }
+                       boolean over = false;
+                       while (!over) {
+                               if ((minH < 0) || (maxH >= _listeBases.size())) {
+                                       over = true;
+                               } else {
+                                       if (_listeBases.get(minH).getElementStructure() == maxH) {
+                                               minH--;
+                                               maxH++;
+                                       } else {
+                                               over = true;
+                                       }
+                               }
+                       }
+                       minH++;
+                       maxH--;
+                       return new Point(minH, maxH);
+               }
+               return new Point(0, 0);
+       }
+
+       public Point getExteriorHelix(int index) {
+               Point h = getHelixInterval(index);
+               int a = h.x;
+               int b = h.y;            
+               while (!((h.x==0)))
+               {                       
+                       a = h.x;
+                       b = h.y;                
+                       h = getHelixInterval(a-1);
+               }
+               return new Point(a, b);
+       }
+       
+       
+       public ArrayList<Integer> getHelix(int index) {
+               ArrayList<Integer> result = new ArrayList<Integer>();
+               if ((index < 0) || (index >= _listeBases.size())) {
+                       return result;
+               }
+               Point p = getHelixInterval(index);
+               for (int i = p.x; i <= p.y; i++) {
+                       result.add(i);
+                       result.add(this._listeBases.get(i).getElementStructure());
+               }
+               return result;
+       }
+
+       public Point getMultiLoop(int index) {
+               if ((index < 0) || (index >= _listeBases.size())) {
+                       return new Point(index, index);
+               }
+               Point h = getHelixInterval(index);
+               int minH = h.x - 1;
+               int maxH = h.y + 1;
+               boolean over = false;
+               while (!over) {
+                       if (minH < 0) {
+                               over = true;
+                               minH = 0;
+                       } else {
+                               if (_listeBases.get(minH).getElementStructure() == -1) {
+                                       minH--;
+                               } else if (_listeBases.get(minH).getElementStructure() < minH) {
+                                       minH = _listeBases.get(minH).getElementStructure() - 1;
+                               } else {
+                                       over = true;
+                               }
+                       }
+               }
+               over = false;
+               while (!over) {
+                       if (maxH > _listeBases.size() - 1) {
+                               over = true;
+                               maxH = _listeBases.size() - 1;
+                       } else {
+                               if (_listeBases.get(maxH).getElementStructure() == -1) {
+                                       maxH++;
+                               } else if (_listeBases.get(maxH).getElementStructure() > maxH) {
+                                       maxH = _listeBases.get(maxH).getElementStructure() + 1;
+                               } else {
+                                       over = true;
+                               }
+                       }
+               }
+               return new Point(minH, maxH);
+       }
+
+       public Vector<Integer> getLoopBases(int startIndex) {
+               Vector<Integer> result = new Vector<Integer>();
+
+               if ((startIndex < 0) || (startIndex >= _listeBases.size())) {
+                       return result;
+               }
+               int index = startIndex;
+               result.add(startIndex);
+               if (_listeBases.get(index).getElementStructure() <= index) {
+                       index = (index + 1) % _listeBases.size();
+               } else {
+                       index = _listeBases.get(index).getElementStructure();
+                       result.add(index);
+                       index = (index + 1) % _listeBases.size();
+               }
+
+               while (index != startIndex) {
+                       result.add(index);
+                       if (_listeBases.get(index).getElementStructure() == -1) {
+                               index = (index + 1) % _listeBases.size();
+                       } else {
+                               index = _listeBases.get(index).getElementStructure();
+                               result.add(index);
+                               index = (index + 1) % _listeBases.size();
+                       }
+               }
+               return result;
+       }
+
+       /**
+        * Returns the RNA secondary structure displayed by this panel as a
+        * well-parenthesized word, accordingly to the DBN format
+        * 
+        * @return This panel's secondary structure
+        */
+       public String getStructDBN() {
+               String result = "";
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       int j = _listeBases.get(i).getElementStructure();
+                       if (j == -1) {
+                               result += ".";
+                       } else if (i > j) {
+                               result += ")";
+                       } else {
+                               result += "(";
+                       }
+               }
+               return addStrandSeparators(result);
+       }
+
+       private ArrayList<ModeleBP> getNonCrossingSubset(
+                       ArrayList<ArrayList<ModeleBP>> rankedBPs) {
+               ArrayList<ModeleBP> currentBPs = new ArrayList<ModeleBP>();
+               Stack<Integer> pile = new Stack<Integer>();
+               for (int i = 0; i < rankedBPs.size(); i++) {
+                       ArrayList<ModeleBP> lbp = rankedBPs.get(i);
+                       if (!lbp.isEmpty()) {
+                               ModeleBP bp = lbp.get(0);
+                               boolean ok = true;
+                               if (!pile.empty()) {
+                                       int x = pile.peek();
+                                       if ((bp.getIndex3() >= x)) {
+                                               ok = false;
+                                       }
+                               }
+                               if (ok) {
+                                       lbp.remove(0);
+                                       currentBPs.add(bp);
+                                       pile.add(bp.getIndex3());
+                               }
+                       }
+                       if (!pile.empty() && (i == pile.peek())) {
+                               pile.pop();
+                       }
+               }
+               return currentBPs;
+       }
+
+       public ArrayList<int[]> paginateStructure() {
+               ArrayList<int[]> result = new ArrayList<int[]>();
+               // Mumbo jumbo to sort the basepair list
+               ArrayList<ModeleBP> bps = this.getAllBPs();
+               ModeleBP[] mt = new ModeleBP[bps.size()];
+               bps.toArray(mt);
+               Arrays.sort(mt, new Comparator<ModeleBP>() {
+                       public int compare(ModeleBP arg0, ModeleBP arg1) {
+                               if (arg0.getIndex5() != arg1.getIndex5())
+                                       return arg0.getIndex5() - arg1.getIndex5();
+                               else
+                                       return arg0.getIndex3() - arg1.getIndex3();
+
+                       }
+               });
+               ArrayList<ArrayList<ModeleBP>> rankedBps = new ArrayList<ArrayList<ModeleBP>>();
+               for (int i = 0; i < getSize(); i++) {
+                       rankedBps.add(new ArrayList<ModeleBP>());
+               }
+               for (int i = 0; i < mt.length; i++) {
+                       rankedBps.get(mt[i].getIndex5()).add(mt[i]);
+               }
+
+               while (!bps.isEmpty()) {
+                       //System.out.println("Page: " + result.size());
+                       ArrayList<ModeleBP> currentBPs = getNonCrossingSubset(rankedBps);
+                       int[] ss = new int[this.getSize()];
+                       for (int i = 0; i < ss.length; i++) {
+                               ss[i] = -1;
+                       }
+
+                       for (int i = 0; i < currentBPs.size(); i++) {
+                               ModeleBP mbp = currentBPs.get(i);
+                               ss[mbp.getIndex3()] = mbp.getIndex5();
+                               ss[mbp.getIndex5()] = mbp.getIndex3();
+                       }
+                       bps.removeAll(currentBPs);
+                       result.add(ss);
+               }
+               return result;
+       }
+
+       private void showBasic(int[] res) {
+               for (int i = 0; i < res.length; i++) {
+                       System.out.print(res[i] + ",");
+               }
+               System.out.println();
+
+       }
+       
+       public int[] getStrandShifts()
+       {
+               int[] result = new int[getSize()];
+               int acc = 0;
+               for (int i=0;i<getSize();i++)
+               {
+                       if (_backbone.getTypeBefore(i)==BackboneType.DISCONTINUOUS_TYPE)
+                       {
+                               acc++;
+                       }
+                       result[i] = acc;
+               }
+               return result;
+               
+       }
+       
+       public String addStrandSeparators(String s)
+       {
+               String res = "";
+               for (int i=0;i<s.length();i++)
+               {
+                       res += s.charAt(i);
+                       if (_backbone.getTypeAfter(i)==BackboneType.DISCONTINUOUS_TYPE)
+                       {
+                               res += DBNStrandSep;
+                       }
+               }
+               return res;
+       }
+       
+
+       public String getStructDBN(boolean includeMostPKs) {
+               String result = getStructDBN();
+               if (includeMostPKs) {
+                       ArrayList<int[]> pages = paginateStructure();
+                       char[] res = new char[getSize()];
+                       for (int i = 0; i < res.length; i++) {
+                               res[i] = '.';
+                       }
+                       char[] open = { '(', '[', '{', '<', 'A', 'B', 'C', 'D', 'E', 'F',
+                                       'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+                                       'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
+                       
+                       char[] close = new char[open.length];
+                       close[0] = ')';
+                       close[1] = ']';
+                       close[2] = '}';
+                       close[3] = '>';
+                       for (int i=4; i<open.length;i++)
+                       {
+                               close[i] = Character.toLowerCase(open[i]);
+                       }
+                       
+                       for (int p = 0; p < Math.min(pages.size(), open.length); p++) {
+                               int[] page = pages.get(p);
+                               //showBasic(page);
+                               for (int i = 0; i < res.length; i++) {
+                                       if (page[i] != -1 && page[i] > i && res[i] == '.'
+                                                       && res[page[i]] == '.') {
+                                               res[i] = open[p];
+                                               res[page[i]] = close[p];
+                                       }
+                               }
+                       }
+                       result = "";
+                       for (int i = 0; i < res.length; i++) {
+                               result += res[i];
+                       }
+
+               }
+               return addStrandSeparators(result);
+
+       }
+
+       public String getStructDBN(int[] str) {
+               String result = "";
+               for (int i = 0; i < str.length; i++) {
+                       if (str[i] == -1) {
+                               result += ".";
+                       } else if (str[i] > i) {
+                               result += "(";
+                       } else {
+                               result += ")";
+                       }
+               }
+               return addStrandSeparators(result);
+       }
+
+       /**
+        * Returns the raw nucleotides sequence for the displayed RNA
+        * 
+        * @return The RNA sequence
+        */
+       public String getSeq() {
+               String result = "";
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       result += ((ModeleBase) _listeBases.get(i)).getContent();
+               }
+               return addStrandSeparators(result);
+       }
+
+       public String getStructBPSEQ() {
+               String result = "";
+               int[] str = getNonOverlappingStruct();
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       result += (i + 1) + " "
+                                       + ((ModeleBaseNucleotide) _listeBases.get(i)).getContent()
+                                       + " " + (str[i] + 1) + "\n";
+               }
+               return result;
+       }
+
+       public int[] getNonCrossingStruct() {
+               int[] result = new int[_listeBases.size()];
+               // Adding "planar" base-pairs
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       result[i] = _listeBases.get(i).getElementStructure();
+               }
+               return result;
+       }
+
+       public int[] getNonOverlappingStruct() {
+               int[] result = getNonCrossingStruct();
+               // Adding additional base pairs when possible (No more than one
+               // base-pair per base)
+               for (int i = 0; i < _structureAux.size(); i++) {
+                       ModeleBP msbp = _structureAux.get(i);
+                       ModeleBase mb5 = msbp.getPartner5();
+                       ModeleBase mb3 = msbp.getPartner3();
+                       int j5 = mb5.getIndex();
+                       int j3 = mb3.getIndex();
+                       if ((result[j3] == -1) && (result[j5] == -1)) {
+                               result[j3] = j5;
+                               result[j5] = j3;
+                       }
+               }
+               return result;
+       }
+
+       public String getStructCT() {
+               String result = "";
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       result += (i + 1) + " "
+                                       + ((ModeleBase) _listeBases.get(i)).getContent() + " " + i
+                                       + " " + (i + 2) + " "
+                                       + (_listeBases.get(i).getElementStructure() + 1) + " "
+                                       + (i + 1) + "\n";
+               }
+               return result;
+       }
+
+       public void saveAsBPSEQ(String path, String title)
+                       throws ExceptionExportFailed, ExceptionPermissionDenied {
+               try {
+                       FileWriter f = new FileWriter(path);
+                       f.write("# " + title + "\n");
+                       f.write(this.getStructBPSEQ() + "\n");
+                       f.close();
+               } catch (IOException e) {
+                       throw new ExceptionExportFailed(e.getMessage(), path);
+               }
+       }
+
+       public void saveAsCT(String path, String title)
+                       throws ExceptionExportFailed, ExceptionPermissionDenied {
+               try {
+                       FileWriter f = new FileWriter(path);
+                       f.write("" + _listeBases.size() + " " + title + "\n");
+                       f.write(this.getStructCT() + "\n");
+                       f.close();
+               } catch (IOException e) {
+                       throw new ExceptionExportFailed(e.getMessage(), path);
+               }
+       }
+
+       public void saveAsDBN(String path, String title)
+                       throws ExceptionExportFailed, ExceptionPermissionDenied {
+               try {
+                       FileWriter f = new FileWriter(path);
+                       f.write("> " + title + "\n");
+                       f.write(getListeBasesToString() + "\n");
+                       f.write(getStructDBN() + "\n");
+                       f.close();
+               } catch (IOException e) {
+                       throw new ExceptionExportFailed(e.getMessage(), path);
+               }
+       }
+
+       public String getListeBasesToString() {
+               String s = new String();
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       s += ((ModeleBaseNucleotide) _listeBases.get(i)).getContent();
+               }
+               return addStrandSeparators(s);
+       }
+
+       public void applyBPs(ArrayList<ModeleBP> allbps) {
+               ArrayList<ModeleBP> planar = new ArrayList<ModeleBP>();
+               ArrayList<ModeleBP> others = new ArrayList<ModeleBP>();
+               // System.err.println("Sequence: "+this.getSeq());
+               RNAMLParser.planarize(allbps, planar, others, getSize());
+               // System.err.println("All:"+allbps);
+               // System.err.println("=> Planar: "+planar);
+               // System.err.println("=> Others: "+others);
+
+               for (ModeleBP mb : planar) {
+                       addBPnow(mb.getPartner5().getIndex(), mb.getPartner3().getIndex(),
+                                       mb);
+               }
+
+               for (ModeleBP mb : others) {
+                       addBPAux(mb.getPartner5().getIndex(), mb.getPartner3().getIndex(),
+                                       mb);
+               }
+       }
+
+       public void set_listeBases(ArrayList<ModeleBase> _liste) {
+               this._listeBases = _liste;
+       }
+
+       public void addVARNAListener(InterfaceVARNAListener rl) {
+               _listeVARNAListener.add(rl);
+       }
+
+       public void warningEmition(String warningMessage) {
+               for (int i = 0; i < _listeVARNAListener.size(); i++) {
+                       _listeVARNAListener.get(i).onWarningEmitted(warningMessage);
+               }
+       }
+
+       public void applyStyleOnBases(ArrayList<Integer> basesList,
+                       ModelBaseStyle style) {
+               for (int i = 1; i < basesList.size(); i++) {
+                       _listeBases.get(basesList.get(i)).setStyleBase(style);
+               }
+       }
+
+       private int[] correctReciprocity(int[] str) {
+               int[] result = new int[str.length];
+               for (int i = 0; i < str.length; i++) {
+                       if (str[i] != -1) {
+                               if (i == str[str[i]]) {
+                                       result[i] = str[i];
+                               } else {
+                                       str[str[i]] = i;
+                               }
+                       } else {
+                               result[i] = -1;
+                       }
+               }
+               return result;
+       }
+
+       private void applyStruct(int[] str) throws ExceptionFileFormatOrSyntax {
+               str = correctReciprocity(str);
+
+               int[] planarSubset = RNAMLParser.planarize(str);
+               _structureAux.clear();
+
+               for (int i = 0; i < planarSubset.length; i++) {
+                       if (str[i] > i) {
+                               if (planarSubset[i] > i) {
+                                       addBPNow(i, planarSubset[i]);
+                               } else if ((planarSubset[i] != str[i])) {
+                                       addBPAux(i, str[i]);
+                               }
+                       }
+               }
+
+       }
+
+       public ArrayList<ModeleBase> get_listeBases() {
+               return _listeBases;
+       }
+
+       public int getSize() {
+               return _listeBases.size();
+       }
+
+       public ArrayList<Integer> findAll() {
+               ArrayList<Integer> listAll = new ArrayList<Integer>();
+               for (int i = 0; i < get_listeBases().size(); i++) {
+                       listAll.add(i);
+               }
+               return listAll;
+       }
+
+       public ArrayList<Integer> findBulge(int index) {
+               ArrayList<Integer> listUp = new ArrayList<Integer>();
+               if (get_listeBases().get(index).getElementStructure() == -1) {
+                       int i = index;
+                       boolean over = false;
+                       while ((i < get_listeBases().size()) && !over) {
+                               int j = get_listeBases().get(i).getElementStructure();
+                               if (j == -1) {
+                                       listUp.add(i);
+                                       i++;
+                               } else {
+                                       over = true;
+                               }
+                       }
+                       i = index - 1;
+                       over = false;
+                       while ((i >= 0) && !over) {
+                               int j = get_listeBases().get(i).getElementStructure();
+                               if (j == -1) {
+                                       listUp.add(i);
+                                       i--;
+                               } else {
+                                       over = true;
+                               }
+                       }
+               }
+               return listUp;
+       }
+
+       public ArrayList<Integer> findStem(int index) {
+               ArrayList<Integer> listUp = new ArrayList<Integer>();
+               int i = index;
+               do {
+                       listUp.add(i);
+                       int j = get_listeBases().get(i).getElementStructure();
+                       if (j == -1) {
+                               i = (i + 1) % getSize();
+                       } else {
+                               if ((j < i) && (index <= i) && (j <= index)) {
+                                       i = j;
+                               } else {
+                                       i = (i + 1) % getSize();
+                               }
+                       }
+               } while (i != index);
+               return listUp;
+       }
+
+       public int getHelixCountOnLoop(int indice) {
+               int cptHelice = 0;
+               if (indice < 0 || indice >= get_listeBases().size())
+                       return cptHelice;
+               int i = indice;
+               int j = get_listeBases().get(i).getElementStructure();
+               // Only way to distinguish "supporting base-pair" from others
+               boolean justJumped = false;
+               if ((j != -1) && (j < i)) {
+                       i = j + 1;
+                       indice = i;
+               }
+               do {
+                       j = get_listeBases().get(i).getElementStructure();
+                       if ((j != -1) && (!justJumped)) {
+                               i = j;
+                               justJumped = true;
+                               cptHelice++;
+                       } else {
+                               i = (i + 1) % get_listeBases().size();
+                               justJumped = false;
+                       }
+               } while (i != indice);
+               return cptHelice;
+       }
+
+       public ArrayList<Integer> findLoop(int indice) {
+               return findLoopForward(indice);
+       }
+
+       public ArrayList<Integer> findLoopForward(int indice) {
+               ArrayList<Integer> base = new ArrayList<Integer>();
+               if (indice < 0 || indice >= get_listeBases().size())
+                       return base;
+               int i = indice;
+               int j = get_listeBases().get(i).getElementStructure();
+               // Only way to distinguish "supporting base-pair" from others
+               boolean justJumped = false;
+               if (j != -1) {
+                       i = Math.min(i, j) + 1;
+                       indice = i;
+               }
+               do {
+                       base.add(i);
+                       j = get_listeBases().get(i).getElementStructure();
+                       if ((j != -1) && (!justJumped)) {
+                               i = j;
+                               justJumped = true;
+                       } else {
+                               i = (i + 1) % get_listeBases().size();
+                               justJumped = false;
+                       }
+               } while (i != indice);
+               return base;
+       }
+
+       public ArrayList<Integer> findPair(int indice) {
+               ArrayList<Integer> base = new ArrayList<Integer>();
+               int j = get_listeBases().get(indice).getElementStructure();
+               if (j != -1) {
+                       base.add(Math.min(indice, j));
+                       base.add(Math.max(indice, j));
+               }
+
+               return base;
+
+       }
+
+       public ArrayList<Integer> findLoopBackward(int indice) {
+               ArrayList<Integer> base = new ArrayList<Integer>();
+               if (indice < 0 || indice >= get_listeBases().size())
+                       return base;
+               int i = indice;
+               int j = get_listeBases().get(i).getElementStructure();
+               // Only way to distinguish "supporting base-pair" from others
+               boolean justJumped = false;
+               if (j != -1) {
+                       i = Math.min(i, j) - 1;
+                       indice = i;
+               }
+               if (i < 0) {
+                       return base;
+               }
+               do {
+                       base.add(i);
+                       j = get_listeBases().get(i).getElementStructure();
+                       if ((j != -1) && (!justJumped)) {
+                               i = j;
+                               justJumped = true;
+                       } else {
+                               i = (i + get_listeBases().size() - 1) % get_listeBases().size();
+                               justJumped = false;
+                       }
+               } while (i != indice);
+               return base;
+       }
+
+       public ArrayList<Integer> findHelix(int indice) {
+               ArrayList<Integer> list = new ArrayList<Integer>();
+               if (get_listeBases().get(indice).getElementStructure() != -1) {
+                       list.add(indice);
+                       list.add(get_listeBases().get(indice).getElementStructure());
+                       int i = 1, prec = get_listeBases().get(indice)
+                                       .getElementStructure();
+                       while (indice + i < get_listeBases().size()
+                                       && get_listeBases().get(indice + i).getElementStructure() != -1
+                                       && get_listeBases().get(indice + i).getElementStructure() == prec - 1) {
+                               list.add(indice + i);
+                               list.add(get_listeBases().get(indice + i).getElementStructure());
+                               prec = get_listeBases().get(indice + i).getElementStructure();
+                               i++;
+                       }
+                       i = -1;
+                       prec = get_listeBases().get(indice).getElementStructure();
+                       while (indice + i >= 0
+                                       && get_listeBases().get(indice + i).getElementStructure() != -1
+                                       && get_listeBases().get(indice + i).getElementStructure() == prec + 1) {
+                               list.add(indice + i);
+                               list.add(get_listeBases().get(indice + i).getElementStructure());
+                               prec = get_listeBases().get(indice + i).getElementStructure();
+                               i--;
+                       }
+               }
+               return list;
+       }
+
+       public ArrayList<Integer> find3Prime(int indice) {
+               ArrayList<Integer> list = new ArrayList<Integer>();
+               boolean over = false;
+               while ((indice >= 0) && !over) {
+                       over = (get_listeBases().get(indice).getElementStructure() != -1);
+                       indice--;
+               }
+               indice++;
+               if (over) {
+                       indice++;
+               }
+               for (int i = indice; i < get_listeBases().size(); i++) {
+                       list.add(i);
+                       if (get_listeBases().get(i).getElementStructure() != -1) {
+                               return new ArrayList<Integer>();
+                       }
+               }
+               return list;
+       }
+
+       public ArrayList<Integer> find5Prime(int indice) {
+               ArrayList<Integer> list = new ArrayList<Integer>();
+               for (int i = 0; i <= indice; i++) {
+                       list.add(i);
+                       if (get_listeBases().get(i).getElementStructure() != -1) {
+                               return new ArrayList<Integer>();
+                       }
+               }
+               return list;
+       }
+
+       public static Double angle(Point2D.Double p1, Point2D.Double p2,
+                       Point2D.Double p3) {
+               Double alpha = Math.atan2(p1.y - p2.y, p1.x - p2.x);
+               Double beta = Math.atan2(p3.y - p2.y, p3.x - p2.x);
+               Double angle = (beta - alpha);
+
+               // Correction de l'angle pour le resituer entre 0 et 2PI
+               while (angle < 0.0 || angle > 2 * Math.PI) {
+                       if (angle < 0.0)
+                               angle += 2 * Math.PI;
+                       else if (angle > 2 * Math.PI)
+                               angle -= 2 * Math.PI;
+               }
+               return angle;
+       }
+
+       public ArrayList<Integer> findNonPairedBaseGroup(Integer get_nearestBase) {
+               // detection 3', 5', bulge
+               ArrayList<Integer> list = new ArrayList<Integer>();
+               int indice = get_nearestBase;
+               boolean nonpairedUp = true, nonpairedDown = true;
+               while (indice < get_listeBases().size() && nonpairedUp) {
+                       if (get_listeBases().get(indice).getElementStructure() == -1) {
+                               list.add(indice);
+                               indice++;
+                       } else {
+                               nonpairedUp = false;
+                       }
+               }
+               indice = get_nearestBase - 1;
+               while (indice >= 0 && nonpairedDown) {
+                       if (get_listeBases().get(indice).getElementStructure() == -1) {
+                               list.add(indice);
+                               indice--;
+                       } else {
+                               nonpairedDown = false;
+                       }
+               }
+               return list;
+       }
+
+       /*
+        * public boolean getDrawn() { return _drawn; }
+        */
+
+       public ArrayList<ModeleBP> getStructureAux() {
+               return _structureAux;
+       }
+
+       /**
+        * Translates a base number into its corresponding index. Although both
+        * should be unique, base numbers are not necessarily contiguous, and
+        * indices should be preferred for any reasonably complex algorithmic
+        * treatment.
+        * 
+        * @param num
+        *            The base number
+        * @return The first index whose associated Base model has base number
+        *         <code>num</code>, <code>-1</code> of no such base model exists.
+        */
+
+       public int getIndexFromBaseNumber(int num) {
+               for (int i = 0; i < this._listeBases.size(); i++) {
+                       if (_listeBases.get(i).getBaseNumber() == num) {
+                               return i;
+                       }
+               }
+               return -1;
+       }
+
+       /**
+        * Adds a base pair to this RNA's structure. Tries to add it to the
+        * secondary structure first, eventually adding it to the 'tertiary'
+        * interactions if it clashes with the current secondary structure.
+        * 
+        * @param baseNumber5
+        *            - Base number of the origin of this base pair
+        * @param baseNumber3
+        *            - Base number of the destination of this base pair
+        */
+
+       public void addBPToStructureUsingNumbers(int baseNumber5, int baseNumber3) {
+               int i = getIndexFromBaseNumber(baseNumber5);
+               int j = getIndexFromBaseNumber(baseNumber3);
+               addBP(i, j);
+       }
+
+       /**
+        * Adds a base pair to this RNA's structure. Tries to add it to the
+        * secondary structure first, possibly adding it to the 'tertiary'
+        * interactions if it clashes with the current secondary structure.
+        * 
+        * @param number5
+        *            - Base number of the origin of this base pair
+        * @param number3
+        *            - Base number of the destination of this base pair
+        */
+
+       public void addBPToStructureUsingNumbers(int number5, int number3,
+                       ModeleBP msbp) {
+               addBP(getIndexFromBaseNumber(number5), getIndexFromBaseNumber(number3),
+                               msbp);
+       }
+
+       public void addBP(int index5, int index3) {
+               int i = index5;
+               int j = index3;
+               ModeleBase part5 = _listeBases.get(i);
+               ModeleBase part3 = _listeBases.get(j);
+               ModeleBP msbp = new ModeleBP(part5, part3);
+               addBP(i, j, msbp);
+       }
+
+       public void addBP(int index5, int index3, ModeleBP msbp) {
+               int i = index5;
+               int j = index3;
+
+               if (j < i) {
+                       int k = j;
+                       j = i;
+                       i = k;
+               }
+               if (i != -1) {
+                       for (int k = i; k <= j; k++) {
+                               ModeleBase tmp = _listeBases.get(k);
+                               int l = tmp.getElementStructure();
+                               if (l != -1) {
+                                       if ((l <= i) || (l >= j)) {
+                                               addBPAux(i, j, msbp);
+                                               return;
+                                       }
+                               }
+                       }
+                       addBPnow(i, j, msbp);
+               }
+       }
+
+       public void removeBP(ModeleBP ms) {
+               if (_structureAux.contains(ms)) {
+                       _structureAux.remove(ms);
+               } else {
+                       ModeleBase m5 = ms.getPartner5();
+                       ModeleBase m3 = ms.getPartner3();
+                       int i = m5.getIndex();
+                       int j = m3.getIndex();
+                       if ((m5.getElementStructure() == m3.getIndex())
+                                       && (m3.getElementStructure() == m5.getIndex())) {
+                               m5.removeElementStructure();
+                               m3.removeElementStructure();
+                       }
+               }
+       }
+
+       /**
+        * Register base-pair, no question asked. More precisely, this function will
+        * not try to determine if the base-pairs crosses any other.
+        * 
+        * @param i
+        * @param j
+        * @param msbp
+        */
+       private void addBPNow(int i, int j) {
+               if (j < i) {
+                       int k = j;
+                       j = i;
+                       i = k;
+               }
+
+               ModeleBase part5 = _listeBases.get(i);
+               ModeleBase part3 = _listeBases.get(j);
+               ModeleBP msbp = new ModeleBP(part5, part3);
+               addBPnow(i, j, msbp);
+       }
+
+       /**
+        * Register base-pair, no question asked. More precisely, this function will
+        * not try to determine if the base-pairs crosses any other.
+        * 
+        * @param i
+        * @param j
+        * @param msbp
+        */
+       private void addBPnow(int i, int j, ModeleBP msbp) {
+               if (j < i) {
+                       int k = j;
+                       j = i;
+                       i = k;
+               }
+               ModeleBase part5 = _listeBases.get(i);
+               ModeleBase part3 = _listeBases.get(j);
+               msbp.setPartner5(part5);
+               msbp.setPartner3(part3);
+               part5.setElementStructure(j, msbp);
+               part3.setElementStructure(i, msbp);
+       }
+
+       public void addBPAux(int i, int j) {
+               ModeleBase part5 = _listeBases.get(i);
+               ModeleBase part3 = _listeBases.get(j);
+               ModeleBP msbp = new ModeleBP(part5, part3);
+               addBPAux(i, j, msbp);
+       }
+
+       public void addBPAux(int i, int j, ModeleBP msbp) {
+               if (j < i) {
+                       int k = j;
+                       j = i;
+                       i = k;
+               }
+               ModeleBase part5 = _listeBases.get(i);
+               ModeleBase part3 = _listeBases.get(j);
+               msbp.setPartner5(part5);
+               msbp.setPartner3(part3);
+               _structureAux.add(msbp);
+       }
+
+       public ArrayList<ModeleBP> getBPsAt(int i) {
+               ArrayList<ModeleBP> result = new ArrayList<ModeleBP>();
+               if (_listeBases.get(i).getElementStructure() != -1) {
+                       result.add(_listeBases.get(i).getStyleBP());
+               }
+               for (int k = 0; k < _structureAux.size(); k++) {
+                       ModeleBP bp = _structureAux.get(k);
+                       if ((bp.getPartner5().getIndex() == i)
+                                       || (bp.getPartner3().getIndex() == i)) {
+                               result.add(bp);
+                       }
+               }
+               return result;
+
+       }
+
+       public ModeleBP getBPStyle(int i, int j) {
+               ModeleBP result = null;
+               if (i > j) {
+                       int k = j;
+                       j = i;
+                       i = k;
+               }
+               if (_listeBases.get(i).getElementStructure() == j) {
+                       result = _listeBases.get(i).getStyleBP();
+               }
+               for (int k = 0; k < _structureAux.size(); k++) {
+                       ModeleBP bp = _structureAux.get(k);
+                       if ((bp.getPartner5().getIndex() == i)
+                                       && (bp.getPartner3().getIndex() == j)) {
+                               result = bp;
+                       }
+               }
+               return result;
+       }
+
+       public ArrayList<ModeleBP> getSecStrBPs() {
+               ArrayList<ModeleBP> result = new ArrayList<ModeleBP>();
+               for (int i = 0; i < this.getSize(); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       int k = mb.getElementStructure();
+                       if ((k != -1) && (k > i)) {
+                               result.add(mb.getStyleBP());
+                       }
+               }
+               return result;
+       }
+
+       public ArrayList<ModeleBP> getAuxBPs() {
+               ArrayList<ModeleBP> result = new ArrayList<ModeleBP>();
+               for (ModeleBP bp : _structureAux) {
+                       result.add(bp);
+               }
+               return result;
+       }
+
+       public ArrayList<ModeleBP> getAllBPs() {
+               ArrayList<ModeleBP> result = new ArrayList<ModeleBP>();
+               result.addAll(getSecStrBPs());
+               result.addAll(getAuxBPs());
+               return result;
+       }
+
+       public ArrayList<ModeleBP> getAuxBPs(int i) {
+               ArrayList<ModeleBP> result = new ArrayList<ModeleBP>();
+               for (ModeleBP bp : _structureAux) {
+                       if ((bp.getPartner5().getIndex() == i)
+                                       || (bp.getPartner3().getIndex() == i)) {
+                               result.add(bp);
+                       }
+               }
+               return result;
+       }
+
+       public void setBaseInnerColor(Color c) {
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       mb.getStyleBase().setBaseInnerColor(c);
+               }
+       }
+
+       public void setBaseNumbersColor(Color c) {
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       mb.getStyleBase().setBaseNumberColor(c);
+               }
+       }
+
+       public void setBaseNameColor(Color c) {
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       mb.getStyleBase().setBaseNameColor(c);
+               }
+       }
+
+       public void setBaseOutlineColor(Color c) {
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       mb.getStyleBase().setBaseOutlineColor(c);
+               }
+       }
+
+       public String getName() {
+               return _name;
+       }
+
+       public void setName(String n) {
+               _name = n;
+       }
+
+       public ArrayList<TextAnnotation> getAnnotations() {
+               return _listeAnnotations;
+       }
+
+       public boolean removeAnnotation(TextAnnotation t) {
+               return _listeAnnotations.remove(t);
+       }
+
+       public void addAnnotation(TextAnnotation t) {
+               _listeAnnotations.add(t);
+       }
+
+       public void removeAnnotation(String filter) {
+               ArrayList<TextAnnotation> condamne = new ArrayList<TextAnnotation>();
+               for (TextAnnotation t : _listeAnnotations) {
+                       if (t.getTexte().contains(filter)) {
+                               condamne.add(t);
+                       }
+               }
+               for (TextAnnotation t : condamne) {
+                       _listeAnnotations.remove(t);
+               }
+       }
+
+       public void clearAnnotations() {
+               _listeAnnotations.clear();
+       }
+
+       private boolean _strandEndsAnnotated = false;
+
+       public void autoAnnotateStrandEnds() {
+               if (!_strandEndsAnnotated) {
+                       int tailleListBases = _listeBases.size();
+                       boolean endAnnotate = false;
+                       addAnnotation(new TextAnnotation("5'", _listeBases.get(0)));
+                       for (int i = 0; i < _listeBases.size() - 1; i++) {
+                               int realposA = _listeBases.get(i).getBaseNumber();
+                               int realposB = _listeBases.get(i + 1).getBaseNumber();
+                               if (realposB - realposA != 1) {
+                                       addAnnotation(new TextAnnotation("3'", _listeBases.get(i)));
+                                       addAnnotation(new TextAnnotation("5'",
+                                                       _listeBases.get(i + 1)));
+                                       if (i + 1 == _listeBases.size() - 1) {
+                                               endAnnotate = true;
+                                       }
+                               }
+                       }
+                       if (!endAnnotate) {
+                               addAnnotation(new TextAnnotation("3'",
+                                               _listeBases.get(tailleListBases - 1)));
+                       }
+                       _strandEndsAnnotated = true;
+               } else {
+                       removeAnnotation("3'");
+                       removeAnnotation("5'");
+                       _strandEndsAnnotated = false;
+               }
+       }
+
+       public void autoAnnotateHelices() {
+               Stack<Integer> p = new Stack<Integer>();
+               p.push(0);
+               int nbH = 1;
+               while (!p.empty()) {
+                       int i = p.pop();
+                       if (i < _listeBases.size()) {
+                               ModeleBase mb = _listeBases.get(i);
+                               int j = mb.getElementStructure();
+                               if (j == -1) {
+                                       p.push(i + 1);
+                               } else {
+                                       if (j > i) {
+                                               ModeleBase mbp = _listeBases.get(j);
+                                               p.push(j + 1);
+                                               ArrayList<ModeleBase> h = new ArrayList<ModeleBase>();
+                                               int k = 1;
+                                               while (mb.getElementStructure() == mbp.getIndex()) {
+                                                       h.add(mb);
+                                                       h.add(mbp);
+                                                       mb = _listeBases.get(i + k);
+                                                       mbp = _listeBases.get(j - k);
+
+                                                       k++;
+                                               }
+                                               try {
+                                                       addAnnotation(new TextAnnotation("H" + nbH++, h,
+                                                                       TextAnnotation.AnchorType.HELIX));
+                                               } catch (Exception e) {
+                                                       e.printStackTrace();
+                                               }
+                                               p.push(i + k);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void autoAnnotateTerminalLoops() {
+               Stack<Integer> p = new Stack<Integer>();
+               p.push(0);
+               int nbT = 1;
+               while (!p.empty()) {
+                       int i = p.pop();
+                       if (i < _listeBases.size()) {
+                               ModeleBase mb = _listeBases.get(i);
+                               int j = mb.getElementStructure();
+                               if (j == -1) {
+                                       int k = 1;
+                                       ArrayList<ModeleBase> t = new ArrayList<ModeleBase>();
+                                       while ((i + k < getSize())
+                                                       && (mb.getElementStructure() == -1)) {
+                                               t.add(mb);
+                                               mb = _listeBases.get(i + k);
+                                               k++;
+                                       }
+                                       if (mb.getElementStructure() != -1) {
+                                               if (mb.getElementStructure() == i - 1) {
+                                                       try {
+                                                               t.add(_listeBases.get(i - 1));
+                                                               t.add(_listeBases.get(i + k - 1));
+                                                               addAnnotation(new TextAnnotation("T" + nbT++,
+                                                                               t, TextAnnotation.AnchorType.LOOP));
+                                                       } catch (Exception e) {
+                                                               e.printStackTrace();
+                                                       }
+                                               }
+                                               p.push(i + k - 1);
+                                       }
+
+                               } else {
+                                       if (j > i) {
+                                               p.push(j + 1);
+                                               p.push(i + 1);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void autoAnnotateInteriorLoops() {
+               Stack<Integer> p = new Stack<Integer>();
+               p.push(0);
+               int nbT = 1;
+               while (!p.empty()) {
+                       int i = p.pop();
+                       if (i < _listeBases.size()) {
+                               ModeleBase mb = _listeBases.get(i);
+                               int j = mb.getElementStructure();
+                               if (j == -1) {
+                                       int k = i + 1;
+                                       ArrayList<ModeleBase> t = new ArrayList<ModeleBase>();
+                                       boolean terminal = true;
+                                       while ((k < getSize())
+                                                       && ((mb.getElementStructure() >= i) || (mb
+                                                                       .getElementStructure() == -1))) {
+                                               t.add(mb);
+                                               mb = _listeBases.get(k);
+                                               if ((mb.getElementStructure() == -1)
+                                                               || (mb.getElementStructure() < k))
+                                                       k++;
+                                               else {
+                                                       p.push(k);
+                                                       terminal = false;
+                                                       k = mb.getElementStructure();
+                                               }
+                                       }
+                                       if (mb.getElementStructure() != -1) {
+                                               if ((mb.getElementStructure() == i - 1) && !terminal) {
+                                                       try {
+                                                               t.add(_listeBases.get(i - 1));
+                                                               t.add(_listeBases.get(k - 1));
+                                                               addAnnotation(new TextAnnotation("I" + nbT++,
+                                                                               t, TextAnnotation.AnchorType.LOOP));
+                                                       } catch (Exception e) {
+                                                               e.printStackTrace();
+                                                       }
+                                                       p.push(k - 1);
+                                               }
+                                       }
+                               } else {
+                                       if (j > i) {
+                                               p.push(i + 1);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       @SuppressWarnings("unchecked")
+       public TextAnnotation getAnnotation(TextAnnotation.AnchorType type,
+                       ModeleBase base) {
+               TextAnnotation result = null;
+               for (TextAnnotation t : _listeAnnotations) {
+                       if (t.getType() == type) {
+                               switch (type) {
+                               case BASE:
+                                       if (base == (ModeleBase) t.getAncrage())
+                                               return t;
+                                       break;
+                               case HELIX:
+                               case LOOP: {
+                                       ArrayList<ModeleBase> mbl = (ArrayList<ModeleBase>) t
+                                                       .getAncrage();
+                                       if (mbl.contains(base))
+                                               return t;
+                               }
+                                       break;
+                               }
+                       }
+               }
+               return result;
+       }
+
+       public void addChemProbAnnotation(ChemProbAnnotation cpa) {
+               //System.err.println(cpa.isOut());
+               _chemProbAnnotations.add(cpa);
+       }
+
+       public ArrayList<ChemProbAnnotation> getChemProbAnnotations() {
+               return _chemProbAnnotations;
+       }
+
+       public void setColorMapValues(Double[] values, ModeleColorMap cm) {
+               setColorMapValues(values, cm, false);
+       }
+
+       public void adaptColorMapToValues(ModeleColorMap cm) {
+               double min = Double.MAX_VALUE;
+               double max = Double.MIN_VALUE;
+               for (int i = 0; i < Math.min(_listeBases.size(), _listeBases.size()); i++) {
+                       ModeleBase mb = _listeBases.get(i);
+                       max = Math.max(max, mb.getValue());
+                       min = Math.min(min, mb.getValue());
+               }
+               cm.rescale(min, max);
+       }
+       
+       
+       private ArrayList<Double> loadDotPlot(StreamTokenizer st)
+       {
+               ArrayList<Double> result = new ArrayList<Double>();
+               try {
+                       boolean inSeq = false;
+                       String sequence = "";
+                       ArrayList<Double> accumulator = new ArrayList<Double>();
+                       int type = st.nextToken();
+                       Hashtable<Couple<Integer,Integer>,Double> BP = new Hashtable<Couple<Integer,Integer>,Double>();
+                       while (type != StreamTokenizer.TT_EOF) {
+                               switch (type) {
+                                       case (StreamTokenizer.TT_NUMBER):
+                                               accumulator.add(st.nval);
+                                               break;
+                                       case (StreamTokenizer.TT_EOL):
+                                               break;
+                                       case (StreamTokenizer.TT_WORD):
+                                               if (st.sval.equals("/sequence"))
+                                               {
+                                                       inSeq = true;
+                                               }
+                                               else if (st.sval.equals("ubox"))
+                                               {
+                                                       int i = accumulator.get(accumulator.size()-3).intValue()-1;
+                                                       int j = accumulator.get(accumulator.size()-2).intValue()-1;
+                                                       double val = accumulator.get(accumulator.size()-1);
+                                                       //System.err.println((char) type);                      
+                                                       BP.put(new Couple<Integer, Integer>(Math.min(i, j), Math.max(i, j)),val*val);
+                                                       accumulator.clear();
+                                               }
+                                               else if (inSeq)
+                                               {
+                                                       sequence += st.sval;
+                                               }
+                                               break;
+                                       case ')':
+                                               inSeq = false;
+                                       break;
+                               }
+                               type = st.nextToken();
+                       }
+                       for (int i = 0; i < getSize(); i++) {
+                               int j = getBaseAt(i).getElementStructure();
+                               if (j != -1) {
+                                       Couple<Integer, Integer> coor = new Couple<Integer, Integer>(
+                                                       Math.min(i, j), Math.max(i, j));
+                                       if (BP.containsKey(coor)) {
+                                               result.add(BP.get(coor));
+                                       } else {
+                                               result.add(0.);
+                                       }
+                               } else {
+                                       double acc = 1.0;
+                                       for (int k = 0; k < getSize(); k++) {
+                                               Couple<Integer, Integer> coor = new Couple<Integer, Integer>(
+                                                               Math.min(i, k), Math.max(i, k));
+                                               if (BP.containsKey(coor)) {
+                                                       acc -= BP.get(coor);
+                                               }
+                                       }
+                                       result.add(acc);
+                               }
+                       }
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               return result;
+       }
+
+       public void readValues(Reader r, ModeleColorMap cm) {
+               try {
+                       StreamTokenizer st = new StreamTokenizer(r);
+                       st.eolIsSignificant(true);
+                       st.wordChars('/', '/');
+                       st.parseNumbers();
+                       ArrayList<Double> vals = new ArrayList<Double>();
+                       ArrayList<Double> curVals = new ArrayList<Double>();
+                       int type = st.nextToken();
+                       boolean isDotPlot = false;
+                       if (type=='%')
+                       {
+                               vals = loadDotPlot(st);
+                               isDotPlot = true;
+                       }
+                       else
+                       {       
+                               while (type != StreamTokenizer.TT_EOF) {
+                                       switch (type) {
+                                       case (StreamTokenizer.TT_NUMBER):
+                                               curVals.add(st.nval);
+                                               break;
+                                       case (StreamTokenizer.TT_EOL):
+                                               if (curVals.size() > 0) {
+                                                       vals.add(curVals.get(curVals.size()-1));
+                                                       curVals = new ArrayList<Double>();
+                                               }
+                                               break;
+                                       }
+                                       type = st.nextToken();
+                               }
+                               if (curVals.size() > 0)
+                                       vals.add(curVals.get(curVals.size()-1));
+                       }
+
+                       Double[] v = new Double[vals.size()];
+                       for (int i = 0; i < Math.min(vals.size(), getSize()); i++) {
+                               v[i] = vals.get(i);
+                       }
+                       setColorMapValues(v, cm, true);
+                       if (isDotPlot)
+                       {
+                               cm.setMinValue(0.0);
+                               cm.setMaxValue(1.0);
+                       }
+               } catch (IOException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void setColorMapValues(Double[] values, ModeleColorMap cm,
+                       boolean rescaleColorMap) {
+               if (values.length > 0) {
+                       for (int i = 0; i < Math.min(values.length, _listeBases.size()); i++) {
+                               ModeleBase mb = _listeBases.get(i);
+                               mb.setValue(values[i]);
+                       }
+                       if (rescaleColorMap) {
+                               adaptColorMapToValues(cm);
+                       }
+               }
+       }
+
+       public Double[] getColorMapValues() {
+               Double[] values = new Double[_listeBases.size()];
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       values[i] = _listeBases.get(i).getValue();
+               }
+               return values;
+       }
+
+       public void rescaleColorMap(ModeleColorMap cm) {
+               Double max = Double.MIN_VALUE;
+               Double min = Double.MAX_VALUE;
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       Double value = _listeBases.get(i).getValue();
+                       max = Math.max(max, value);
+                       min = Math.min(min, value);
+               }
+               cm.rescale(min, max);
+       }
+
+       public void addBase(ModeleBase mb) {
+               _listeBases.add(mb);
+       }
+
+       public void setSequence(String s) {
+               setSequence(RNA.explodeSequence(s));
+       }
+
+       public void setSequence(List<String> s) {
+               int i = 0;
+               int j = 0;
+               while ((i < s.size()) && (j < _listeBases.size())) {
+                       ModeleBase mb = _listeBases.get(j);
+                       if (mb instanceof ModeleBaseNucleotide) {
+                               ((ModeleBaseNucleotide) mb).setBase(s.get(i));
+                               i++;
+                               j++;
+                       } else if (mb instanceof ModeleBasesComparison) {
+                               ((ModeleBasesComparison) mb)
+                                               .setBase1(((s.get(i).length() > 0) ? s.get(i).charAt(0)
+                                                               : ' '));
+                               ((ModeleBasesComparison) mb)
+                                               .setBase2(((s.get(i + 1).length() > 0) ? s.get(i + 1)
+                                                               .charAt(0) : ' '));
+                               i += 2;
+                               j++;
+                       } else
+                               j++;
+               }
+               for (i = _listeBases.size(); i < s.size(); i++) {
+                       _listeBases.add(new ModeleBaseNucleotide(s.get(i), i));
+               }
+       }
+
+       public void eraseSequence() {
+               int j = 0;
+               while ((j < _listeBases.size())) {
+                       ModeleBase mb = _listeBases.get(j);
+                       if (mb instanceof ModeleBaseNucleotide) {
+                               ((ModeleBaseNucleotide) mb).setBase("");
+                               j++;
+                       } else if (mb instanceof ModeleBasesComparison) {
+                               ((ModeleBasesComparison) mb).setBase1(' ');
+                               ((ModeleBasesComparison) mb).setBase2(' ');
+                               j++;
+                       } else
+                               j++;
+               }
+       }
+
+       public RNA clone() {
+               try {
+                       ByteArrayOutputStream out = new ByteArrayOutputStream();
+                       ObjectOutputStream oout = new ObjectOutputStream(out);
+                       oout.writeObject(this);
+
+                       ObjectInputStream in = new ObjectInputStream(
+                                       new ByteArrayInputStream(out.toByteArray()));
+                       return (RNA) in.readObject();
+               } catch (Exception e) {
+                       throw new RuntimeException("cannot clone class ["
+                                       + this.getClass().getName() + "] via serialization: "
+                                       + e.toString());
+               }
+       }
+
+       /**
+        * Returns the base at index <code>index</code>. Indices are contiguous in
+        * the sequence over an interval <code>[0,this.getSize()-1]</code>, where
+        * <code>n</code> is the length of the sequence.
+        * 
+        * @param index
+        *            The index, <code>0 &le; index < this.getSize()</code>, of the
+        *            base model
+        * @return The base model of index <code>index</code>
+        */
+       public ModeleBase getBaseAt(int index) {
+               return this._listeBases.get(index);
+       }
+
+       /**
+        * Returns the set of bases of indices in <code>indices</code>. Indices are
+        * contiguous in the sequence, and belong to an interval
+        * <code>[0,n-1]</code>, where <code>n</code> is the length of the sequence.
+        * 
+        * @param indices
+        *            A Collection of indices <code>i</code>,
+        *            <code>0 &le; index < this.getSize()</code>, where some base
+        *            models are found.
+        * @return A list of base model of indices in <code>indices</code>
+        */
+       public ArrayList<ModeleBase> getBasesAt(
+                       Collection<? extends Integer> indices) {
+               ArrayList<ModeleBase> mbs = new ArrayList<ModeleBase>();
+               for (int i : indices) {
+                       mbs.add(getBaseAt(i));
+               }
+               return mbs;
+       }
+
+       public ArrayList<ModeleBase> getBasesBetween(int from, int to) {
+               ArrayList<ModeleBase> mbs = new ArrayList<ModeleBase>();
+               int bck = Math.min(from, to);
+               to = Math.max(from, to);
+               from = bck;
+               for (int i = from; i <= to; i++) {
+                       mbs.add(getBaseAt(i));
+               }
+               return mbs;
+       }
+
+       public void addHighlightRegion(HighlightRegionAnnotation n) {
+               _listeRegionHighlights.add(n);
+       }
+
+       public void removeHighlightRegion(HighlightRegionAnnotation n) {
+               _listeRegionHighlights.remove(n);
+       }
+
+       public void removeChemProbAnnotation(ChemProbAnnotation a) {
+               _chemProbAnnotations.remove(a);
+       }
+
+       public void clearChemProbAnnotations() {
+               _chemProbAnnotations.clear();
+       }
+
+       public void addHighlightRegion(int from, int to, Color fill, Color outline,
+                       double radius) {
+               _listeRegionHighlights.add(new HighlightRegionAnnotation(
+                               getBasesBetween(from, to), fill, outline, radius));
+       }
+
+       public void addHighlightRegion(int from, int to) {
+               _listeRegionHighlights.add(new HighlightRegionAnnotation(
+                               getBasesBetween(from, to)));
+       }
+
+       public ArrayList<HighlightRegionAnnotation> getHighlightRegion() {
+               return _listeRegionHighlights;
+       }
+
+       /**
+        * Rotates the RNA coordinates by a certain angle
+        * 
+        * @param angleDegres
+        *            Rotation angle, in degrees
+        */
+       public void globalRotation(Double angleDegres) {
+               if (_listeBases.size() > 0) {
+
+                       // angle en radian
+                       Double angle = angleDegres * Math.PI / 180;
+
+                       // initialisation du minimum et dumaximum
+                       Double maxX = _listeBases.get(0).getCoords().x;
+                       Double maxY = _listeBases.get(0).getCoords().y;
+                       Double minX = _listeBases.get(0).getCoords().x;
+                       Double minY = _listeBases.get(0).getCoords().y;
+                       // mise a jour du minimum et du maximum
+                       for (int i = 0; i < _listeBases.size(); i++) {
+                               if (_listeBases.get(i).getCoords().getX() < minX)
+                                       minX = _listeBases.get(i).getCoords().getX();
+                               if (_listeBases.get(i).getCoords().getY() < minY)
+                                       minY = _listeBases.get(i).getCoords().getY();
+                               if (_listeBases.get(i).getCoords().getX() > maxX)
+                                       maxX = _listeBases.get(i).getCoords().getX();
+                               if (_listeBases.get(i).getCoords().getX() > maxY)
+                                       maxY = _listeBases.get(i).getCoords().getY();
+                       }
+                       // creation du point central
+                       Point2D.Double centre = new Point2D.Double((maxX - minX) / 2,
+                                       (maxY - minY) / 2);
+                       Double x, y;
+                       for (int i = 0; i < _listeBases.size(); i++) {
+                               // application de la rotation au centre de chaque base
+                               // x' = cos(theta)*(x-xc) - sin(theta)*(y-yc) + xc
+                               x = Math.cos(angle)
+                                               * (_listeBases.get(i).getCenter().getX() - centre.x)
+                                               - Math.sin(angle)
+                                               * (_listeBases.get(i).getCenter().getY() - centre.y)
+                                               + centre.x;
+                               // y' = sin(theta)*(x-xc) + cos(theta)*(y-yc) + yc
+                               y = Math.sin(angle)
+                                               * (_listeBases.get(i).getCenter().getX() - centre.x)
+                                               + Math.cos(angle)
+                                               * (_listeBases.get(i).getCenter().getY() - centre.y)
+                                               + centre.y;
+                               _listeBases.get(i).setCenter(new Point2D.Double(x, y));
+
+                               // application de la rotation au coordonnees de chaque
+                               // base
+                               // x' = cos(theta)*(x-xc) - sin(theta)*(y-yc) + xc
+                               x = Math.cos(angle)
+                                               * (_listeBases.get(i).getCoords().getX() - centre.x)
+                                               - Math.sin(angle)
+                                               * (_listeBases.get(i).getCoords().getY() - centre.y)
+                                               + centre.x;
+                               // y' = sin(theta)*(x-xc) + cos(theta)*(y-yc) + yc
+                               y = Math.sin(angle)
+                                               * (_listeBases.get(i).getCoords().getX() - centre.x)
+                                               + Math.cos(angle)
+                                               * (_listeBases.get(i).getCoords().getY() - centre.y)
+                                               + centre.y;
+                               _listeBases.get(i).setCoords(new Point2D.Double(x, y));
+                       }
+               }
+       }
+       
+       private static double MIN_DISTANCE = 10.;
+       
+       
+       /**
+        * Flip an helix around its supporting base
+        */
+       public void flipHelix(Point h) {
+               if (h.x!=-1 && h.y!=-1 && h.x!=h.y)
+               {
+                       int hBeg=h.x;
+                       int hEnd=h.y;
+                       Point2D.Double A = getCoords(hBeg);
+                       Point2D.Double B = getCoords(hEnd);
+                       Point2D.Double AB = new Point2D.Double(B.x - A.x, B.y - A.y);
+                       double normAB = Math.sqrt(AB.x * AB.x + AB.y * AB.y);
+                       // Creating a coordinate system centered on A and having
+                       // unit x-vector Ox.
+                       Point2D.Double O = A;
+                       Point2D.Double Ox = new Point2D.Double(AB.x / normAB, AB.y / normAB);
+                       Hashtable<Integer,Point2D.Double> old = new Hashtable<Integer,Point2D.Double>(); 
+                       for (int i = hBeg + 1; i < hEnd; i++) {
+                               Point2D.Double P = getCoords(i);
+                               Point2D.Double nP = project(O, Ox, P);
+                               old.put(i, nP);
+                               setCoord(i, nP);
+                               Point2D.Double Center = getCenter(i);
+                               setCenter(i, project(O, Ox, Center));
+                       }
+               }
+       }
+
+       public static Point2D.Double project(Point2D.Double O, Point2D.Double Ox,
+                       Point2D.Double C) {
+               Point2D.Double OC = new Point2D.Double(C.x - O.x, C.y - O.y);
+               // Projection of OC on OI => OX
+               double normOX = (Ox.x * OC.x + Ox.y * OC.y);
+               Point2D.Double OX = new Point2D.Double((normOX * Ox.x), (normOX * Ox.y));
+               // Portion of OC orthogonal to Ox => XC
+               Point2D.Double XC = new Point2D.Double(OC.x - OX.x, OC.y - OX.y);
+               // Reflexive image of C with respect to Ox => CP
+               Point2D.Double OCP = new Point2D.Double(OX.x - XC.x, OX.y - XC.y);
+               Point2D.Double CP = new Point2D.Double(O.x + OCP.x, O.y + OCP.y);
+               return CP;
+       }
+
+
+       public boolean testDirectionality(int i, int j, int k) {
+
+               // Which direction are we heading toward?
+               Point2D.Double pi = getCoords(i);
+               Point2D.Double pj = getCoords(j);
+               Point2D.Double pk = getCoords(k);
+               return testDirectionality(pi, pj, pk);
+       }
+
+       public static boolean testDirectionality(Point2D.Double pi,
+                       Point2D.Double pj, Point2D.Double pk) {
+
+               // Which direction are we heading toward?
+               double test = (pj.x - pi.x) * (pk.y - pj.y) - (pj.y - pi.y)
+                               * (pk.x - pj.x);
+               return test < 0.0;
+       }
+
+       public double getOrientation() {
+               double maxDist = Double.MIN_VALUE;
+               double angle = 0;
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       ModeleBase b1 = _listeBases.get(i);
+                       for (int j = i + 1; j < _listeBases.size(); j++) {
+                               ModeleBase b2 = _listeBases.get(j);
+                               Point2D.Double p1 = b1._coords.toPoint2D();
+                               Point2D.Double p2 = b2._coords.toPoint2D();
+                               double dist = p1.distance(p2);
+                               if (dist > maxDist) {
+                                       maxDist = dist;
+                                       angle = computeAngle(p1, p2);
+                               }
+                       }
+               }
+               return angle;
+       }
+
+       public boolean hasVirtualLoops() {
+               boolean consecutiveBPs = false;
+               for (int i = 0; i < _listeBases.size(); i++) {
+                       int j = _listeBases.get(i).getElementStructure();
+                       if (j == i + 1) {
+                               consecutiveBPs = true;
+                       }
+
+               }
+               return ((_drawMode != DRAW_MODE_LINEAR)
+                               && (_drawMode != DRAW_MODE_CIRCULAR) && (consecutiveBPs));
+       }
+
+       public String getHTMLDescription() {
+               String result = "<table>";
+               result += "<tr><td><b>Name:</b></td><td>" + this._name + "</td></tr>";
+               result += "<tr><td><b>Length:</b></td><td>" + this.getSize()
+                               + " nts</td></tr>";
+               result += "<tr><td><b>Base-pairs:</b></td><td>"
+                               + this.getAllBPs().size() + " </td></tr>";
+               return result + "</table>";
+       }
+
+       public String getID() {
+               return _id;
+       }
+
+       public void setID(String id) {
+               _id = id;
+       }
+
+       public static ArrayList<Integer> getGapPositions(String gapString) {
+               ArrayList<Integer> result = new ArrayList<Integer>();
+               for (int i = 0; i < gapString.length(); i++) {
+                       char c = gapString.charAt(i);
+                       if (c == '.' || c == ':') {
+                               result.add(i);
+                       }
+               }
+               return result;
+       }
+
+       public RNA restrictTo(String gapString) {
+               return restrictTo(getGapPositions(gapString));
+       }
+
+       public RNA restrictTo(ArrayList<Integer> positions) {
+               RNA result = new RNA();
+               String oldSeq = this.getSeq();
+               String newSeq = "";
+               HashSet<Integer> removedPos = new HashSet<Integer>(positions);
+               int[] matching = new int[oldSeq.length()];
+               int j = 0;
+               for (int i = 0; i < oldSeq.length(); i++) {
+                       matching[i] = j;
+                       if (!removedPos.contains(i)) {
+                               newSeq += oldSeq.charAt(i);
+                               j++;
+                       }
+               }
+               result.setRNA(newSeq);
+               for (ModeleBP m : getAllBPs()) {
+                       if (removedPos.contains(m.getIndex5())
+                                       || removedPos.contains(m.getIndex3())) {
+                               int i5 = matching[m.getIndex5()];
+                               int i3 = matching[m.getIndex3()];
+                               ModeleBP msbp = new ModeleBP(result.getBaseAt(i5),
+                                               result.getBaseAt(i3), m.getEdgePartner5(),
+                                               m.getEdgePartner3(), m.getStericity());
+                               result.addBP(i5, i3, msbp);
+                       }
+               }
+               return result;
+       }
+
+       public void rescale(double d) {
+               for (ModeleBase mb : _listeBases) {
+                       mb._coords.x *= d;
+                       mb._coords.y *= d;
+                       mb._center.x *= d;
+                       mb._center.y *= d;
+               }
+       }
+
+       /**
+        * Necessary for DrawRNATemplate (which is why the method is
+        * package-visible).
+        */
+       ArrayList<ModeleBase> getListeBases() {
+               return _listeBases;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/StructureTemp.java b/srcjar/fr/orsay/lri/varna/models/rna/StructureTemp.java
new file mode 100644 (file)
index 0000000..40063ba
--- /dev/null
@@ -0,0 +1,43 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class StructureTemp  implements Serializable{
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -436852923461989105L;
+       private ArrayList<ModeleStrand> _struct = new ArrayList<ModeleStrand>();
+       
+       public StructureTemp(){
+               
+       }
+       
+       public void addStrand(ModeleStrand ms){
+               this._struct.add(ms);
+               
+       }
+       
+       public int sizeStruct() {
+               return this._struct.size();
+       }
+       
+       public ModeleStrand getStrand(int a) {
+               return this._struct.get(a);
+       }
+
+       public ArrayList<ModeleStrand> getListStrands() {
+               return _struct;
+       }
+
+       public void clearListStrands() {
+               this._struct.clear();
+       }
+       
+       public boolean isEmpty() {
+               return this._struct.isEmpty();
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/VARNAPoint.java b/srcjar/fr/orsay/lri/varna/models/rna/VARNAPoint.java
new file mode 100644 (file)
index 0000000..8929642
--- /dev/null
@@ -0,0 +1,74 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Point2D.Double;
+import java.io.Serializable;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+public class VARNAPoint implements Serializable {
+
+       private static final long serialVersionUID = 8815373295131046029L;
+       
+       public double x = 0.0;
+       public double y = 0.0;
+       
+       public void toXML(TransformerHandler hd) throws SAXException
+       {
+               toXML(hd,"");
+       }       
+       
+
+       public static String XML_ELEMENT_NAME = "p";
+       public static String XML_VAR_ROLE_NAME = "r";
+       public static String XML_VAR_X_NAME = "x";
+       public static String XML_VAR_Y_NAME = "y";
+
+       public void toXML(TransformerHandler hd, String role) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               if (!role.equals(""))
+               {
+                       atts.addAttribute("","",XML_VAR_ROLE_NAME,"CDATA",""+role);
+               }
+               atts.addAttribute("","",XML_VAR_X_NAME,"CDATA",""+x);
+               atts.addAttribute("","",XML_VAR_Y_NAME,"CDATA",""+y);
+               hd.startElement("","",XML_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_ELEMENT_NAME);
+       }       
+
+       public VARNAPoint()
+       { this(0.0,0.0); }
+       
+       public VARNAPoint(double px, double py)
+    {
+       x = px; y = py;
+    }
+    public VARNAPoint(Point2D.Double p)
+    {
+       this(p.x,p.y);
+    }
+    public double getX()
+    {
+       return x;
+    }
+    
+    public double getY()
+    {
+       return y;
+    }
+    
+    public Point2D.Double toPoint2D()
+    {
+       return new Point2D.Double(x,y);
+    }
+    
+    public String toString()
+    {
+       return "("+x+","+y+")" ;
+    }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/rna/VARNASecDraw.java b/srcjar/fr/orsay/lri/varna/models/rna/VARNASecDraw.java
new file mode 100644 (file)
index 0000000..8a9e06e
--- /dev/null
@@ -0,0 +1,896 @@
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Random;
+import java.util.Stack;
+import java.util.Vector;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+public class VARNASecDraw {
+       public static VARNAPanel _vp = null;
+
+       
+       public abstract class Portion
+       {
+               public abstract ArrayList<Integer> getBaseList();
+               public abstract int getNumBases();
+               public abstract GeneralPath getOutline(RNA r);
+               
+       };
+       
+       public class UnpairedPortion extends Portion
+       {
+               int _pos;
+               int _len;
+               public UnpairedPortion(int pos, int len)
+               {
+                       _len = len;
+                       _pos = pos;
+               }
+               @Override
+               public ArrayList<Integer> getBaseList() {
+                       // TODO Auto-generated method stub
+                       return null;
+               }               
+               public String toString()
+               {
+                       return "U["+_pos+","+(_pos+_len-1)+"]";
+               }
+
+               public int getNumBases() {
+                       return _len;
+               }
+
+               
+               public GeneralPath getOutline(RNA r) {
+                       
+                       GeneralPath gp = new GeneralPath();
+                       ArrayList<ModeleBase> l = r.get_listeBases();
+                       Point2D.Double p0 = l.get(_pos).getCoords();
+                       gp.moveTo((float)p0.x, (float)p0.y);
+                       for (int i=1;i<_len;i++)
+                       {
+                               Point2D.Double p = l.get(_pos+i).getCoords();
+                               gp.lineTo((float)p.x, (float)p.y);
+                       }
+                       return gp;
+               }
+       };
+       
+       public class PairedPortion extends Portion
+       {
+               int _pos1;
+               int _pos2;
+               int _len;
+               RNATree _r;
+               
+               public PairedPortion(int pos1,int pos2, int len, RNATree r)
+               {
+                       _pos1 = pos1;
+                       _pos2 = pos2;
+                       _len = len;
+                       _r =r;
+               }
+
+               @Override
+               public ArrayList<Integer> getBaseList() {
+                       // TODO Auto-generated method stub
+                       return null;
+               }
+               
+               public String toString()
+               {
+                       return "H["+_pos1+","+(_pos1+_len-1)+"]["+(_pos2-_len+1)+","+(_pos2)+"]\n"+_r.toString();
+               }
+
+               @Override
+               public int getNumBases() {
+                       return 2*_len;
+               }
+
+               public GeneralPath getLocalOutline(RNA r) {
+                       GeneralPath gp = new GeneralPath();
+                       if (_len>0)
+                       {
+                               ArrayList<ModeleBase> l = r.get_listeBases();
+                               Point2D.Double p1 = l.get(_pos1).getCoords();
+                               Point2D.Double p2 = l.get(_pos1+_len-1).getCoords();
+                               Point2D.Double p3 = l.get(_pos2-_len+1).getCoords();
+                               Point2D.Double p4 = l.get(_pos2).getCoords();
+                               gp.moveTo((float)p1.x, (float)p1.y);
+                               gp.lineTo((float)p2.x, (float)p2.y);
+                               gp.lineTo((float)p3.x, (float)p3.y);
+                               gp.lineTo((float)p4.x, (float)p4.y);
+                       }
+                       return gp;
+               }
+
+
+               public GeneralPath getOutline(RNA r) {
+                       return getOutline(r,false);
+               }
+
+               public GeneralPath getOutline(RNA r, boolean local) {
+                       ArrayList<ModeleBase> l = r.get_listeBases();
+                       Point2D.Double p1 = l.get(_pos1).getCoords();
+                       Point2D.Double p2 = l.get(_pos1+_len-1).getCoords();
+                       Point2D.Double p3 = l.get(_pos2-_len+1).getCoords();
+                       Point2D.Double p4 = l.get(_pos2).getCoords();
+                       GeneralPath gp = new GeneralPath();
+                       gp.moveTo((float)p1.x, (float)p1.y);
+                       gp.lineTo((float)p2.x, (float)p2.y);
+                       if (!local)
+                               gp.append(_r.getOutline(r), true);
+                       gp.lineTo((float)p3.x, (float)p3.y);
+                       gp.lineTo((float)p4.x, (float)p4.y);
+                       
+                       return gp;
+                       
+               }
+       };
+       public  int   _depth = 0;
+
+       
+       public class RNATree
+       {
+               ArrayList<Portion> _portions = new ArrayList<Portion>();
+               int _numPairedPortions=0;
+               public RNATree()
+               {
+                       
+               }
+               
+               
+               public void addPortion(Portion p)
+               {
+                       _portions.add(p);
+                       if (p instanceof PairedPortion)
+                       {
+                               _numPairedPortions++;
+                       }
+               }
+
+               public int getNumPortions()
+               {
+                       return _portions.size();
+               }
+
+               public Portion getPortion(int i)
+               {
+                       return _portions.get(i);
+               }
+               
+               public String toString()
+               {
+                       String result = "";
+                       _depth++;
+                       for (int i=0;i<_portions.size();i++ )
+                       {
+                               result += String.format("%1$#" + _depth + "s", ' ');
+                               result += _portions.get(i).toString();
+                               if (i<_portions.size()-1)
+                                 result += "\n";
+                       }
+                       _depth--;
+                       return result;
+               }
+
+               public GeneralPath getOutline(RNA r) {
+                       GeneralPath result = new GeneralPath();
+                       for (int i=0;i<_portions.size();i++)
+                       {
+                               result.append(_portions.get(i).getOutline(r),true);
+                       }
+                       return result;
+               }
+       };
+       
+       
+       private void buildTree(int i, int j, RNATree parent,  RNA r) 
+       {
+               //LinkedList<BuildTreeArgs> s = new LinkedList<BuildTreeArgs>();
+               //s.add(new BuildTreeArgs(xi, xj, xparent,xr));
+               //while(s.size()!=0)
+               //{
+                       
+                       //BuildTreeArgs a = s.removeLast();
+                       if (i >= j) {
+                               parent.addPortion(new UnpairedPortion(i,j-i+1));
+                       }
+                       // BasePaired
+                       if (r.get_listeBases().get(i).getElementStructure() == j) 
+                       {
+                               int i1 = i;
+                               int j1 = j;
+                               boolean over = false;
+                               while( (i+1<r.get_listeBases().size())  &&  (j-1>=0)&&  (i+1<=j-1) && !over)
+                               {
+                                       if (r.get_listeBases().get(i).getElementStructure() != j)
+                                       { over = true; }
+                                       else
+                                       { i++;j--; }
+                               }
+                               int i2 = i;
+                               int j2 = j;
+                               RNATree t = new RNATree();
+                               if (i<j-1)
+                                 buildTree(i2,j2,t,r);
+                               PairedPortion p = new PairedPortion(i1,j1,i2-i1,t);
+                               parent.addPortion(p);
+                       } else 
+                       {
+                               int k = i;
+                               int l;
+                               int start = k;
+                               int len = 0;
+                               while (k <= j) {
+                                       l = r.get_listeBases().get(k).getElementStructure();
+                                       if (l != -1) 
+                                       {
+                                               if (len>0)
+                                               { parent.addPortion(new UnpairedPortion(start,len)); }
+                                               buildTree(k, l, parent,  r);
+                                               k = l + 1;
+                                               start = k;
+                                               len = 0;
+                                       } else {
+                                               len++;
+                                               k++;
+                                       }
+                               }
+                               if (len>0)
+                               {
+                                       parent.addPortion(new UnpairedPortion(start,len));
+                               }
+                       }
+               }
+
+       /*
+       public void drawTree(double x0, double y0, RNATree t, double dir, RNA r, double straightness)
+       {
+               boolean collision = true;
+               double x=x0;
+               double y=y0;
+               double multRadius = 1.0;
+               double initCirc = r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE;
+               for (int i=0;i<t.getNumPortions();i++ )
+               {
+                       Portion p = t.getPortion(i);
+                       if (p instanceof PairedPortion)
+                       {
+                               initCirc += (r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE);                             
+                       }
+                       else
+                       {
+                               initCirc += r.LOOP_DISTANCE*(p.getNumBases());
+                       }
+               }
+               while(collision)
+               {
+                               double totCirc = r.BASE_PAIR_DISTANCE+straightness*r.LOOP_DISTANCE;
+                               for (int i=0;i<t.getNumPortions();i++ )
+                               {
+                                       Portion p = t.getPortion(i);
+                                       if (p instanceof PairedPortion)
+                                       {
+                                               totCirc += (r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE);                              
+                                       }
+                                       else
+                                       {
+                                               double mod = 1.0;
+                                               if ((i==0) || (i==t.getNumPortions()-1))
+                                                       mod = straightness;
+                                                 totCirc += mod*r.LOOP_DISTANCE*(p.getNumBases());
+                                       }
+                               }
+                               double radius = multRadius*initCirc/(2.0*Math.PI);
+                       //radius = 2.0;
+                       x = x0+radius*Math.cos(dir+Math.PI);
+                       y = y0+radius*Math.sin(dir+Math.PI);
+                       dir += 2.0*Math.PI;
+                       double angleIncr = (2.0*Math.PI)/(totCirc);
+                       double circ = r.BASE_PAIR_DISTANCE/2.0+straightness*r.LOOP_DISTANCE;
+                       double ndir;
+                       ArrayList<GeneralPath> shapes = new ArrayList<GeneralPath>(); 
+                       for (int i=0;i<t.getNumPortions();i++ )
+                       {
+                               Portion p = t.getPortion(i);
+                               if (p instanceof PairedPortion)
+                               {
+                                       circ+=r.BASE_PAIR_DISTANCE/2.0;
+                                       ndir = dir + circ*angleIncr;
+                                       PairedPortion pp = (PairedPortion) p; 
+                                       for(int j=0;j<pp._len;j++)
+                                       {
+                                               int i1 = pp._pos1+j;
+                                               int i2 = pp._pos2-j;
+                                               double vx = Math.cos(ndir);
+                                               double vy = Math.sin(ndir);
+                                               double nx = x+((j*r.LOOP_DISTANCE+radius)*vx);
+                                               double ny = y+((j*r.LOOP_DISTANCE+radius)*vy);
+                                               r.get_listeBases().get(i1).set_coords(new Point2D.Double(nx+r.BASE_PAIR_DISTANCE*vy/ 2.0,ny-r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                               r.get_listeBases().get(i2).set_coords(new Point2D.Double(nx-r.BASE_PAIR_DISTANCE*vy/ 2.0,ny+r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                       }
+                                       double nx = x+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.cos(ndir));
+                                       double ny = y+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.sin(ndir));
+                                       drawTree(nx, ny, pp._r, ndir+Math.PI, r, straightness);
+                                       shapes.add(pp.getOutline(r));
+                                       circ += r.LOOP_DISTANCE + r.BASE_PAIR_DISTANCE/2.0;
+                               }
+                               else if (p instanceof UnpairedPortion)
+                               {
+                                       UnpairedPortion up = (UnpairedPortion) p;
+                                       double mod = 1.0;
+                                       if ((i==0) || (i==t.getNumPortions()-1))
+                                                 mod = straightness;
+                                       for(int j=0;j<up._len;j++)
+                                       {
+                                               ndir = dir + circ*angleIncr;
+                                               double vx = Math.cos(ndir);
+                                               double vy = Math.sin(ndir);
+                                               double nx = x+((radius)*vx);
+                                               double ny = y+((radius)*vy);
+                                               r.get_listeBases().get(up._pos+j).set_coords(new Point2D.Double(nx,ny));
+                                               circ += mod*r.LOOP_DISTANCE;
+                                       }
+                               }
+                               //System.out.println(dir);
+                       }
+                       circ += r.BASE_PAIR_DISTANCE/2.0;
+                       System.out.println(""+circ+"/"+totCirc);
+                       if(shapes.size()>0)
+                       {
+                               collision = false;
+                               for (int i=0;(i<shapes.size()) && !collision;i++)
+                               {       
+                                       Area a1 = new Area(shapes.get(i));
+                                       for (int j=i+1;(j<shapes.size())&& !collision;j++)
+                                       {       
+                                               Area a2 = new Area(shapes.get(j));
+                                               a1.intersect(a2);
+                                               if (!a1.isEmpty())
+                                               {
+                                                       collision = true;
+                                               }
+                                       }
+                               }
+                               if (collision)
+                               {
+                                       straightness *= 1.2;
+                                       multRadius *= 1.5;
+                               }
+                                       
+                       }
+                       else 
+                       {
+                               collision = false;
+                       }
+               }
+       }
+       */
+       
+       public int[] nextPlacement(int[] p) throws Exception
+       {
+                //System.out.println(Arrays.toString(p));
+               int i=p.length-1;
+               int prev = MAX_NUM_DIR;
+               boolean stop = false;
+               while((i>=0) && !stop)
+               {
+                       if (p[i]==prev-1)
+                       { 
+                               prev = p[i]; 
+                               i--; 
+                       }
+                       else
+                       { stop = true; }
+               }
+               if (i<0)
+                       throw new Exception("No more placement available"); 
+               p[i]++;
+               i++;
+               while(i<p.length)
+               {
+                       p[i] = p[i-1]+1;
+                       i++;
+               }
+                //System.out.println(Arrays.toString(p));              
+               return p;
+       }
+       
+       
+       public void drawTree(double x0, double y0, RNATree t, double dir, RNA r) throws Exception
+       {
+               boolean collision = true;
+               double x=x0;
+               double y=y0;
+               int numHelices = 0;
+               int nbHel = 1;
+               int nbUn = 0;
+               double totCirc = r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE;
+               for (int i=0;i<t.getNumPortions();i++ )
+               {
+                       Portion p = t.getPortion(i);
+                       if (p instanceof PairedPortion)
+                       {
+                               totCirc += (r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE);
+                               nbHel += 1;
+                       }
+                       else
+                       {
+                               totCirc += r.LOOP_DISTANCE*(p.getNumBases());
+                               nbUn += p.getNumBases()+1;
+                       }
+               }
+               double radius = r.determineRadius(nbHel, nbUn, totCirc/(2.0*Math.PI));
+
+               for (int i=0;i<t.getNumPortions();i++ )
+               {
+                       Portion p = t.getPortion(i);
+                       if (p instanceof PairedPortion)
+                       {
+                               numHelices++;                           
+                       }
+               }
+               int[] placement = new int[numHelices];
+               double inc = ((double)MAX_NUM_DIR)/((double)numHelices+1);
+               double val = inc;
+               for (int i=0;i<numHelices;i++ )
+               {
+                       placement[i] = (int)Math.round(val);
+                       val += inc;
+               }
+               System.out.println();
+               double angleIncr = 2.0*Math.PI/(double)MAX_NUM_DIR;
+               while(collision)
+               {
+                       x = x0+radius*Math.cos(dir+Math.PI);
+                       y = y0+radius*Math.sin(dir+Math.PI);
+                       ArrayList<GeneralPath> shapes = new ArrayList<GeneralPath>();
+                       int curH = 0;
+                       for (int i=0;i<t.getNumPortions();i++ )
+                       {
+                               Portion p = t.getPortion(i);
+                               if (p instanceof PairedPortion)
+                               {
+                                       double ndir = dir + placement[curH]*angleIncr;
+                                       curH++;
+                                       PairedPortion pp = (PairedPortion) p; 
+                                       for(int j=0;j<pp._len;j++)
+                                       {
+                                               int i1 = pp._pos1+j;
+                                               int i2 = pp._pos2-j;
+                                               double vx = Math.cos(ndir);
+                                               double vy = Math.sin(ndir);
+                                               double nx = x+(((j)*r.LOOP_DISTANCE+radius)*vx);
+                                               double ny = y+(((j)*r.LOOP_DISTANCE+radius)*vy);
+                                               r.get_listeBases().get(i1).setCoords(new Point2D.Double(nx+r.BASE_PAIR_DISTANCE*vy/ 2.0,ny-r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                               r.get_listeBases().get(i2).setCoords(new Point2D.Double(nx-r.BASE_PAIR_DISTANCE*vy/ 2.0,ny+r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                       }
+                                       double nx = x+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.cos(ndir));
+                                       double ny = y+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.sin(ndir));
+                                       drawTree(nx, ny, pp._r, ndir+Math.PI, r);
+                                       shapes.add(pp.getOutline(r));
+                               }
+                               else if (p instanceof UnpairedPortion)
+                               {
+                                       UnpairedPortion up = (UnpairedPortion) p;
+                                       for(int j=0;j<up._len;j++)
+                                       {
+                                               /*ndir = dir + circ*angleIncr;
+                                                       double vx = Math.cos(ndir);
+                                                       double vy = Math.sin(ndir);
+                                                       double nx = x+((radius)*vx);
+                                                       double ny = y+((radius)*vy);
+                                                       r.get_listeBases().get(up._pos+j).set_coords(new Point2D.Double(nx,ny));
+                                                       circ += mod*r.LOOP_DISTANCE;*/
+                                               r.get_listeBases().get(up._pos+j).setCoords(new Point2D.Double(x,y));
+                                       }
+                               }
+                               //System.out.println(dir);
+                       }
+                       if(shapes.size()>0)
+                       {
+                               collision = false;
+                               for (int i=0;(i<shapes.size()) && !collision;i++)
+                               {       
+                                       Area a1 = new Area(shapes.get(i));
+                                       for (int j=i+1;(j<shapes.size())&& !collision;j++)
+                                       {       
+                                               Area a2 = new Area(shapes.get(j));
+                                               a1.intersect(a2);
+                                               if (!a1.isEmpty())
+                                               {
+                                                       collision = true;
+                                               }
+                                       }
+                               }
+                               if (collision)
+                               {
+                                       placement = nextPlacement(placement);
+                               }
+                                       
+                       }
+                       else 
+                       {
+                               collision = false;
+                       }
+               }
+       }
+
+
+       private class HelixEmbedding
+       {
+               private GeneralPath _clip;
+               Point2D.Double _support;
+               ArrayList<HelixEmbedding> _children = new ArrayList<HelixEmbedding>();
+               ArrayList<Integer> _indices = new ArrayList<Integer>();
+               PairedPortion _p;
+               RNA _r;
+               HelixEmbedding _parent;
+               
+               public HelixEmbedding(Point2D.Double support, PairedPortion p, RNA r, HelixEmbedding parent)
+               {
+                       _support = support;
+                       _clip = p.getLocalOutline(r);
+                       _p = p;
+                       _r = r;
+                       _parent = parent;
+               }
+               
+               public void addHelixEmbedding(HelixEmbedding h, int index)
+               {
+                       _children.add(h);
+                       _indices.add(index);
+               }
+               
+               public GeneralPath getShape()
+               {
+                       return _clip;
+               }
+               
+               
+               public int chooseNextMove()
+               {
+                       int i = _parent._children.indexOf(this);
+                       int min;
+                       int max;
+                       if (_parent._children.size()<VARNASecDraw.MAX_NUM_DIR-1)
+                       {
+                               if (_parent._children.size()==1)
+                               { min=1;max=VARNASecDraw.MAX_NUM_DIR-1; }
+                               else 
+                               {
+                                       if (i==0)
+                                       { min = 1; }
+                                       else
+                                       { min = _parent._indices.get(i-1)+1;}
+                                       if (i==_parent._children.size()-1)
+                                       { max = VARNASecDraw.MAX_NUM_DIR-1; }
+                                       else
+                                       { max = _parent._indices.get(i+1)-1;}
+                               }
+                               int prevIndex = _parent._indices.get(i);
+                               int newIndex = min+_rnd.nextInt(max+1-min);
+                               double rot = ((double)(newIndex-prevIndex)*Math.PI*2.0)/MAX_NUM_DIR;
+                               _parent._indices.set(i, newIndex);
+                               rotate(rot);
+                               return newIndex-prevIndex;
+                       }
+                       return 0;
+               }
+               
+               public void cancelMove(int delta)
+               {
+                       int i = _parent._children.indexOf(this);
+                       int prevIndex = _parent._indices.get(i);
+                       double rot = ((double)(-delta)*Math.PI*2.0)/MAX_NUM_DIR;
+                       _parent._indices.set(i, prevIndex-delta);
+                       rotate(rot);
+               }
+               
+               public void rotate(double angle)
+               {
+                       transform(AffineTransform.getRotateInstance(angle, _support.x, _support.y));
+               }
+               
+               private void transform(AffineTransform a)
+               {
+                       _clip.transform(a);
+                       Point2D p = a.transform(_support, null);
+                       _support.setLocation(p.getX(), p.getY());
+                       for (int i=0;i<_children.size();i++)
+                       {
+                               _children.get(i).transform(a);
+                       }
+               }
+               
+               public void reflectCoordinates()
+               {
+                       ArrayList<ModeleBase> mbl = _r.get_listeBases();
+
+                       if (_p._len>0)
+                       {
+                               PathIterator pi = _clip.getPathIterator(AffineTransform.getRotateInstance(0.0));
+                               ArrayList<Point2D.Double> p = new ArrayList<Point2D.Double>(); 
+                               while(!pi.isDone())
+                               {
+                                       double[] args = new double[6];
+                                       int type= pi.currentSegment(args);
+                                       if ((type == PathIterator.SEG_MOVETO)  || (type == PathIterator.SEG_LINETO))
+                                       {
+
+                                               Point2D.Double np = new Point2D.Double(args[0],args[1]); 
+                                               p.add(np);
+                                               System.out.println(Arrays.toString(args));
+                                       }
+                                       pi.next();
+                               }
+                               if (p.size()<4)
+                               { return; }
+                               
+                               Point2D.Double startLeft = p.get(0);
+                               Point2D.Double endLeft = p.get(1);
+                               Point2D.Double endRight = p.get(2);
+                               Point2D.Double startRight = p.get(3);
+                               
+                               double d = startLeft.distance(endLeft);
+                               double vx = endLeft.x-startLeft.x;
+                               double vy = endLeft.y-startLeft.y;
+                               double interval = 0.0;
+                               if (_p._len>1)
+                               {
+                                       vx/=d;
+                                       vy/=d;
+                                       interval = d/((double)_p._len-1);
+                                       System.out.println("DELTA: "+interval+" "+_r.LOOP_DISTANCE);
+                               }
+                               for (int n=0;n<_p._len;n++)
+                               {
+                                       int i = _p._pos1 + n;
+                                       int j = _p._pos2 - n;
+                                       ModeleBase mbLeft = mbl.get(i);
+                                       mbLeft.setCoords(new Point2D.Double(startLeft.x+n*vx*interval, startLeft.y+n*vy*interval));
+                                       ModeleBase mbRight = mbl.get(j);
+                                       mbRight.setCoords(new Point2D.Double(startRight.x+n*vx*interval, startRight.y+n*vy*interval));
+                               }
+                       }
+                       for (int i=0;i<_children.size();i++)
+                       {
+                               _children.get(i).reflectCoordinates();
+                       }
+                       if (_children.size()>0)
+                       {
+                               Point2D.Double center = _children.get(0)._support;
+                               for (int i=0;i<_p._r.getNumPortions();i++)
+                               {
+                                       Portion p = _p._r.getPortion(i);
+                                       if (p instanceof UnpairedPortion)
+                                       {
+                                               UnpairedPortion up = (UnpairedPortion) p;
+                                               for (int j=0;j<up._len;j++)
+                                               {
+                                                       int n = up._pos + j;
+                                                       ModeleBase mbLeft = mbl.get(n);
+                                                       mbLeft.setCoords(center);
+                                               }
+                                       }
+                               }       
+                       }
+                       else
+                       {
+                               placeTerminalLoop(mbl,_r);
+                       }
+               }
+               
+               private void placeTerminalLoop(ArrayList<ModeleBase> mbl, RNA r)
+               {
+                       if ((_children.size()==0)&&(_p._r.getNumPortions()==1))
+                       {
+                               Portion p = _p._r.getPortion(0);
+                               if (p instanceof UnpairedPortion)
+                               {
+                                       UnpairedPortion up = (UnpairedPortion) p;
+                                       double rad = determineRadius(1,up.getNumBases(),_r);
+                                       int a = _p._pos1+_p._len-1;
+                                       int b = _p._pos2-(_p._len-1);
+                                       ModeleBase mbLeft = mbl.get(a);
+                                       ModeleBase mbRight = mbl.get(b);
+                                       Point2D.Double pl = mbLeft.getCoords();
+                                       Point2D.Double pr = mbRight.getCoords();
+                                       Point2D.Double pm = new Point2D.Double((pl.x+pr.x)/2.0,(pl.y+pr.y)/2.0);
+                                       double vx = (pl.x-pr.x)/pl.distance(pr);
+                                       double vy = (pl.y-pr.y)/pl.distance(pr);
+                                       double vnx = -vy, vny = vx;
+                                       Point2D.Double pc = new Point2D.Double(pm.x+rad*vnx,pm.y+rad*vny);
+                                       double circ = r.LOOP_DISTANCE*(1.0+up.getNumBases())+r.BASE_PAIR_DISTANCE; 
+                                       double incrLoop = Math.PI*2.0*r.LOOP_DISTANCE/circ;  
+                                       double angle = Math.PI*2.0*r.BASE_PAIR_DISTANCE/(2.0*circ);
+                                       for (int j=0;j<up._len;j++)
+                                       {
+                                               int n = up._pos + j;
+                                               ModeleBase mb = mbl.get(n);
+                                               angle += incrLoop;
+                                               double dx = -Math.cos(angle)*vnx+Math.sin(angle)*vx;
+                                               double dy = -Math.cos(angle)*vny+Math.sin(angle)*vy;
+//                                             Point2D.Double pf = new Point2D.Double(pc.x,pc.y);
+                                               Point2D.Double pf = new Point2D.Double(pc.x+rad*dx,pc.y+rad*dy);
+                                               mb.setCoords(pf);
+                                       }
+                                       
+                               }
+                       }
+               }
+               
+               
+               
+               public String toString()
+               {
+                       return "Emb.Hel.: "+_p.toString();
+               }
+       }
+       
+       public double determineRadius(int numHelices, int numUnpaired, RNA r)
+       {
+               double circ = numHelices*r.BASE_PAIR_DISTANCE+(numHelices+numUnpaired)*r.LOOP_DISTANCE;
+               return circ/(2.0*Math.PI);
+       }
+       
+       
+       public void predrawTree(double x0, double y0, RNATree t, double dir, RNA r, HelixEmbedding parent, ArrayList<HelixEmbedding> all) throws Exception
+       {
+               double x=x0;
+               double y=y0;
+               int numHelices = 0;
+               int numUBases = 0;
+               double totCirc = r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE;
+               for (int i=0;i<t.getNumPortions();i++ )
+               {
+                       Portion p = t.getPortion(i);
+                       if (p instanceof PairedPortion)
+                       {
+                               totCirc += (r.BASE_PAIR_DISTANCE+r.LOOP_DISTANCE);
+                               numHelices++;   
+                       }
+                       else
+                       {
+                                 totCirc += r.LOOP_DISTANCE*(p.getNumBases());
+                                 numUBases += p.getNumBases();
+                       }
+               }
+               double radius = determineRadius(numHelices+1,numUBases,r);
+
+               int[] placement = new int[numHelices];
+               double inc = ((double)MAX_NUM_DIR)/((double)numHelices+1);
+               double val = inc;
+               for (int i=0;i<numHelices;i++ )
+               {
+                       placement[i] = (int)Math.round(val);
+                       val += inc;
+               }
+               double angleIncr = 2.0*Math.PI/(double)MAX_NUM_DIR;
+                       x = x0+radius*Math.cos(dir+Math.PI);
+                       y = y0+radius*Math.sin(dir+Math.PI);
+                       int curH = 0;
+                       for (int i=0;i<t.getNumPortions();i++ )
+                       {
+                               Portion p = t.getPortion(i);
+                               if (p instanceof PairedPortion)
+                               {
+                                       double ndir = dir + placement[curH]*angleIncr;
+                                       PairedPortion pp = (PairedPortion) p; 
+                                       for(int j=0;j<pp._len;j++)
+                                       {
+                                               int i1 = pp._pos1+j;
+                                               int i2 = pp._pos2-j;
+                                               double vx = Math.cos(ndir);
+                                               double vy = Math.sin(ndir);
+                                               double nx = x+(((j)*r.LOOP_DISTANCE+radius)*vx);
+                                               double ny = y+(((j)*r.LOOP_DISTANCE+radius)*vy);
+                                               r.get_listeBases().get(i1).setCoords(new Point2D.Double(nx+r.BASE_PAIR_DISTANCE*vy/ 2.0,ny-r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                               r.get_listeBases().get(i2).setCoords(new Point2D.Double(nx-r.BASE_PAIR_DISTANCE*vy/ 2.0,ny+r.BASE_PAIR_DISTANCE*vx/ 2.0));
+                                       }
+                                       double nx = x+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.cos(ndir));
+                                       double ny = y+(((pp._len-1)*r.LOOP_DISTANCE+radius)*Math.sin(ndir));
+                                       HelixEmbedding nh = new HelixEmbedding(new Point2D.Double(x,y),pp,r,parent);
+                                       parent.addHelixEmbedding(nh,placement[curH]);
+                                       all.add(nh);
+                                       predrawTree(nx, ny, pp._r, ndir+Math.PI, r, nh, all);
+                                       curH++;
+                               }
+                               else if (p instanceof UnpairedPortion)
+                               {
+                                       UnpairedPortion up = (UnpairedPortion) p;
+                                       for(int j=0;j<up._len;j++)
+                                       {
+                                               r.get_listeBases().get(up._pos+j).setCoords(new Point2D.Double(x,y));
+                                       }
+                               }
+                               //System.out.println(dir);
+                       }
+       }
+       
+       
+       public static Random _rnd = new Random();
+       
+       private static int MAX_NUM_DIR = 8;
+
+       public RNATree drawRNA(double dirAngle, RNA r) {
+               RNATree t = new RNATree();
+               buildTree(0, r.get_listeBases().size() - 1, t,  r );
+               System.out.println(t);
+               ArrayList<HelixEmbedding> all = new ArrayList<HelixEmbedding>();
+               HelixEmbedding root = null;
+               try {
+                       root = new HelixEmbedding(new Point2D.Double(0.0,0.0),new PairedPortion(0,0,0,t),r,null); 
+                       predrawTree(0,0,t,0.0,r,root,all);
+                       int steps=1000;
+                       double prevbadness = Double.MAX_VALUE;
+                       while((steps>0)&&(prevbadness>0))
+                       {
+
+                               // Generating new structure
+                               HelixEmbedding chosen = all.get(_rnd.nextInt(all.size()));
+                               int delta =  chosen.chooseNextMove();
+                               // Draw current
+                               if (_vp!=null)
+                                       { 
+                                               GeneralPath p = new GeneralPath();
+                                               for (int i=0;i<all.size();i++)
+                                               { p.append(all.get(i).getShape(),false); }
+                                               r._debugShape = p;
+                                               _vp.paintImmediately(0, 0, _vp.getWidth(), _vp.getHeight());    
+                                       }                               
+
+                               //Evaluating solution
+                               double badness = 0.0;
+                               for (int i=0;i<all.size();i++)
+                               { 
+                                       Shape s1 = all.get(i).getShape();
+                                       for (int j=i+1;j<all.size();j++)
+                                       { 
+                                               Shape s2 = all.get(j).getShape();
+                                               Area a = new Area(s1);
+                                               a.intersect(new Area(s2));
+                                               if (!a.isEmpty())
+                                               {
+                                                       badness ++;
+                                               }
+                                       }
+                               }
+
+                               if (badness-prevbadness>0)
+                               {
+                                       chosen.cancelMove(delta);
+                               }
+                               else
+                               {
+                                       prevbadness = badness;
+                               }
+
+                               System.out.println(badness);
+
+                               steps--;
+                       }
+                       if (root!=null)
+                       { root.reflectCoordinates(); }
+               } catch (Exception e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               return t;
+       };
+       
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmark.java b/srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmark.java
new file mode 100644 (file)
index 0000000..06362ca
--- /dev/null
@@ -0,0 +1,236 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.templates;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.ExceptionXmlLoading;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+public class BatchBenchmark {
+       private VARNAConfig conf = new VARNAConfig();
+       
+       final boolean DEFAULT_STRAIGHT_BULGES = false;
+       
+       public static RNA loadRNA(File file) throws ExceptionFileFormatOrSyntax, ExceptionUnmatchedClosingParentheses, FileNotFoundException, ExceptionExportFailed, ExceptionPermissionDenied, ExceptionLoadingFailed {
+               Collection<RNA> rnas = RNAFactory.loadSecStr(file.getPath());
+               if (rnas.isEmpty()) {
+                       throw new ExceptionFileFormatOrSyntax(
+                                       "No RNA could be parsed from that source.");
+               }
+               return rnas.iterator().next();
+       }
+       
+       public void benchmarkRNA(File templatePath, File rnaPath, BufferedWriter outbuf) throws ExceptionXmlLoading, RNATemplateDrawingAlgorithmException, ExceptionFileFormatOrSyntax, ExceptionUnmatchedClosingParentheses, ExceptionExportFailed, ExceptionPermissionDenied, ExceptionLoadingFailed, ExceptionNAViewAlgorithm, IOException {
+               // load template
+               RNATemplate template = RNATemplate.fromXMLFile(templatePath);
+               
+               // load RNA
+               RNA rna = loadRNA(rnaPath);
+               
+               for (int algo=0; algo<=100; algo++) {
+                       String algoname = "";
+               
+                       // draw RNA
+                       switch (algo) {
+                       //case 0:
+                       //      rna.drawRNALine(conf);
+                       //      algoname = "Linear";
+                       //      break;
+                       //case 1:
+                       //      rna.drawRNACircle(conf);
+                       //      algoname = "Circular";
+                       //      break;
+                       case 2:
+                               rna.drawRNARadiate(conf);
+                               algoname = "Radiate";
+                               break;
+                       case 3:
+                               rna.drawRNANAView(conf);
+                               algoname = "NAView";
+                               break;
+                       case 10:
+                               algoname = "Template/noadj";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.NOADJUST, DrawRNATemplateCurveMethod.EXACTLY_AS_IN_TEMPLATE, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       case 11:
+                               algoname = "Template/noadj/ellipses";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.NOADJUST, DrawRNATemplateCurveMethod.ALWAYS_REPLACE_BY_ELLIPSES, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       case 12:
+                               algoname = "Template/noadj/smart";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.NOADJUST, DrawRNATemplateCurveMethod.SMART, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                               /*
+                       case 5:
+                               algoname = "Template/maxfactor";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.MAXSCALINGFACTOR, DrawRNATemplateCurveMethod.EXACTLY_AS_IN_TEMPLATE, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                               */
+                       case 6:
+                               algoname = "Template/mininter";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.NOINTERSECT, DrawRNATemplateCurveMethod.EXACTLY_AS_IN_TEMPLATE, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       case 30:
+                               algoname = "Template/translate";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.HELIXTRANSLATE, DrawRNATemplateCurveMethod.EXACTLY_AS_IN_TEMPLATE, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       case 31:
+                               algoname = "Template/translate/ellipses";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.HELIXTRANSLATE, DrawRNATemplateCurveMethod.ALWAYS_REPLACE_BY_ELLIPSES, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       case 32:
+                               algoname = "Template/translate/smart";
+                               rna.drawRNATemplate(template, conf, DrawRNATemplateMethod.HELIXTRANSLATE, DrawRNATemplateCurveMethod.SMART, DEFAULT_STRAIGHT_BULGES);
+                               break;
+                       default:
+                               continue;
+                       }
+               
+                       // benchmark
+                       Benchmark benchmark = new Benchmark(rna);
+                       
+                       // print results
+                       outbuf.write(
+                                       removeExt(rnaPath.getName())
+                                       + "\t" + algoname
+                                       + "\t" + benchmark.backboneCrossings
+                                       // averageUnpairedDistance % -> best is 100
+                                       + "\t" + (benchmark.averageUnpairedDistance / benchmark.targetConsecutiveBaseDistance *100)
+                                       + "\t" + benchmark.tooNearConsecutiveBases
+                                       + "\t" + benchmark.tooFarConsecutiveBases
+                                       + "\n");
+               }
+               
+       }
+       
+       public void runBenchmark(List<File> templates, List<File> rnas, File outfile) throws Exception {
+               if (templates.size() != rnas.size()) {
+                       throw new Error("templates and rnas list size differ");
+               }
+               
+               BufferedWriter outbuf = new BufferedWriter(new FileWriter(outfile));
+               
+               outbuf.write("RNA\tAlgorithm\tBackbone crossings\tAverage unpaired distance %\tToo near\tToo far\n");
+               
+               for (int i=0; i<templates.size(); i++) {
+                       System.out.println("Benchmarking for RNA " + removeExt(rnas.get(i).getName()));
+                       benchmarkRNA(templates.get(i), rnas.get(i), outbuf);
+               }
+               
+               outbuf.close();
+               
+               System.out.println("******* Benchmark finished. *******");
+       }
+       
+       public void runExamples() throws Exception {
+               File templatesDir = new File("templates");
+               File root = new File(templatesDir, "examples");
+               File outfile = new File(new File(templatesDir, "benchmark"), "benchmark.txt");
+               
+               String seqlist[] = {"RNase P E Coli.ct", "RNase P Synechocystis-PCC6803.ct", "RNase P M Musculus.ct"};
+               
+               List<File> templates = new ArrayList<File>();
+               List<File> rnas = new ArrayList<File>();
+               
+               for (String seq: seqlist) {
+                       templates.add(new File(root, "RNase P E Coli.xml"));
+                       rnas.add(new File(root, seq));
+               }
+               
+               runBenchmark(templates, rnas, outfile);
+       }
+       
+       public static void readFASTA(File file, List<String> seqnames, List<String> sequences) throws IOException {
+               BufferedReader buf = new BufferedReader(new FileReader(file));
+               String line = buf.readLine();
+               while (line != null) {
+                       if (line.length() != 0) {
+                               if (line.charAt(0) == '>') {
+                                       String id = line.substring(1); // remove the >
+                                       seqnames.add(id);
+                                       sequences.add("");
+                               } else {
+                                       sequences.set(sequences.size()-1, sequences.get(sequences.size()-1) + line);
+                               }
+                       }
+                       line = buf.readLine();
+               }
+               buf.close();
+       }
+       
+       
+       /**
+        * We assume given directory contains a alignemnt.fasta file,
+        * of which the first sequence is the consensus structure,
+        * and the other sequences are aligned nucleotides. 
+        * The principle is to convert it to a set of secondary structure,
+        * using the following rule:
+        * - keep the same nucleotides as in original sequence
+        * - keep base pairs where both bases of the pair are non-gaps in our sequence
+        */
+       public void benchmarkAllDir(File rootdir) throws Exception {
+               File seqdir = new File(rootdir, "sequences");
+               File templateFile = new File(rootdir, "template.xml");
+               File sequenceFiles[] = seqdir.listFiles();
+               Arrays.sort(sequenceFiles);
+               
+               List<File> templates = new ArrayList<File>();
+               List<File> rnas = new ArrayList<File>();
+               for (File seq: sequenceFiles) {
+                       if (!seq.getPath().endsWith(".dbn")) continue;
+                       rnas.add(seq);
+                       templates.add(templateFile);
+               }
+               
+               File outfile = new File(rootdir, "benchmark.txt");
+               runBenchmark(templates, rnas, outfile);
+               
+       }
+       
+       
+       public static void main(String[] args) throws Exception {
+               File templatesDir = new File("templates");
+               if (args.length < 1) {
+                       System.out.println("Command-line argument required: RNA");
+                       System.out.println("Example: RNaseP_bact_a");
+                       System.exit(1);
+               }
+               //new BatchBenchmark().runExamples();
+               for (String arg: args) {
+                       new BatchBenchmark().benchmarkAllDir(new File(templatesDir, arg));
+               }
+       }
+       
+       /**
+        * Return the given file path without the (last) extension. 
+        */
+       public static String removeExt(String path) {
+               return path.substring(0, path.lastIndexOf('.'));
+       }
+       
+       public static File removeExt(File path) {
+               return new File(removeExt(path.getPath()));
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmarkPrepare.java b/srcjar/fr/orsay/lri/varna/models/templates/BatchBenchmarkPrepare.java
new file mode 100644 (file)
index 0000000..c3ab8ac
--- /dev/null
@@ -0,0 +1,98 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.templates;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import fr.orsay.lri.varna.factories.RNAFactory;
+
+public class BatchBenchmarkPrepare {
+
+       /**
+        * We assume given directory contains a alignemnt.fasta file,
+        * of which the first sequence is the consensus structure,
+        * and the other sequences are aligned nucleotides. 
+        * The principle is to convert it to a set of secondary structure,
+        * using the following rule:
+        * - keep the same nucleotides as in original sequence
+        * - keep base pairs where both bases of the pair are non-gaps in our sequence
+        */
+       public void benchmarkAllDir(File rootdir) throws Exception {
+               File seqdir = new File(rootdir, "sequences");
+               if (!seqdir.exists()) {
+                       seqdir.mkdir();
+               }
+               
+               File templateFile = new File(rootdir, "template.xml");
+               
+               ArrayList<String> seqnames = new ArrayList<String>();
+               ArrayList<String> sequences = new ArrayList<String>();
+               BatchBenchmark.readFASTA(new File(rootdir, "alignment.fasta"), seqnames, sequences);
+               
+               BufferedWriter outbufASS = new BufferedWriter(new FileWriter(new File(rootdir, "all_secondary_structures.fasta")));
+               
+               String consensusSecStr = sequences.get(0);
+               int[] consensusSecStrInt = RNAFactory.parseSecStr(consensusSecStr);
+               
+               List<File> templates = new ArrayList<File>();
+               for (int i=1; i<seqnames.size(); i++) {
+                       String seqname = seqnames.get(i);
+                       String sequence = sequences.get(i);
+                       String sequenceUngapped = sequence.replaceAll("[\\.-]", "");
+                       System.out.println(seqname);
+                       String ss = "";
+                       String nt = "";
+                       for (int j=0; j<sequence.length(); j++) {
+                               if (sequence.charAt(j) != '.' && sequence.charAt(j) != '-') {
+                                       if (consensusSecStr.charAt(j) == '-' || consensusSecStr.charAt(j) == '.') {
+                                               nt += sequence.charAt(j);
+                                               ss += '.';
+                                       } else {
+                                               int k = consensusSecStrInt[j];
+                                               // k is the matching base, is it aligned to a base in our sequence?
+                                               if (sequence.charAt(k) != '.' && sequence.charAt(k) != '-') {
+                                                       nt += sequence.charAt(j);
+                                                       ss += consensusSecStr.charAt(j);
+                                               } else {
+                                                       nt += sequence.charAt(j);
+                                                       ss += '.';
+                                               }
+                                       }
+                               }
+                       }
+                       
+                       if (!sequenceUngapped.equals(nt)) {
+                               System.out.println(sequenceUngapped);
+                               System.out.println(nt);
+                               throw new Error("bug");
+                       }
+                       
+                       // We now have the sequence with its secondary structure.
+                       File outfile = new File(seqdir, seqname + ".dbn");
+                       BufferedWriter outbuf = new BufferedWriter(new FileWriter(outfile));
+                       outbuf.write(">" + seqname + "\n");
+                       outbuf.write(nt + "\n");
+                       outbuf.write(ss + "\n");
+                       outbuf.close();
+                       
+                       outbufASS.write(">" + seqname + "\n");
+                       outbufASS.write(ss + "\n");
+                       
+                       templates.add(templateFile);
+               }
+               
+               outbufASS.close();
+               
+       }
+       
+       public static void main(String[] args) throws Exception {
+               new BatchBenchmarkPrepare().benchmarkAllDir(new File(new File("templates"), "RNaseP_bact_a"));
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/Benchmark.java b/srcjar/fr/orsay/lri/varna/models/templates/Benchmark.java
new file mode 100644 (file)
index 0000000..b3db1fd
--- /dev/null
@@ -0,0 +1,120 @@
+package fr.orsay.lri.varna.models.templates;
+
+import java.awt.geom.Line2D;
+import java.util.Arrays;
+
+import fr.orsay.lri.varna.models.geom.LinesIntersect;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+/**
+ * @author Raphael Champeimont
+ *
+ */
+public class Benchmark {
+       private RNA rna;
+       
+       // number of backbone crossings
+       public int backboneCrossings;
+       // average distance between unpaired consecutive bases
+       public double averageUnpairedDistance;
+       // median distance between consecutive bases
+       public double medianConsecutiveBaseDistance;
+       // Number of consecutive bases that are too near from each other
+       public int tooNearConsecutiveBases;
+       // Number of consecutive bases that are too far from each other
+       public int tooFarConsecutiveBases;
+       
+       
+       
+       // Parameters
+       public double targetConsecutiveBaseDistance = RNA.LOOP_DISTANCE;
+       // If consecutive bases are nearer that tooNearFactor * target distance, we call them too near.
+       public double tooNearFactor = 0.5;
+       // If consecutive bases are further that tooFarFactor * target distance, we call them too far.
+       public double tooFarFactor = 2;
+       
+       
+       
+       public Benchmark(RNA rna) {
+               this.rna = rna;
+               computeAll();
+       }
+       
+       private void computeAll() {
+               // Compute number of backbone crossings
+               {
+                       int n = rna.getSize();
+                       Line2D.Double[] lines = new Line2D.Double[n-1];
+                       for (int i=0; i<n-1; i++) {
+                               lines[i] = new Line2D.Double(rna.getCoords(i), rna.getCoords(i+1));
+                       }
+                       int intersectLines = 0;
+                       for (int i=0; i<n-1; i++) {
+                               for (int j=i+2; j<n-1; j++) {
+                                       if (LinesIntersect.linesIntersect(lines[i], lines[j])) {
+                                               intersectLines++;
+                                       }
+                               }
+                       }
+                       backboneCrossings = intersectLines;
+               }
+               
+               // Stats about distances between consecutive bases not both part of an helix
+               {
+                       int n = rna.getSize();
+                       double sum = 0;
+                       int count = 0;
+                       for (int i=0; i<n-1; i++) {
+                               int indexOfAssociatedBase1 = rna.getBaseAt(i).getElementStructure();
+                               int indexOfAssociatedBase2 = rna.getBaseAt(i+1).getElementStructure();
+                               if (indexOfAssociatedBase1 != -1 || indexOfAssociatedBase2 != -1) {
+                                       // If they are not both associated (ie. not part of an helix)
+                                       sum += rna.getBaseAt(i).getCoords().distance(rna.getBaseAt(i+1).getCoords());
+                                       count++;
+                               }
+                       }
+                       averageUnpairedDistance = sum / count;
+               }
+               
+               // Stats about base distances
+               {
+                       int n = rna.getSize();
+                       double distances[] = new double[n-1];
+                       for (int i=0; i<n-1; i++) {
+                               // If they are not both associated (ie. not part of an helix)
+                               distances[i] = rna.getBaseAt(i).getCoords().distance(rna.getBaseAt(i+1).getCoords());
+                       }
+                       Arrays.sort(distances);
+                       double median = distances[distances.length/2];
+                       medianConsecutiveBaseDistance = median;
+                       
+                       // We take the median as target distance, and count
+                       // the number of consecutive bases with a distance too different
+                       tooNearConsecutiveBases = 0;
+                       tooFarConsecutiveBases = 0;
+                       for (int i=0; i<n-1; i++) {
+                               if (distances[i] < tooNearFactor*targetConsecutiveBaseDistance) {
+                                       tooNearConsecutiveBases++;
+                               }
+                               if (distances[i] > tooFarFactor*targetConsecutiveBaseDistance) {
+                                       tooFarConsecutiveBases++;
+                               }
+                       }
+               }
+       }
+       
+       @SuppressWarnings("unused")
+       private int percent(int a, int b) {
+               return (int) Math.round((double) a / (double) b * 100.0);
+       }
+
+       public void printAll() {
+               System.out.println("Benchmark:");
+               System.out.println("\tBackbone crossings = " + backboneCrossings);
+               System.out.println("\tAverage unpaired distance = " + averageUnpairedDistance);
+               System.out.println("\tMedian of consecutive base distance = " + medianConsecutiveBaseDistance);
+               System.out.println("\tNumber of too near consecutive bases = " + tooNearConsecutiveBases); // + " ie. " + percent(tooNearConsecutiveBases, rna.getSize()-1) + " %");
+               System.out.println("\tNumber of too far consecutive bases = " + tooFarConsecutiveBases); // + " ie. " + percent(tooFarConsecutiveBases, rna.getSize()-1) + " %");
+       }
+}
+
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateCurveMethod.java b/srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateCurveMethod.java
new file mode 100644 (file)
index 0000000..b1796f7
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.templates;
+
+/**
+ * @author Raphael Champeimont
+ * How should we draw unpaired regions?
+ */
+public enum DrawRNATemplateCurveMethod {
+        // use what is in the template
+       EXACTLY_AS_IN_TEMPLATE("Bezier"),
+       // use ellipses, ignoring Bezier parameters from template (useful mostly for debugging ellipses)
+       ALWAYS_REPLACE_BY_ELLIPSES("Ellipses"),
+       // replace by ellipse where it is a good idea
+       SMART("Auto-Select");
+
+       String _msg;
+       
+       private DrawRNATemplateCurveMethod(String msg){
+               _msg=msg;
+       }
+       
+       public String toString()
+       {
+               return _msg;
+       }
+       
+       public static DrawRNATemplateCurveMethod getDefault() {
+               return SMART;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateMethod.java b/srcjar/fr/orsay/lri/varna/models/templates/DrawRNATemplateMethod.java
new file mode 100644 (file)
index 0000000..9fd5f67
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.templates;
+
+/**
+ * What to do in case some helices are of a different length
+ * in the template and the actual helix.
+ * Possibles values are:
+ * 0 - No adjustment is done.
+ *     A longer than expected helix might bump into an other helix.
+ * 1 - Scaling factors (actual length / template length) are calculated,
+ *     the maximum scaling factor L is extracted, then all helix positions
+ *     are multiplied by L. 
+ * 2 - Same as 1, but L is computed as the minimum value such that there
+ *     are no backbone intersections.
+ */
+public enum DrawRNATemplateMethod {
+       NOADJUST("No Adjust"), MAXSCALINGFACTOR("Max Scaling"), NOINTERSECT("Non-crossing"), HELIXTRANSLATE("Helix Translation");
+       
+       String _msg;
+       
+       public static DrawRNATemplateMethod getDefault() {
+               return HELIXTRANSLATE;
+       }
+       
+
+       public String toString()
+       {
+               return _msg;
+       }
+       
+       private DrawRNATemplateMethod(String msg)
+       {
+               _msg = msg;
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValue2TemplateDistance.java b/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValue2TemplateDistance.java
new file mode 100644 (file)
index 0000000..51e8b46
--- /dev/null
@@ -0,0 +1,93 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.models.treealign.RNANodeValue;
+import fr.orsay.lri.varna.models.treealign.RNANodeValue2;
+import fr.orsay.lri.varna.models.treealign.TreeAlignLabelDistanceAsymmetric;
+
+/**
+ * Distance between an RNANodeValue2 and an RNANodeValueTemplate.
+ * 
+ * @author Raphael Champeimont
+ *
+ */
+public class RNANodeValue2TemplateDistance implements TreeAlignLabelDistanceAsymmetric<RNANodeValue2,RNANodeValueTemplate> {
+       public double delete(RNANodeValue2 v) {
+               if (v == null) {
+                       // deleting nothing costs nothing
+                       return 0;
+               } else {
+                       if (v.isSingleNode()) {
+                               if (v.getNode().getRightBasePosition() < 0) {
+                                       // delete one base (that comes from a broken base pair)
+                                       return 1;
+                               } else {
+                                       // delete a base pair
+                                       return 2;
+                               }
+                       } else {
+                               // delete a sequence
+                               return v.getNodes().size();
+                       }
+               }
+       }
+       
+       public double insert(RNANodeValueTemplate v) {
+               if (v == null) {
+                       // inserting nothing costs nothing
+                       return 0;
+               } else {
+                       if (v instanceof RNANodeValueTemplateSequence) {
+                               return ((RNANodeValueTemplateSequence) v).getSequence().getLength();
+                       } else if (v instanceof RNANodeValueTemplateBrokenBasePair) {
+                               // insert one base
+                               return 1;
+                       } else { // this is a base pair
+                               // delete the base pair
+                               return 2;
+                       }
+               }
+       }
+       
+       public double f(RNANodeValue2 v1, RNANodeValueTemplate v2) {
+               if (v1 == null) {
+                       return insert(v2);
+               } else if (v2 == null) {
+                       return delete(v1);
+               } else if (!v1.isSingleNode()) { // v1 is a sequence
+                       if (v2 instanceof RNANodeValueTemplateSequence) {
+                               // the cost is the difference between the sequence lengths
+                               return Math.abs(v1.getNodes().size() - ((RNANodeValueTemplateSequence) v2).getSequence().getLength());
+                       } else {
+                               // a sequence cannot be changed in something else
+                               return Double.POSITIVE_INFINITY;
+                       }
+               } else if (v1.getNode().getRightBasePosition() >= 0) { // v1 is a base pair
+                       if (v2 instanceof RNANodeValueTemplateBasePair) {
+                               // ok, a base pair can be mapped to a base pair
+                               return 0;
+                       } else {
+                               // a base pair cannot be changed in something else
+                               return Double.POSITIVE_INFINITY;
+                       }
+               } else { // v1 is a broken base pair
+                       if (v2 instanceof RNANodeValueTemplateBrokenBasePair) {
+                               RNANodeValueTemplateBrokenBasePair brokenBasePair = ((RNANodeValueTemplateBrokenBasePair) v2);
+                               boolean strand5onTemplateSide = brokenBasePair.getPositionInHelix() < brokenBasePair.getHelix().getLength();
+                               boolean strand3onTemplateSide = ! strand5onTemplateSide;
+                               boolean strand5onRNASide = (v1.getNode().getOrigin() == RNANodeValue.Origin.BASE_FROM_HELIX_STRAND5);
+                               boolean strand3onRNASide = (v1.getNode().getOrigin() == RNANodeValue.Origin.BASE_FROM_HELIX_STRAND3);
+                               if ((strand5onTemplateSide && strand5onRNASide)
+                                               || (strand3onTemplateSide && strand3onRNASide)) {
+                                       // Ok they can be mapped together
+                                       return 0.0;
+                               } else {
+                                       // A base on a 5' strand of an helix
+                                       // cannot be mapped to base on a 3' strand of an helix
+                                       return Double.POSITIVE_INFINITY;
+                               }
+                       } else {
+                               return Double.POSITIVE_INFINITY;
+                       }
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplate.java b/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplate.java
new file mode 100644 (file)
index 0000000..79374ba
--- /dev/null
@@ -0,0 +1,23 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.models.treealign.GraphvizDrawableNodeValue;
+
+
+/**
+ * An node from an RNA template is either a sequence of non-paired bases,
+ * a base pair originally belonging to an helix,
+ * or a single base originally belonging to an helix but which was broken
+ * in order to remove pseudoknots.
+ * 
+ * @author Raphael Champeimont
+ */
+public abstract class RNANodeValueTemplate implements GraphvizDrawableNodeValue {
+
+       public String toString() {
+               return toGraphvizNodeName();
+       }
+       
+       public abstract String toGraphvizNodeName();
+       
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBasePair.java b/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBasePair.java
new file mode 100644 (file)
index 0000000..99c67b5
--- /dev/null
@@ -0,0 +1,48 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+
+
+/**
+ * See RNANodeValueTemplate.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNANodeValueTemplateBasePair extends RNANodeValueTemplate {
+       
+       /**
+        * The original template element this node came from.
+        */
+       private RNATemplateHelix helix;
+
+       /**
+        * The position (in the 5' to 3' order)
+        * of this base pair in the original helix.
+        */
+       private int positionInHelix;
+       
+       
+       
+       public String toGraphvizNodeName() {
+               return "H[" + positionInHelix + "]";
+       }
+       
+       
+       
+       public RNATemplateHelix getHelix() {
+               return helix;
+       }
+
+       public void setHelix(RNATemplateHelix helix) {
+               this.helix = helix;
+       }
+
+       public int getPositionInHelix() {
+               return positionInHelix;
+       }
+
+       public void setPositionInHelix(int positionInHelix) {
+               this.positionInHelix = positionInHelix;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBrokenBasePair.java b/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateBrokenBasePair.java
new file mode 100644 (file)
index 0000000..efa1679
--- /dev/null
@@ -0,0 +1,47 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+
+/**
+ * See RNANodeValueTemplate.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNANodeValueTemplateBrokenBasePair extends RNANodeValueTemplate {
+       
+       /**
+        * The original template element this node came from.
+        */
+       private RNATemplateHelix helix;
+
+       /**
+        * The position (in the 5' to 3' order)
+        * of this base in the original helix.
+        */
+       private int positionInHelix;
+       
+       
+       
+       public String toGraphvizNodeName() {
+               return "BH[" + positionInHelix + "]";
+       }
+       
+       
+       
+       public RNATemplateHelix getHelix() {
+               return helix;
+       }
+
+       public void setHelix(RNATemplateHelix helix) {
+               this.helix = helix;
+       }
+
+       public int getPositionInHelix() {
+               return positionInHelix;
+       }
+
+       public void setPositionInHelix(int positionInHelix) {
+               this.positionInHelix = positionInHelix;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateSequence.java b/srcjar/fr/orsay/lri/varna/models/templates/RNANodeValueTemplateSequence.java
new file mode 100644 (file)
index 0000000..0dfa53c
--- /dev/null
@@ -0,0 +1,34 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
+
+
+/**
+ * See RNANodeValueTemplate.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNANodeValueTemplateSequence extends RNANodeValueTemplate {
+
+       /**
+        * The sequence this node came from.
+        */
+       private RNATemplateUnpairedSequence sequence;
+       
+
+       
+       public String toGraphvizNodeName() {
+               return "S(len=" + sequence.getLength() + ")";
+       }
+       
+       
+       
+
+       public RNATemplateUnpairedSequence getSequence() {
+               return sequence;
+       }
+
+       public void setSequence(RNATemplateUnpairedSequence sequence) {
+               this.sequence = sequence;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNATemplate.java b/srcjar/fr/orsay/lri/varna/models/templates/RNATemplate.java
new file mode 100644 (file)
index 0000000..5e2a3ce
--- /dev/null
@@ -0,0 +1,1821 @@
+package fr.orsay.lri.varna.models.templates;
+
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.exceptions.ExceptionXMLGeneration;
+import fr.orsay.lri.varna.exceptions.ExceptionXmlLoading;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
+import fr.orsay.lri.varna.models.treealign.Tree;
+
+
+/**
+ * A model for RNA templates.
+ * A template is a way to display an RNA secondary structure.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNATemplate {
+       /**
+        * The list of template elements.
+        */
+       private Collection<RNATemplateElement> elements = new ArrayList<RNATemplateElement>();
+
+       /**
+        * Tells whether the template contains elements.
+        */
+       public boolean isEmpty() {
+               return elements.isEmpty();
+       }
+       
+       /**
+        * The first endpoint (in sequence order) of the template.
+        * If there are multiple connected components, the first elements of one
+        * connected component will be returned.
+        * If the template contains no elements, null is returned.
+        * If there is a cycle, an arbitrary endpoint will be returned
+        * (as it then does not make sense to define the first endpoint).
+        * Time: O(n)
+        */
+       public RNATemplateElement getFirst() {
+               return getFirstEndPoint().getElement();
+       }
+       
+       /**
+        * The first endpoint edge endpoint (in sequence order) of the template.
+        * If there are multiple connected components, the first elements of one
+        * connected component will be returned.
+        * If the template contains no elements, null is returned.
+        * If there is a cycle, an arbitrary endpoint will be returned
+        * (as it then does not make sense to define the first endpoint).
+        * Time: O(n)
+        */
+       public EdgeEndPoint getFirstEndPoint() {
+               if (elements.isEmpty()) {
+                       return null;
+               } else {
+                       Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>();
+                       EdgeEndPoint currentEndPoint = getAnyEndPoint();
+                       while (true) {
+                               if (knownEndPoints.contains(currentEndPoint)) {
+                                       // There is a cycle in the template, so we stop there
+                                       // to avoid looping infinitely.
+                                       return currentEndPoint;
+                               }
+                               knownEndPoints.add(currentEndPoint);
+                               EdgeEndPoint previousEndPoint = currentEndPoint.getPreviousEndPoint();
+                               if (previousEndPoint == null) {
+                                       return currentEndPoint;
+                               } else {
+                                       currentEndPoint = previousEndPoint;
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Return an arbitrary element of the template,
+        * null if empty.
+        * Time: O(1)
+        */
+       public RNATemplateElement getAny() {
+               if (elements.isEmpty()) {
+                       return null;
+               } else {
+                       return elements.iterator().next();
+               }
+       }
+       
+       /**
+        * Return an arbitrary endpoint of the template,
+        * null if empty.
+        * Time: O(1)
+        */
+       public EdgeEndPoint getAnyEndPoint() {
+               if (isEmpty()) {
+                       return null;
+               } else {
+                       return getAny().getIn1EndPoint();
+               }
+       }
+       
+       /**
+        * Variable containing "this", used by the internal class
+        * to access this object.
+        */
+       private final RNATemplate template = this;
+       
+
+       /**
+        * To get an iterator of this class, use rnaIterator().
+        * See rnaIterator() for documentation.
+        */
+       private class RNAIterator implements Iterator<RNATemplateElement> {
+               private Iterator<EdgeEndPoint> iter = vertexIterator();
+
+               public boolean hasNext() {
+                       return iter.hasNext();
+               }
+
+               public RNATemplateElement next() {
+                       if (! hasNext()) {
+                               throw (new NoSuchElementException());
+                       }
+                       
+                       EdgeEndPoint currentEndPoint = iter.next();
+                       switch (currentEndPoint.getPosition()) {
+                       // We skip "IN" endpoints, so that we don't return elements twice
+                       case IN1:
+                       case IN2:
+                               // We get the corresponding "OUT" endpoint
+                               currentEndPoint = iter.next();
+                               break;
+                       }
+                       
+                       return currentEndPoint.getElement();
+               }
+
+               public void remove() {
+                       throw (new UnsupportedOperationException());
+               }
+               
+       }
+       
+       /**
+        * Iterates over the elements of the template, in the sequence order.
+        * Helixes will be given twice.
+        * Only one connected component will be iterated on.
+        * Note that if there is a cycle, the iterator may return a infinite
+        * number of elements.
+        */
+       public Iterator<RNATemplateElement> rnaIterator() {
+               return new RNAIterator();
+       }
+       
+       /**
+        * Iterates over all elements (each endpoint is given only once)
+        * in an arbitrary order.
+        */
+       public Iterator<RNATemplateElement> classicIterator() {
+               return elements.iterator();
+       }
+       
+       private class VertexIterator implements Iterator<EdgeEndPoint> {
+               private EdgeEndPoint endpoint = getFirstEndPoint();
+
+               public boolean hasNext() {
+                       return (endpoint != null);
+               }
+
+               public EdgeEndPoint next() {
+                       if (endpoint == null) {
+                               throw (new NoSuchElementException());
+                       }
+                       
+                       EdgeEndPoint currentEndPoint = endpoint;
+                       endpoint = currentEndPoint.getNextEndPoint();
+                       return currentEndPoint;
+               }
+
+               public void remove() {
+                       throw (new UnsupportedOperationException());
+               }
+       }
+       
+       /**
+        * Iterates over the elements edge endpoints of the template,
+        * in the sequence order.
+        * Only one connected component will be iterated on.
+        * Note that if there is a cycle, the iterator may return a infinite
+        * number of elements.
+        */
+       public Iterator<EdgeEndPoint> vertexIterator() {
+               return new VertexIterator();
+       }
+       
+       
+       
+       private class MakeEdgeList {
+               List<EdgeEndPoint> list = new LinkedList<EdgeEndPoint>();
+               
+               private void addEdgeIfNecessary(EdgeEndPoint endPoint) {
+                       if (endPoint.isConnected()) {
+                               list.add(endPoint);
+                       }
+               }
+               
+               public List<EdgeEndPoint> make() {
+                       for (RNATemplateElement element: elements) {
+                               if (element instanceof RNATemplateHelix) {
+                                       RNATemplateHelix helix = (RNATemplateHelix) element;
+                                       addEdgeIfNecessary(helix.getIn1());
+                                       addEdgeIfNecessary(helix.getIn2());
+                               } else if (element instanceof RNATemplateUnpairedSequence) {
+                                       RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element;
+                                       addEdgeIfNecessary(sequence.getIn());
+                               }
+                       }
+                       return list;
+               }
+       }
+       
+       /**
+        * Return over all edges in an arbitrary order.
+        * For each edge, the first (5' side) endpoint will be given. 
+        */
+       public List<EdgeEndPoint> makeEdgeList() {
+               MakeEdgeList listMaker = new MakeEdgeList();
+               return listMaker.make();
+       }
+       
+       
+       
+       
+
+       private class RemovePseudoKnots {
+               /**
+                * The elements of the template as an array, in the order of the
+                * RNA sequence. Note that helixes will appear twice,
+                * and non-paired sequences don't appear.
+                */
+               private ArrayList<RNATemplateHelix> helixesSeq;
+               
+               /**
+                * For any i,
+                * j = helixesStruct[i] is the index in helixesSeq
+                * where the same helix also appears.
+                * It means we have for all i:
+                * helixesSeq[helixesStruct[i]] == helixesSeq[i]
+                */
+               private ArrayList<Integer> helixesStruct;
+               
+               /**
+                * The same as helixesStruct, but without the pseudoknots,
+                * ie. helixesStructWithoutPseudoKnots[i] may be -1
+                * even though helixesStruct[i] != -1 .
+                */
+               private int[] helixesStructWithoutPseudoKnots;
+
+               
+               private void initArrays() throws ExceptionInvalidRNATemplate {
+                       helixesSeq = new ArrayList<RNATemplateHelix>();
+                       helixesStruct = new ArrayList<Integer>();
+                       Map<RNATemplateHelix,Integer> knownHelixes = new Hashtable<RNATemplateHelix,Integer>();
+                       Iterator<RNATemplateElement> iter = rnaIterator();
+                       while (iter.hasNext()) {
+                               RNATemplateElement element = iter.next();
+                               if (element instanceof RNATemplateHelix) {
+                                       helixesSeq.add((RNATemplateHelix) element);
+                                       int index = helixesSeq.size() - 1;
+                                       if (knownHelixes.containsKey(element)) {
+                                               // This is the second time we meet this helix.
+                                               int otherOccurenceIndex = knownHelixes.get(element);
+                                               helixesStruct.add(otherOccurenceIndex);
+                                               if (helixesStruct.get(otherOccurenceIndex) != -1) {
+                                                       throw (new ExceptionInvalidRNATemplate("We met an helix 3 times. Is there a cycle?"));
+                                               }
+                                               // Now we know the partner of the other part of
+                                               // the helix.
+                                               helixesStruct.set(otherOccurenceIndex, index);
+                                       } else {
+                                               // This is the first time we meet this helix.
+                                               // Remember its index
+                                               knownHelixes.put((RNATemplateHelix) element, index);
+                                               // For the moment we don't know where the other part
+                                               // of the helix is, but this will be found later.
+                                               helixesStruct.add(-1);
+                                       }
+                               }
+                       }
+                       
+               }
+               
+               /**
+                * Tells whether there is a pseudoknot.
+                * Adapted from RNAMLParser.isSelfCrossing()
+                */
+               private boolean isSelfCrossing() {
+                       Stack<Point> intervals = new Stack<Point>();
+                       intervals.add(new Point(0, helixesStruct.size() - 1));
+                       while (!intervals.empty()) {
+                               Point p = intervals.pop();
+                               if (p.x <= p.y) {
+                                       if (helixesStruct.get(p.x) == -1) {
+                                               intervals.push(new Point(p.x + 1, p.y));
+                                       } else {
+                                               int i = p.x;
+                                               int j = p.y;
+                                               int k = helixesStruct.get(i);
+                                               if ((k <= i) || (k > j)) {
+                                                       return true;
+                                               } else {
+                                                       intervals.push(new Point(i + 1, k - 1));
+                                                       intervals.push(new Point(k + 1, j));
+                                               }
+                                       }
+                               }
+                       }
+                       return false;
+               }
+               
+               /**
+                * We compute helixesStructWithoutPseudoKnots
+                * from helixesStruct by replacing values by -1
+                * for the helixes we cut (the bases are become non-paired).
+                * We try to cut as few base pairs as possible.
+                */
+               private void removePseudoKnotsAux() {
+                       if (!isSelfCrossing()) {
+                               helixesStructWithoutPseudoKnots = new int[helixesStruct.size()];
+                               for (int i=0; i<helixesStructWithoutPseudoKnots.length; i++) {
+                                       helixesStructWithoutPseudoKnots[i] = helixesStruct.get(i);
+                               }
+                       } else {
+                               
+                               // We need to get rid of pseudoknots
+                               // This code was adapted from RNAMLParser.planarize()
+                               int length = helixesStruct.size();
+       
+                               int[] result = new int[length];
+                               for (int i = 0; i < result.length; i++) {
+                                       result[i] = -1;
+                               }
+       
+                               short[][] tab = new short[length][length];
+                               short[][] backtrack = new short[length][length];
+       
+                               // On the diagonal we have intervals containing only
+                               // one endpoint. Therefore there can be no helix
+                               // (because an helix consists of 2 elements).
+                               for (int i = 0; i < result.length; i++) {
+                                       // tab[i][j] = 0;
+                                       backtrack[i][i] = -1;
+                               }
+                               
+                               for (int n = 1; n < length; n++) {
+                                       for (int i = 0; i < length - n; i++) {
+                                               int j = i + n;
+                                               tab[i][j] = tab[i + 1][j];
+                                               backtrack[i][j] = -1;
+                                               int k = helixesStruct.get(i);
+                                               assert k != -1;
+                                               if ((k <= j) && (i < k)) {
+                                                       int tmp = helixesSeq.get(i).getLength();
+                                                       if (i + 1 <= k - 1) {
+                                                               tmp += tab[i + 1][k - 1];
+                                                       }
+                                                       if (k + 1 <= j) {
+                                                               tmp += tab[k + 1][j];
+                                                       }
+                                                       if (tmp > tab[i][j]) {
+                                                               tab[i][j] = (short) tmp;
+                                                               backtrack[i][j] = (short) k;
+                                                       }
+                                               }
+                                       }
+                               }
+                               
+                               // debug
+                               //RNATemplateTests.printShortMatrix(tab);
+                               
+                               Stack<Point> intervals = new Stack<Point>();
+                               intervals.add(new Point(0, length - 1));
+                               while (!intervals.empty()) {
+                                       Point p = intervals.pop();
+                                       if (p.x <= p.y) {
+                                               if (backtrack[p.x][p.y] == -1) {
+                                                       result[p.x] = -1;
+                                                       intervals.push(new Point(p.x + 1, p.y));
+                                               } else {
+                                                       int i = p.x;
+                                                       int j = p.y;
+                                                       int k = backtrack[i][j];
+                                                       result[i] = k;
+                                                       result[k] = i;
+                                                       intervals.push(new Point(i + 1, k - 1));
+                                                       intervals.push(new Point(k + 1, j));
+                                               }
+                                       }
+                               }
+                               
+                               helixesStructWithoutPseudoKnots = result;
+                       }
+               }
+               
+               private Set<RNATemplateHelix> makeSet() {
+                       Set<RNATemplateHelix> removedHelixes = new HashSet<RNATemplateHelix>();
+                       
+                       for (int i=0; i<helixesStructWithoutPseudoKnots.length; i++) {
+                               if (helixesStructWithoutPseudoKnots[i] < 0) {
+                                       removedHelixes.add(helixesSeq.get(i));
+                               }
+                       }
+                       
+                       return removedHelixes;
+               }
+               
+               public Set<RNATemplateHelix> removePseudoKnots() throws ExceptionInvalidRNATemplate {
+                       initArrays();
+                       
+                       removePseudoKnotsAux();
+                       // debug
+                       //printIntArrayList(helixesStruct);
+                       //printIntArray(helixesStructWithoutPseudoKnots);
+                       
+                       return makeSet();
+               }
+       }
+       
+       private class ConvertToTree {
+               private Set<RNATemplateHelix> removedHelixes;
+               
+               public ConvertToTree(Set<RNATemplateHelix> removedHelixes) {
+                       this.removedHelixes = removedHelixes;
+               }
+               
+               private Iterator<RNATemplateElement> iter = template.rnaIterator();
+               private Set<RNATemplateHelix> knownHelixes = new HashSet<RNATemplateHelix>();
+               
+               public Tree<RNANodeValueTemplate> convert() throws ExceptionInvalidRNATemplate {
+                       Tree<RNANodeValueTemplate> root = new Tree<RNANodeValueTemplate>();
+                        // No value, this is a fake node because we need a root.
+                       root.setValue(null);
+                       makeChildren(root);
+                       return root;
+               }
+               
+               private void makeChildren(Tree<RNANodeValueTemplate> father) throws ExceptionInvalidRNATemplate {
+                       List<Tree<RNANodeValueTemplate>> children = father.getChildren();
+                       while (true) {
+                               try {
+                                       RNATemplateElement element = iter.next();
+                                       if (element instanceof RNATemplateHelix) {
+                                               RNATemplateHelix helix = (RNATemplateHelix) element;
+                                               if (removedHelixes.contains(helix)) {
+                                                       // Helix was removed
+                                                       
+                                                       boolean firstPartOfHelix;
+                                                       if (knownHelixes.contains(helix)) {
+                                                               firstPartOfHelix = false;
+                                                       } else {
+                                                               knownHelixes.add(helix);
+                                                               firstPartOfHelix = true;
+                                                       }
+                                                       
+                                                       int helixLength = helix.getLength();
+                                                       // Maybe we could allow helixes of length 0?
+                                                       // If we want to then this code can be changed in the future.
+                                                       if (helixLength < 1) {
+                                                               throw (new ExceptionInvalidRNATemplate("Helix length < 1"));
+                                                       }
+                                                       int firstPosition = firstPartOfHelix ? 0 : helixLength;
+                                                       int afterLastPosition = firstPartOfHelix ? helixLength : 2*helixLength;
+                                                       for (int i=firstPosition; i<afterLastPosition; i++) {
+                                                               RNANodeValueTemplateBrokenBasePair value = new RNANodeValueTemplateBrokenBasePair();
+                                                               value.setHelix(helix);
+                                                               value.setPositionInHelix(i);
+                                                               Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>();
+                                                               child.setValue(value);
+                                                               father.getChildren().add(child);
+                                                       }
+                                                       
+                                               } else {
+                                                       // We have an non-removed helix
+                                                       
+                                                       if (knownHelixes.contains(helix)) {
+                                                               if ((! (father.getValue() instanceof RNANodeValueTemplateBasePair))
+                                                                               || ((RNANodeValueTemplateBasePair) father.getValue()).getHelix() != helix) {
+                                                                       // We have already met this helix, so unless it is our father,
+                                                                       // we have a pseudoknot (didn't we remove them???).
+                                                                       throw (new ExceptionInvalidRNATemplate("Unexpected helix. Looks like there still are pseudoknots even after we removed them so something is wrong about the template."));
+                                                               } else {
+                                                                       // As we have found the father, we have finished our work
+                                                                       // with the children.
+                                                                       return;
+                                                               }
+                                                       } else {
+                                                               knownHelixes.add(helix);
+                                                               
+                                                               int helixLength = helix.getLength();
+                                                               // Maybe we could allow helixes of length 0?
+                                                               // If we want to then this code can be changed in the future.
+                                                               if (helixLength < 1) {
+                                                                       throw (new ExceptionInvalidRNATemplate("Helix length < 1"));
+                                                               }
+                                                               Tree<RNANodeValueTemplate> lastChild = father;
+                                                               for (int i=0; i<helixLength; i++) {
+                                                                       RNANodeValueTemplateBasePair value = new RNANodeValueTemplateBasePair();
+                                                                       value.setHelix(helix);
+                                                                       value.setPositionInHelix(i);
+                                                                       Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>();
+                                                                       child.setValue(value);
+                                                                       lastChild.getChildren().add(child);
+                                                                       lastChild = child;
+                                                               }
+                                                               // Now we put what follows as children of lastChild
+                                                               makeChildren(lastChild);
+                                                       }
+                                                       
+                                                       
+                                               }
+                                       } else if (element instanceof RNATemplateUnpairedSequence) {
+                                               RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element;
+                                               int seqLength = sequence.getLength();
+                                               
+                                               // Maybe we could allow sequences of length 0?
+                                               // If we want to then this code can be changed in the future.
+                                               if (seqLength < 1) {
+                                                       throw (new ExceptionInvalidRNATemplate("Non-paired sequence length < 1"));
+                                               }
+                                               
+                                               RNANodeValueTemplateSequence value = new RNANodeValueTemplateSequence();
+                                               value.setSequence(sequence);
+                                               Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>();
+                                               child.setValue(value);
+                                               children.add(child);
+                                       } else {
+                                               throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?"));
+                                       }
+                                       
+                               } catch (NoSuchElementException e) {
+                                       // We are at the end of elements so if everything is ok
+                                       // the father must be the root.
+                                       if (father.getValue() == null) {
+                                               return; // Work finished.
+                                       } else {
+                                               throw (new ExceptionInvalidRNATemplate("Unexpected end of template endpoint list."));
+                                       }
+                               }
+                       }
+               }
+       
+       }
+       
+       /**
+        * Tells whether the connected component to which endPoint belongs to
+        * is cyclic.
+        */
+       public boolean connectedComponentIsCyclic(EdgeEndPoint endPoint) {
+               Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>();
+               EdgeEndPoint currentEndPoint = endPoint;
+               while (true) {
+                       if (knownEndPoints.contains(currentEndPoint)) {
+                               return true;
+                       }
+                       knownEndPoints.add(currentEndPoint);
+                       EdgeEndPoint previousEndPoint = currentEndPoint.getPreviousEndPoint();
+                       if (previousEndPoint == null) {
+                               return false;
+                       } else {
+                               currentEndPoint = previousEndPoint;
+                       }
+               }
+       }
+       
+       /**
+        * Tells whether the template elements are all connected, ie. if the
+        * graph (edge endpoints being vertices) is connected. 
+        */
+       public boolean isConnected() {
+               if (isEmpty()) {
+                       return true;
+               }
+               
+               // Count all vertices
+               int n = 0;
+               for (RNATemplateElement element: elements) {
+                       if (element instanceof RNATemplateHelix) {
+                               n += 4;
+                       } else if (element instanceof RNATemplateUnpairedSequence) {
+                               n += 2;
+                       }
+               }
+               
+               // Now try reaching all vertices
+               Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>();
+               EdgeEndPoint currentEndPoint = getFirstEndPoint();
+               while (true) {
+                       if (knownEndPoints.contains(currentEndPoint)) {
+                               // We are back to our original endpoint, so stop
+                               break;
+                       }
+                       knownEndPoints.add(currentEndPoint);
+                       EdgeEndPoint nextEndPoint = currentEndPoint.getNextEndPoint();
+                       if (nextEndPoint == null) {
+                               break;
+                       } else {
+                               currentEndPoint = nextEndPoint;
+                       }
+               }
+               
+               // The graph is connected iff the number of reached vertices
+               // is equal to the total number of vertices.
+               return (knownEndPoints.size() == n);
+
+       }
+
+       /**
+        * Checks whether this template is a valid RNA template, ie.
+        * it is non-empty, it does not contain a cycle and all elements are in one
+        * connected component.
+        */
+       public void checkIsValidTemplate() throws ExceptionInvalidRNATemplate {
+               if (isEmpty()) {
+                       throw (new ExceptionInvalidRNATemplate("The template is empty."));
+               }
+               
+               if (! isConnected()) {
+                       throw (new ExceptionInvalidRNATemplate("The template is a non-connected graph."));
+               }
+               
+               // Now we know the graph is connected.
+               
+               if (connectedComponentIsCyclic(getAnyEndPoint())) {
+                       throw (new ExceptionInvalidRNATemplate("The template is cyclic."));
+               }
+       }
+       
+       /**
+        * Make a tree of the template. For this, we will remove pseudoknots,
+        * taking care to remove as few base pair links as possible.
+        * Requires the template to be valid and will check the validity
+        * (will call checkIsValidTemplate()).
+        * Calling this method will automatically call computeIn1Is().
+        */
+       public Tree<RNANodeValueTemplate> toTree() throws ExceptionInvalidRNATemplate {
+               // Compute the helix in1Is fields.
+               // We also rely on computeIn1Is() for checking the template validity.
+               computeIn1Is();
+               
+               // Remove pseudoknots
+               RemovePseudoKnots pseudoKnotKiller = new RemovePseudoKnots();
+               Set<RNATemplateHelix> removedHelixes = pseudoKnotKiller.removePseudoKnots();
+               
+               // Convert to tree
+               ConvertToTree converter = new ConvertToTree(removedHelixes);
+               return converter.convert();
+       }
+       
+       
+       /**
+        * Generate an RNA sequence that exactly matches the template.
+        * Requires the template to be valid and will check the validity
+        * (will call checkIsValidTemplate()).
+        */
+       public RNA toRNA() throws ExceptionInvalidRNATemplate {
+               // First, we check this is a valid template
+               checkIsValidTemplate();
+               
+               ArrayList<Integer> str = new ArrayList<Integer>();
+               Map<RNATemplateHelix, ArrayList<Integer>> helixes = new HashMap<RNATemplateHelix, ArrayList<Integer>>();
+               Iterator<RNATemplateElement> iter = rnaIterator();
+               while (iter.hasNext()) {
+                       RNATemplateElement element = iter.next();
+                       if (element instanceof RNATemplateHelix) {
+                               RNATemplateHelix helix = (RNATemplateHelix) element;
+                               int n = helix.getLength();
+                               if (helixes.containsKey(helix)) {
+                                       int firstBase = str.size();
+                                       ArrayList<Integer> helixMembers = helixes.get(helix);
+                                       for (int i = 0; i < n; i++) {
+                                               int indexOfAssociatedBase = helixMembers.get(n-1-i);
+                                               str.set(indexOfAssociatedBase, firstBase + i);
+                                               str.add(indexOfAssociatedBase);
+                                       }
+                               } else {
+                                       int firstBase = str.size();
+                                       ArrayList<Integer> helixMembers = new ArrayList<Integer>();
+                                       for (int i = 0; i < n; i++) {
+                                               // We don't known yet where the associated base is
+                                               str.add(-1);
+                                               helixMembers.add(firstBase + i);
+                                       }
+                                       helixes.put(helix, helixMembers);
+                               }
+                       } else if (element instanceof RNATemplateUnpairedSequence) {
+                               RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element;
+                               int n = sequence.getLength();
+                               for (int i=0; i<n; i++) {
+                                       str.add(-1);
+                               }
+                       } else {
+                               throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?"));
+                       }
+               }
+               
+               int[] strAsArray = RNATemplateAlign.intArrayFromList(str);
+               String[] seqAsArray = new String[strAsArray.length];
+               Arrays.fill(seqAsArray, " ");
+               RNA rna = new RNA();
+               try {
+                       rna.setRNA(seqAsArray, strAsArray);
+               } catch (ExceptionFileFormatOrSyntax e) {
+                       throw (new RuntimeException("Bug in toRNA(): setRNA() threw an ExceptionFileFormatOrSyntax exception."));
+               }
+               return rna;
+       }
+       
+       
+       private class ConvertToXml {
+               private Map<RNATemplateElement, String> elementNames = new HashMap<RNATemplateElement, String>();
+               private Element connectionsXmlElement;
+               private Document document;
+               
+               private void addConnectionIfNecessary(EdgeEndPoint endPoint) {
+                       if (endPoint != null && endPoint.isConnected()) {
+                               RNATemplateElement e1 = endPoint.getElement();
+                               EdgeEndPointPosition p1 = endPoint.getPosition();
+                               RNATemplateElement e2 = endPoint.getOtherElement();
+                               EdgeEndPointPosition p2 = endPoint.getOtherEndPoint().getPosition();
+                               Element xmlElement = document.createElement("edge");
+                               {
+                                       Element fromXmlElement = document.createElement("from");
+                                       fromXmlElement.setAttribute("endpoint", elementNames.get(e1));
+                                       fromXmlElement.setAttribute("position", p1.toString());
+                                       xmlElement.appendChild(fromXmlElement);
+                               }
+                               {
+                                       Element toXmlElement = document.createElement("to");
+                                       toXmlElement.setAttribute("endpoint", elementNames.get(e2));
+                                       toXmlElement.setAttribute("position", p2.toString());
+                                       xmlElement.appendChild(toXmlElement);
+                               }
+                               connectionsXmlElement.appendChild(xmlElement);
+                       }
+               }
+               
+               public Document toXMLDocument() throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate {
+                       try {
+                               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                               DocumentBuilder builder = factory.newDocumentBuilder();
+                               document = builder.newDocument();
+                               Element root = document.createElement("RNATemplate");
+                               document.appendChild(root);
+                               Element elementsXmlElement = document.createElement("elements");
+                               root.appendChild(elementsXmlElement);
+                               connectionsXmlElement = document.createElement("edges");
+                               root.appendChild(connectionsXmlElement);
+                               
+                               // First pass, we create a mapping between java references and names (strings)
+                               {
+                                       int nextHelix = 1;
+                                       int nextNonPairedSequence = 1;
+                                       for (RNATemplateElement templateElement: elements) {
+                                               if (templateElement instanceof RNATemplateHelix) {
+                                                       RNATemplateHelix helix = (RNATemplateHelix) templateElement;
+                                                       if (! elementNames.containsKey(helix)) {
+                                                               elementNames.put(helix, "H ID " + nextHelix);
+                                                               nextHelix++;
+                                                       }
+                                               } else if (templateElement instanceof RNATemplateUnpairedSequence) {
+                                                       RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) templateElement;
+                                                       if (! elementNames.containsKey(sequence)) {
+                                                               elementNames.put(sequence, "S ID " + nextNonPairedSequence);
+                                                               nextNonPairedSequence++;
+                                                       }
+                                               } else {
+                                                       throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?"));
+                                               }
+                                       }
+                               }
+                               
+                               // Now we generate the XML document
+                               for (RNATemplateElement templateElement: elements) {
+                                       String elementXmlName = elementNames.get(templateElement);
+                                       Element xmlElement;
+                                       if (templateElement instanceof RNATemplateHelix) {
+                                               RNATemplateHelix helix = (RNATemplateHelix) templateElement;
+                                               xmlElement = document.createElement("helix");
+                                               xmlElement.setAttribute("id", elementXmlName);
+                                               xmlElement.setAttribute("length", Integer.toString(helix.getLength()));
+                                               xmlElement.setAttribute("flipped", Boolean.toString(helix.isFlipped()));
+                                               if (helix.hasCaption()) {
+                                                       xmlElement.setAttribute("caption", helix.getCaption());
+                                               }
+                                               {
+                                                       Element startPositionXmlElement = document.createElement("startPosition");
+                                                       startPositionXmlElement.setAttribute("x", Double.toString(helix.getStartPosition().x));
+                                                       startPositionXmlElement.setAttribute("y", Double.toString(helix.getStartPosition().y));
+                                                       xmlElement.appendChild(startPositionXmlElement);
+                                               }
+                                               {
+                                                       Element endPositionXmlElement = document.createElement("endPosition");
+                                                       endPositionXmlElement.setAttribute("x", Double.toString(helix.getEndPosition().x));
+                                                       endPositionXmlElement.setAttribute("y", Double.toString(helix.getEndPosition().y));
+                                                       xmlElement.appendChild(endPositionXmlElement);
+                                               }
+                                               addConnectionIfNecessary(helix.getOut1());
+                                               addConnectionIfNecessary(helix.getOut2());
+                                       } else if (templateElement instanceof RNATemplateUnpairedSequence) {
+                                               RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) templateElement;
+                                               xmlElement = document.createElement("sequence");
+                                               xmlElement.setAttribute("id", elementXmlName);
+                                               xmlElement.setAttribute("length", Integer.toString(sequence.getLength()));
+                                               {
+                                                       Element vertex5XmlElement = document.createElement("vertex5");
+                                                       vertex5XmlElement.setAttribute("x", Double.toString(sequence.getVertex5().x));
+                                                       vertex5XmlElement.setAttribute("y", Double.toString(sequence.getVertex5().y));
+                                                       xmlElement.appendChild(vertex5XmlElement);
+                                               }
+                                               {
+                                                       Element vertex3XmlElement = document.createElement("vertex3");
+                                                       vertex3XmlElement.setAttribute("x", Double.toString(sequence.getVertex3().x));
+                                                       vertex3XmlElement.setAttribute("y", Double.toString(sequence.getVertex3().y));
+                                                       xmlElement.appendChild(vertex3XmlElement);
+                                               }
+                                               {
+                                                       Element inTangentVectorXmlElement = document.createElement("inTangentVector");
+                                                       inTangentVectorXmlElement.setAttribute("angle", Double.toString(sequence.getInTangentVectorAngle()));
+                                                       inTangentVectorXmlElement.setAttribute("length", Double.toString(sequence.getInTangentVectorLength()));
+                                                       xmlElement.appendChild(inTangentVectorXmlElement);
+                                               }
+                                               {
+                                                       Element outTangentVectorXmlElement = document.createElement("outTangentVector");
+                                                       outTangentVectorXmlElement.setAttribute("angle", Double.toString(sequence.getOutTangentVectorAngle()));
+                                                       outTangentVectorXmlElement.setAttribute("length", Double.toString(sequence.getOutTangentVectorLength()));
+                                                       xmlElement.appendChild(outTangentVectorXmlElement);
+                                               }
+                                               addConnectionIfNecessary(sequence.getOut());
+                                       } else {
+                                               throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?"));
+                                       }
+                                       elementsXmlElement.appendChild(xmlElement);
+                               }
+                               
+                               return document;
+                       } catch (ParserConfigurationException e) {
+                               throw (new ExceptionXMLGeneration("ParserConfigurationException: " + e.getMessage()));
+                       }
+               }
+       }
+       
+       
+       
+       public void toXMLFile(File file) throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate {
+               try {
+                       Document xmlDocument = toXMLDocument();
+                       Source source = new DOMSource(xmlDocument);
+                       Result result = new StreamResult(file);
+                       Transformer transformer;
+                       transformer = TransformerFactory.newInstance().newTransformer();
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+                       transformer.transform(source, result); 
+               } catch (TransformerConfigurationException e) {
+                       throw (new ExceptionXMLGeneration("TransformerConfigurationException: " + e.getMessage()));
+               } catch (TransformerFactoryConfigurationError e) {
+                       throw (new ExceptionXMLGeneration("TransformerFactoryConfigurationError: " + e.getMessage()));
+               } catch (TransformerException e) {
+                       throw (new ExceptionXMLGeneration("TransformerException: " + e.getMessage()));
+               }
+       }
+
+       
+       public Document toXMLDocument() throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate {
+               ConvertToXml converter = new ConvertToXml();
+               return converter.toXMLDocument();
+       }
+       
+       
+       private class LoadFromXml {
+               private Document xmlDocument;
+               private Map<String, RNATemplateElement> elementNames = new HashMap<String, RNATemplateElement>();
+               
+               public LoadFromXml(Document xmlDocument) {
+                       this.xmlDocument = xmlDocument;
+               }
+               
+               private Point2D.Double pointFromXml(Element xmlPoint) {
+                       Point2D.Double point = new Point2D.Double();
+                       point.x = Double.parseDouble(xmlPoint.getAttribute("x"));
+                       point.y = Double.parseDouble(xmlPoint.getAttribute("y"));
+                       return point;
+               }
+               
+               private double vectorLengthFromXml(Element xmlVector) {
+                       return Double.parseDouble(xmlVector.getAttribute("length"));
+               }
+               
+               private double vectorAngleFromXml(Element xmlVector) {
+                       return Double.parseDouble(xmlVector.getAttribute("angle"));
+               }
+               
+               /**
+                * Takes an element of the form:
+                * <anything endpoint="an element id" position="a valid EdgeEndPointPosition"/>
+                * and returns the corresponding EdgeEndPoint object. 
+                */
+               private EdgeEndPoint endPointFromXml(Element xmlEdgeEndPoint) throws ExceptionXmlLoading {
+                       String elementId = xmlEdgeEndPoint.getAttribute("endpoint");
+                       if (elementId == null || elementId == "")
+                               throw (new ExceptionXmlLoading ("Missing endpoint attribute on " + xmlEdgeEndPoint));
+                       String positionOnElement = xmlEdgeEndPoint.getAttribute("position");
+                       if (positionOnElement == null || positionOnElement == "")
+                               throw (new ExceptionXmlLoading ("Missing position attribute on " + xmlEdgeEndPoint));
+                       if (elementNames.containsKey(elementId)) {
+                               RNATemplateElement templateElement = elementNames.get(elementId);
+                               EdgeEndPointPosition relativePosition = EdgeEndPointPosition.valueOf(positionOnElement);
+                               if (relativePosition == null)
+                                       throw (new ExceptionXmlLoading ("Could not compute relativePosition"));
+                               return templateElement.getEndPointFromPosition(relativePosition);
+                       } else {
+                               throw (new ExceptionXmlLoading("Edge is connected on unkown element: " + elementId));
+                       }
+               }
+               
+               private String connectErrMsg(EdgeEndPoint v1, EdgeEndPoint v2, String reason) {
+                       return "Error while connecting\n"
+                       + v1.toString() + " to\n"
+                       + v2.toString() + " because:\n"
+                       + reason;
+               }
+               
+               private void connect(EdgeEndPoint v1, EdgeEndPoint v2) throws ExceptionXmlLoading {
+                       if (v1 == null || v2 == null) {
+                               throw (new ExceptionXmlLoading("Invalid edge: missing endpoint\n v1 = " + v1 + "\n v2 = " + v2));
+                       }
+                       if (v2.isConnected()) {
+                               throw (new ExceptionXmlLoading(connectErrMsg(v1, v2, "Second vertex is already connected to " + v2.getOtherElement().toString())));
+                       }
+                       if (v1.isConnected()) {
+                               throw (new ExceptionXmlLoading(connectErrMsg(v1, v2, "First vertex is already connected to " + v1.getOtherElement().toString())));
+                       }
+                       
+                       try {
+                               v1.connectTo(v2);
+                       } catch (ExceptionEdgeEndpointAlreadyConnected e) {
+                               throw (new ExceptionXmlLoading("A vertex is on two edges at the same time: " + e.getMessage()));
+                       } catch (ExceptionInvalidRNATemplate e) {
+                               throw (new ExceptionXmlLoading("ExceptionInvalidRNATemplate: " + e.getMessage()));
+                       }
+               }
+               
+               public void load() throws ExceptionXmlLoading {
+                       // First part: we load all elements from the XML tree
+                       Element xmlElements = (Element) xmlDocument.getElementsByTagName("elements").item(0);
+                       {
+                               NodeList xmlElementsChildren = xmlElements.getChildNodes();
+                               for (int i=0; i<xmlElementsChildren.getLength(); i++) {
+                                       Node xmlElementsChild = xmlElementsChildren.item(i);
+                                       if (xmlElementsChild instanceof Element) {
+                                               Element xmlTemplateElement = (Element) xmlElementsChild;
+                                               String tagName = xmlTemplateElement.getTagName();
+                                               if (tagName == "helix") {
+                                                       RNATemplateHelix helix = new RNATemplateHelix(xmlTemplateElement.getAttribute("id"));
+                                                       helix.setFlipped(Boolean.parseBoolean(xmlTemplateElement.getAttribute("flipped")));
+                                                       helix.setLength(Integer.parseInt(xmlTemplateElement.getAttribute("length")));
+                                                       if (xmlTemplateElement.hasAttribute("caption")) {
+                                                               helix.setCaption(xmlTemplateElement.getAttribute("caption"));
+                                                       }
+                                                       elementNames.put(xmlTemplateElement.getAttribute("id"), helix);
+                                                       NodeList xmlHelixChildren = xmlTemplateElement.getChildNodes();
+                                                       for (int j=0; j<xmlHelixChildren.getLength(); j++) {
+                                                               Node xmlHelixChild = xmlHelixChildren.item(j);
+                                                               if (xmlHelixChild instanceof Element) {
+                                                                       Element xmlHelixChildElement = (Element) xmlHelixChild;
+                                                                       String helixChildTagName = xmlHelixChildElement.getTagName();
+                                                                       if (helixChildTagName == "startPosition") {
+                                                                               helix.setStartPosition(pointFromXml(xmlHelixChildElement));
+                                                                       } else if (helixChildTagName == "endPosition") {
+                                                                               helix.setEndPosition(pointFromXml(xmlHelixChildElement));
+                                                                       }
+                                                               }
+                                                       }
+                                               } else if (tagName == "sequence") {
+                                                       RNATemplateUnpairedSequence sequence = new RNATemplateUnpairedSequence(xmlTemplateElement.getAttribute("id"));
+                                                       sequence.setLength(Integer.parseInt(xmlTemplateElement.getAttribute("length")));
+                                                       elementNames.put(xmlTemplateElement.getAttribute("id"), sequence);
+                                                       NodeList xmlSequenceChildren = xmlTemplateElement.getChildNodes();
+                                                       for (int j=0; j<xmlSequenceChildren.getLength(); j++) {
+                                                               Node xmlSequenceChild = xmlSequenceChildren.item(j);
+                                                               if (xmlSequenceChild instanceof Element) {
+                                                                       Element xmlSequenceChildElement = (Element) xmlSequenceChild;
+                                                                       String sequenceChildTagName = xmlSequenceChildElement.getTagName();
+                                                                       if (sequenceChildTagName == "inTangentVector") {
+                                                                               sequence.setInTangentVectorLength(vectorLengthFromXml(xmlSequenceChildElement));
+                                                                               sequence.setInTangentVectorAngle(vectorAngleFromXml(xmlSequenceChildElement));
+                                                                       } else if (sequenceChildTagName == "outTangentVector") {
+                                                                               sequence.setOutTangentVectorLength(vectorLengthFromXml(xmlSequenceChildElement));
+                                                                               sequence.setOutTangentVectorAngle(vectorAngleFromXml(xmlSequenceChildElement));
+                                                                       } else if (sequenceChildTagName == "vertex5") {
+                                                                               sequence.setVertex5(pointFromXml(xmlSequenceChildElement));
+                                                                       } else if (sequenceChildTagName == "vertex3") {
+                                                                               sequence.setVertex3(pointFromXml(xmlSequenceChildElement));
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       
+                       // Second part: We read the edges from the XML tree
+                       Element xmlEdges = (Element) xmlDocument.getElementsByTagName("edges").item(0);
+                       {
+                               NodeList xmlEdgesChildren = xmlEdges.getChildNodes();
+                               for (int i=0; i<xmlEdgesChildren.getLength(); i++) {
+                                       Node xmlEdgesChild = xmlEdgesChildren.item(i);
+                                       if (xmlEdgesChild instanceof Element) {
+                                               Element xmlTemplateEdge = (Element) xmlEdgesChild;
+                                               if (xmlTemplateEdge.getTagName() == "edge") {
+                                                       EdgeEndPoint v1 = null, v2 = null;
+                                                       NodeList xmlEdgeChildren = xmlTemplateEdge.getChildNodes();
+                                                       for (int j=0; j<xmlEdgeChildren.getLength(); j++) {
+                                                               Node xmlEdgeChild = xmlEdgeChildren.item(j);
+                                                               if (xmlEdgeChild instanceof Element) {
+                                                                       Element xmlEdgeChildElement = (Element) xmlEdgeChild;
+                                                                       String edgeChildTagName = xmlEdgeChildElement.getTagName();
+                                                                       if (edgeChildTagName == "from") {
+                                                                               v1 = endPointFromXml(xmlEdgeChildElement);
+                                                                       } else if (edgeChildTagName == "to") {
+                                                                               v2 = endPointFromXml(xmlEdgeChildElement);
+                                                                       }
+                                                               }
+                                                       }
+                                                       if (v1 == null)
+                                                               throw (new ExceptionXmlLoading("Invalid edge: missing \"from\" declaration"));
+                                                       if (v2 == null)
+                                                               throw (new ExceptionXmlLoading("Invalid edge: missing \"to\" declaration"));
+                                                       connect(v1, v2);
+                                               }
+                                       }
+                               }
+                       
+                       }
+               }
+       }
+       
+       
+       public static RNATemplate fromXMLFile(File file) throws ExceptionXmlLoading {
+               try {
+                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                       factory.setIgnoringElementContentWhitespace(true);
+                       DocumentBuilder builder = factory.newDocumentBuilder();
+                       Document xmlDocument = builder.parse(file);
+                       return fromXMLDocument(xmlDocument);
+               } catch (ParserConfigurationException e) {
+                       throw (new ExceptionXmlLoading("ParserConfigurationException: " + e.getMessage()));
+               } catch (SAXException e) {
+                       throw (new ExceptionXmlLoading("SAXException: " + e.getMessage()));
+               } catch (IOException e) {
+                       throw (new ExceptionXmlLoading("IOException: " + e.getMessage()));
+               }
+       }
+       
+       
+       public static RNATemplate fromXMLDocument(Document xmlDocument) throws ExceptionXmlLoading {
+               RNATemplate template = new RNATemplate();
+               LoadFromXml loader = template.new LoadFromXml(xmlDocument);
+               loader.load();
+               return template;
+       }
+       
+       
+       
+       /**
+        * For an helix, tells us whether IN1/OUT1 is the 5' strand
+        * (the first strand we meet if we follow the RNA sequence)
+        * or the 3' strand (the second we meet if we follow the RNA sequence).
+        */
+       public enum In1Is {
+               IN1_IS_5PRIME, IN1_IS_3PRIME
+       }
+       
+       
+       /**
+        * For each helix, compute the in1Is field.
+        * If helices connections are changed, the value may become obsolete,
+        * so you need to call this method again before accessing the in1Is
+        * fields if you have modified connections in the template.
+        * Requires the template to be valid and will check the validity
+        * (will call checkIsValidTemplate()).
+        */
+       public void computeIn1Is() throws ExceptionInvalidRNATemplate {
+               checkIsValidTemplate();
+               
+               Iterator<EdgeEndPoint> iter = vertexIterator();
+               Set<RNATemplateHelix> knownHelices = new HashSet<RNATemplateHelix>();
+               while (iter.hasNext()) {
+                       EdgeEndPoint endPoint = iter.next();
+                       RNATemplateElement templateElement = endPoint.getElement();
+                       if (templateElement instanceof RNATemplateHelix) {
+                               RNATemplateHelix helix = (RNATemplateHelix) templateElement;
+                               if (! knownHelices.contains(helix)) {
+                                       // first time we meet this helix
+                                       switch (endPoint.getPosition()) {
+                                       case IN1:
+                                       case OUT1:
+                                               helix.setIn1Is(In1Is.IN1_IS_5PRIME);
+                                               break;
+                                       case IN2:
+                                       case OUT2:
+                                               helix.setIn1Is(In1Is.IN1_IS_3PRIME);
+                                               break;
+                                       }
+                                       knownHelices.add(helix);
+                               }
+                       }
+               }
+       }
+       
+       
+       
+       /**
+        * Remove the element from the template.
+        * The element is automatically disconnected from any other element.
+        * Returns true if and only if the element was present in the template,
+        * otherwise nothing was done.
+        */
+       public boolean removeElement(RNATemplateElement element) throws ExceptionInvalidRNATemplate {
+               if (elements.contains(element)) {
+                       element.disconnectFromAny();
+                       elements.remove(element);
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+       
+       
+       /**
+        * Position of an endpoint on an endpoint.
+        * Not all values make sense for any endpoint.
+        * For an helix, all four make sense, but for a non-paired
+        * sequence, only IN1 and OUT1 make sense.
+        */
+       public enum EdgeEndPointPosition {
+               IN1, IN2, OUT1, OUT2;
+       }
+       
+       
+       private static int NEXT_ID = 1;
+       
+       /**
+        * An endpoint of an RNA template,
+        * it can be an helix or a sequence of non-paired bases.
+        * 
+        * You cannot create an object of this class directly,
+        * use RNATemplateHelix or RNATemplateUnpairedSequence instead.
+        * 
+        * @author Raphael Champeimont
+        */
+       public abstract class RNATemplateElement {
+               
+               public int _id = NEXT_ID++;
+               
+               public String getName()
+               {return "RNATemplate"+_id; }
+               
+               
+               /**
+                * This variable is just there so that "this" can be accessed by a name
+                * from the internal class EdgeEndPoint.
+                */
+               private final RNATemplateElement element = this;
+               
+               /**
+                * When the endpoint is created, it is added to the list of elements
+                * in this template. To remove it, call RNATemplate.removeElement().
+                */
+               public RNATemplateElement() {
+                       elements.add(this);
+               }
+               
+               /**
+                * Disconnect this endpoint from any other elements it may be connected to.
+                */
+               public abstract void disconnectFromAny();
+               
+               /**
+                * Get the the IN endpoint in the case of a sequence
+                * and the IN1 endpoint in the case of an helix.
+                */
+               public abstract EdgeEndPoint getIn1EndPoint();
+
+               /**
+                * Returns the template to which this endpoint belongs.
+                */
+               public RNATemplate getParentTemplate() {
+                       return template;
+               }
+               
+               /**
+                * Provided endpoint is an endpoint of this endpoint, get the next
+                * endpoint, either on this same endpoint, or or the connected endpoint.
+                * Note that you should use the getNextEndPoint() method of the endpoint
+                * itself directly.
+                */
+               protected abstract EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint);
+               
+               /**
+                * Provided endpoint is an endpoint of this endpoint, get the previous
+                * endpoint, either on this same endpoint, or or the connected endpoint.
+                * Note that you should use the getPreviousEndPoint() method of the endpoint
+                * itself directly.
+                */
+               protected abstract EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint);
+
+               
+               /**
+                * An edge endpoint is where an edge can connect.
+                */
+               public class EdgeEndPoint {
+                       private EdgeEndPoint() {
+                       }
+                       
+                       /**
+                        * Get the next endpoint. If this endpoint is an "in" endpoint,
+                        * returns the corresponding "out" endpoint. If this endpoint
+                        * is an "out" endpoint, return the connected endpoint if there is
+                        * one, otherwise return null.
+                        */
+                       public EdgeEndPoint getNextEndPoint() {
+                               return element.getNextEndPoint(this);
+                       }
+                       
+                       
+                       /**
+                        * Same as getNextEndPoint(), but with the previous endpoint.
+                        */
+                       public EdgeEndPoint getPreviousEndPoint() {
+                               return element.getPreviousEndPoint(this);
+                       }
+                       
+                       
+                       /**
+                        * Get the position on the endpoint where this endpoint is.
+                        */
+                       public EdgeEndPointPosition getPosition() {
+                               return element.getPositionFromEndPoint(this);
+                       }
+                       
+                       
+                       private EdgeEndPoint otherEndPoint;
+                       
+                       /**
+                        * Returns the endpoint on which this edge endpoint is.
+                        */
+                       public RNATemplateElement getElement() {
+                               return element;
+                       }
+                       
+                       /**
+                        * Returns the other endpoint of the edge.
+                        * Will be null if there is no edge connecter to this endpoint.
+                        */
+                       public EdgeEndPoint getOtherEndPoint() {
+                               return otherEndPoint;
+                       }
+                       /**
+                        * Returns the endpoint at the other endpoint of the edge.
+                        * Will be null if there is no edge connecter to this endpoint.
+                        */
+                       public RNATemplateElement getOtherElement() {
+                               return (otherEndPoint != null) ? otherEndPoint.getElement() : null;
+                       }
+                       
+                       /**
+                        * Disconnect this endpoint from the other, ie. delete the edge
+                        * between them. Note that this will modify both endpoints, and that 
+                        * x.disconnect() is equivalent to x.getOtherEndPoint().disconnect().
+                        * If this endpoint is not connected, does nothing.
+                        */
+                       public void disconnect() {
+                               if (otherEndPoint != null) {
+                                       otherEndPoint.otherEndPoint = null;
+                                       otherEndPoint = null;
+                               }
+                       }
+                       
+                       /**
+                        * Tells whether this endpoint is connected with an edge to
+                        * an other endpoint.
+                        */
+                       public boolean isConnected() {
+                               return (otherEndPoint != null);
+                       }
+
+
+                       /**
+                        * Create an edge between two edge endpoints.
+                        * This is a symmetric operation and it will modify both endpoints.
+                        * It means x.connectTo(y) is equivalent to y.connectTo(x).
+                        * The edge endpoint must be free (ie. not yet connected).
+                        * Also, elements connected together must belong to the same template.
+                        */
+                       public void connectTo(EdgeEndPoint otherEndPoint) throws ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate {
+                               if (this.otherEndPoint != null || otherEndPoint.otherEndPoint != null) {
+                                       throw (new ExceptionEdgeEndpointAlreadyConnected());
+                               }
+                               if (template != otherEndPoint.getElement().getParentTemplate()) {
+                                       throw (new ExceptionInvalidRNATemplate("Elements from different templates cannot be connected with each other."));
+                               }
+                               this.otherEndPoint = otherEndPoint;
+                               otherEndPoint.otherEndPoint = this;
+                       }
+                       
+
+                       public String toString() {
+                               return "Edge endpoint on element " + element.toString() + " at position " + getPosition().toString();
+                       }
+               }
+               
+               
+               /**
+                * Get the EdgeEndPoint object corresponding to the the given
+                * position on this endpoint.
+                */
+               public abstract EdgeEndPoint getEndPointFromPosition(EdgeEndPointPosition position);
+               
+               
+               /**
+                * The inverse of getEndPointFromPosition.
+                */
+               public abstract EdgeEndPointPosition getPositionFromEndPoint(EdgeEndPoint endPoint);
+               
+               
+               /**
+                * Connect the endpoint at position positionHere of this endpoint
+                * to the otherEndPoint.
+                */
+               public void connectTo(
+                               EdgeEndPointPosition positionHere,
+                               EdgeEndPoint otherEndPoint)
+               throws ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate {
+                       EdgeEndPoint endPointHere = getEndPointFromPosition(positionHere);
+                       endPointHere.connectTo(otherEndPoint);
+               }
+               
+               /**
+                * Connect the endpoint at position positionHere of this endpoint
+                * to the endpoint of otherElement at position positionOnOtherElement.
+                * @throws ExceptionInvalidRNATemplate 
+                * @throws ExceptionEdgeEndpointAlreadyConnected, ExceptionEdgeEndpointAlreadyConnected 
+                */
+               public void connectTo(
+                               EdgeEndPointPosition positionHere,
+                               RNATemplateElement otherElement,
+                               EdgeEndPointPosition positionOnOtherElement)
+               throws ExceptionEdgeEndpointAlreadyConnected, ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate {
+                       EdgeEndPoint otherEndPoint = otherElement.getEndPointFromPosition(positionOnOtherElement);
+                       connectTo(positionHere, otherEndPoint);
+               }
+       
+       
+       }
+       
+
+       /**
+        * An helix in an RNA template.
+        * 
+        * @author Raphael Champeimont
+        */
+       public class RNATemplateHelix extends RNATemplateElement {
+               /**
+                * Number of base pairs in the helix.
+                */
+               private int length;
+               
+               /**
+                * Position of the helix start point,
+                * ie. the middle in the line [x,y] where (x,y)
+                * x is the base at the IN1 edge endpoint and
+                * y is the base at the OUT2 edge endpoint.
+                */
+               private Point2D.Double startPosition;
+               
+               /**
+                * Position of the helix end point,
+                * ie. the middle in the line [x,y] where (x,y)
+                * x is the base at the OUT1 edge endpoint and
+                * y is the base at the IN2 edge endpoint.
+                */
+               private Point2D.Double endPosition;
+               
+               
+               /**
+                * Tells whether the helix is flipped.
+                */
+               private boolean flipped = false;
+               
+               public boolean isFlipped() {
+                       return flipped;
+               }
+
+               public void setFlipped(boolean flipped) {
+                       this.flipped = flipped;
+               }
+               
+               /**
+                * For an helix, tells us whether IN1/OUT1 is the 5' strand
+                * (the first strand we meet if we follow the RNA sequence)
+                * or the 3' strand (the second we meet if we follow the RNA sequence).
+                * This information cannot be known locally, we need the complete
+                * template to compute it, see RNATemplate.computeIn1Is().
+                */
+               private In1Is in1Is = null;
+               
+               public In1Is getIn1Is() {
+                       return in1Is;
+               }
+
+               public void setIn1Is(In1Is in1Is) {
+                       this.in1Is = in1Is;
+               }
+               
+               
+               
+               /**
+                * A string displayed on the helix.
+                */
+               private String caption = null;
+               
+               public String getCaption() {
+                       return caption;
+               }
+
+               public void setCaption(String caption) {
+                       this.caption = caption;
+               }
+               
+               public boolean hasCaption() {
+                       return caption != null;
+               }
+
+               
+               
+               
+               /**
+                * If we go through all bases of the RNA from first to last,
+                * we will pass twice through this helix. On time, we arrive
+                * from in1, and leave by out2, and the other time we arrive from
+                * in2 and leave by out2.
+                * Whether we go through in1/out1 or in2/out2 the first time
+                * is written in the in1Is field.
+                */
+               private final EdgeEndPoint in1, out1, in2, out2;
+               private String _name;
+               
+               public RNATemplateHelix(String name) {
+                       in1 = new EdgeEndPoint();
+                       out1 = new EdgeEndPoint();
+                       in2 = new EdgeEndPoint();
+                       out2 = new EdgeEndPoint();
+                       _name = name;
+               }
+               
+               
+
+               
+               
+               public String toString() {
+                       return "Helix    @" + Integer.toHexString(hashCode()) + " len=" + length + " caption=" + caption;
+               }
+               
+               public String getName()
+               {return ""+_name; }
+               
+               
+
+               public int getLength() {
+                       return length;
+               }
+
+               public void setLength(int length) {
+                       this.length = length;
+               }
+
+               public Point2D.Double getStartPosition() {
+                       return startPosition;
+               }
+
+               public void setStartPosition(Point2D.Double startPosition) {
+                       this.startPosition = startPosition;
+               }
+
+               public Point2D.Double getEndPosition() {
+                       return endPosition;
+               }
+
+               public void setEndPosition(Point2D.Double endPosition) {
+                       this.endPosition = endPosition;
+               }
+
+               public EdgeEndPoint getIn1() {
+                       return in1;
+               }
+
+               public EdgeEndPoint getOut1() {
+                       return out1;
+               }
+
+               public EdgeEndPoint getIn2() {
+                       return in2;
+               }
+
+               public EdgeEndPoint getOut2() {
+                       return out2;
+               }
+
+               public void disconnectFromAny() {
+                       getIn1().disconnect();
+                       getIn2().disconnect();
+                       getOut1().disconnect();
+                       getOut2().disconnect();
+               }
+
+
+               protected EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint) {
+                       if (endpoint == in1) {
+                               return out1;
+                       } else if (endpoint == in2) {
+                               return out2;
+                       } else {
+                               return endpoint.getOtherEndPoint();
+                       }
+               }
+
+
+               protected EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint) {
+                       if (endpoint == out1) {
+                               return in1;
+                       } else if (endpoint == out2) {
+                               return in2;
+                       } else {
+                               return endpoint.getOtherEndPoint();
+                       }
+               }
+
+
+               public EdgeEndPoint getIn1EndPoint() {
+                       return in1;
+               }
+
+               public EdgeEndPoint getEndPointFromPosition(
+                               EdgeEndPointPosition position) {
+                       switch (position) {
+                       case IN1:
+                               return getIn1();
+                       case IN2:
+                               return getIn2();
+                       case OUT1:
+                               return getOut1();
+                       case OUT2:
+                               return getOut2();
+                       default:
+                               return null;
+                       }
+               }
+
+               public EdgeEndPointPosition getPositionFromEndPoint(
+                               EdgeEndPoint endPoint) {
+                       if (endPoint == in1) {
+                               return EdgeEndPointPosition.IN1;
+                       } else if (endPoint == in2) {
+                               return EdgeEndPointPosition.IN2;
+                       } else if (endPoint == out1) {
+                               return EdgeEndPointPosition.OUT1;
+                       } else if (endPoint == out2) {
+                               return EdgeEndPointPosition.OUT2;
+                       } else {
+                               return null;
+                       }
+               }
+
+
+       }
+
+
+
+       /**
+        * A sequence of non-paired bases in an RNA template.
+        * 
+        * @author Raphael Champeimont
+        */
+       public class RNATemplateUnpairedSequence extends RNATemplateElement {
+               /**
+                * Number of (non-paired) bases. 
+                */
+               private int length;
+               
+               private static final double defaultTangentVectorAngle  = Math.PI / 2;
+               private static final double defaultTangentVectorLength = 100;
+               
+               /**
+                * The sequence is drawn along a cubic Bezier curve.
+                * The curve can be defined by 2 vectors, one for the start of the line
+                * and the other for the end. They are the tangents to the line at
+                * the beginning and the end of the line.
+                * Each vector can be defined by its length and its absolute angle.
+                * The angles are given in radians.
+                */
+               private double inTangentVectorAngle   = defaultTangentVectorAngle,
+                              inTangentVectorLength  = defaultTangentVectorLength,
+                              outTangentVectorAngle  = defaultTangentVectorAngle,
+                              outTangentVectorLength = defaultTangentVectorLength;
+               
+               
+               
+               
+               /**
+                * Position of the begginning (at the "in" endpoint) of the line.
+                * It is only useful when the sequence is not yet connected to an helix.
+                * (Otherwise we can deduce it from this helix position).
+                */
+               private Point2D.Double vertex5;
+               
+               /**
+                * Position of the end (at the "out" endpoint) of the line.
+                * It is only useful when the sequence is not yet connected to an helix.
+                * (Otherwise we can deduce it from this helix position).
+                */
+               private Point2D.Double vertex3;
+               
+               
+               public Point2D.Double getVertex5() {
+                       return vertex5;
+               }
+
+               public void setVertex5(Point2D.Double vertex5) {
+                       this.vertex5 = vertex5;
+               }
+
+               public Point2D.Double getVertex3() {
+                       return vertex3;
+               }
+
+               public void setVertex3(Point2D.Double vertex3) {
+                       this.vertex3 = vertex3;
+               }
+
+
+               
+               
+               /**
+                * The helixes connected on both sides.
+                * They must be helixes because only helixes have absolute positions,
+                * and the positions of the starting and ending points of the sequence
+                * are those stored in the helixes.
+                */
+               private final EdgeEndPoint in, out;
+               
+               private String _name;
+               
+               public RNATemplateUnpairedSequence(String name) {
+                       in = new EdgeEndPoint();
+                       out = new EdgeEndPoint();
+                       _name = name;
+               }
+
+               
+               public String toString() {
+                       return "Sequence @" + Integer.toHexString(hashCode()) + " len=" + length;
+               }
+               
+               public String getName()
+               {return ""+_name; }
+               
+               
+               
+               public int getLength() {
+                       return length;
+               }
+
+               public void setLength(int length) {
+                       this.length = length;
+               }
+
+               public double getInTangentVectorAngle() {
+                       return inTangentVectorAngle;
+               }
+
+               public void setInTangentVectorAngle(double inTangentVectorAngle) {
+                       this.inTangentVectorAngle = inTangentVectorAngle;
+               }
+
+               public double getInTangentVectorLength() {
+                       return inTangentVectorLength;
+               }
+
+               public void setInTangentVectorLength(double inTangentVectorLength) {
+                       this.inTangentVectorLength = inTangentVectorLength;
+               }
+
+               public double getOutTangentVectorAngle() {
+                       return outTangentVectorAngle;
+               }
+
+               public void setOutTangentVectorAngle(double outTangentVectorAngle) {
+                       this.outTangentVectorAngle = outTangentVectorAngle;
+               }
+
+               public double getOutTangentVectorLength() {
+                       return outTangentVectorLength;
+               }
+
+               public void setOutTangentVectorLength(double outTangentVectorLength) {
+                       this.outTangentVectorLength = outTangentVectorLength;
+               }
+
+               public EdgeEndPoint getIn() {
+                       return in;
+               }
+
+               public EdgeEndPoint getOut() {
+                       return out;
+               }
+
+               public void disconnectFromAny() {
+                       getIn().disconnect();
+                       getOut().disconnect();
+               }
+
+
+               protected EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint) {
+                       if (endpoint == in) {
+                               return out;
+                       } else {
+                               return endpoint.getOtherEndPoint();
+                       }
+               }
+
+               protected EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint) {
+                       if (endpoint == out) {
+                               return in;
+                       } else {
+                               return endpoint.getOtherEndPoint();
+                       }
+               }
+               
+               public EdgeEndPoint getIn1EndPoint() {
+                       return in;
+               }
+
+
+               public EdgeEndPoint getEndPointFromPosition(
+                               EdgeEndPointPosition position) {
+                       switch (position) {
+                       case IN1:
+                               return getIn();
+                       case OUT1:
+                               return getOut();
+                       default:
+                               return null;
+                       }
+               }
+
+
+               public EdgeEndPointPosition getPositionFromEndPoint(
+                               EdgeEndPoint endPoint) {
+                       if (endPoint == in) {
+                               return EdgeEndPointPosition.IN1;
+                       } else if (endPoint == out) {
+                               return EdgeEndPointPosition.OUT1;
+                       } else {
+                               return null;
+                       }
+               }
+
+               
+       }
+
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateAlign.java b/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateAlign.java
new file mode 100644 (file)
index 0000000..358e280
--- /dev/null
@@ -0,0 +1,418 @@
+package fr.orsay.lri.varna.models.templates;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
+import fr.orsay.lri.varna.models.treealign.AlignedNode;
+import fr.orsay.lri.varna.models.treealign.RNANodeValue;
+import fr.orsay.lri.varna.models.treealign.RNANodeValue2;
+import fr.orsay.lri.varna.models.treealign.RNATree2;
+import fr.orsay.lri.varna.models.treealign.RNATree2Exception;
+import fr.orsay.lri.varna.models.treealign.Tree;
+import fr.orsay.lri.varna.models.treealign.TreeAlign;
+import fr.orsay.lri.varna.models.treealign.TreeAlignException;
+import fr.orsay.lri.varna.models.treealign.TreeAlignResult;
+
+/**
+ * This class is about the alignment between a tree of RNANodeValue2
+ * and a tree of RNANodeValueTemplate.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNATemplateAlign {
+       
+       // We check this node can be part of a non-broken helix.
+       private static boolean canBePartOfAnHelix(RNANodeValue2 leftNodeValue) {
+               return (leftNodeValue != null) && leftNodeValue.isSingleNode() && leftNodeValue.getNode().getRightBasePosition() > 0;
+       }
+       
+       private static boolean canBePartOfASequence(RNANodeValue2 leftNodeValue) {
+               return (leftNodeValue != null) && !leftNodeValue.isSingleNode();
+       }
+       
+       private static boolean canBePartOfABrokenHelix(RNANodeValue2 leftNodeValue) {
+               return (leftNodeValue != null) && leftNodeValue.isSingleNode() && leftNodeValue.getNode().getRightBasePosition() < 0;
+       }
+       
+       
+       /**
+        * This method takes an alignment between a tree of RNANodeValue2
+        * of RNANodeValue and a tree of RNANodeValue2 of RNANodeValueTemplate,
+        * and the original RNA object that was used to create the first tree
+        * in the alignment.
+        * It returns the corresponding RNATemplateMapping.
+        */
+       public static RNATemplateMapping makeTemplateMapping(TreeAlignResult<RNANodeValue2,RNANodeValueTemplate> alignResult, RNA rna) throws RNATemplateMappingException {
+               RNATemplateMapping mapping = new RNATemplateMapping();
+               Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>> alignment = alignResult.getAlignment();
+               mapping.setDistance(alignResult.getDistance());
+               
+               // Map sequences and helices together, without managing pseudoknots
+               {
+                       // We will go through the tree using a DFS
+                       // The reason why this algorithm is not trivial is that we may have
+                       // a longer helix on the RNA side than on the template side, in which
+                       // case some nodes on the RNA side are going to be alone while we
+                       // would want them to be part of the helix.
+                       RNATemplateHelix currentHelix = null;
+                       LinkedList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> remainingNodes = new LinkedList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>>();
+                       List<RNANodeValue2> nodesInSameHelix = new LinkedList<RNANodeValue2>();
+                       remainingNodes.add(alignment);
+                       while (!remainingNodes.isEmpty()) {
+                               Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>> node = remainingNodes.getLast();
+                               remainingNodes.removeLast();
+                               
+                               Tree<RNANodeValue2> leftNode = node.getValue().getLeftNode();
+                               Tree<RNANodeValueTemplate> rightNode = node.getValue().getRightNode();
+                               
+                               // Do we have something on RNA side?
+                               if (leftNode != null && leftNode.getValue() != null) {
+                                       RNANodeValue2 leftNodeValue = leftNode.getValue();
+                                       
+                                       // Do we have something on template side?
+                                       if (rightNode != null && rightNode.getValue() != null) {
+                                               RNANodeValueTemplate rightNodeValue = rightNode.getValue();
+                                               
+                                               if (rightNodeValue instanceof RNANodeValueTemplateBasePair
+                                                               && canBePartOfAnHelix(leftNodeValue)) {
+                                                       RNATemplateHelix helix = ((RNANodeValueTemplateBasePair) rightNodeValue).getHelix();
+                                                       currentHelix = helix;
+                                                       int i = leftNodeValue.getNode().getLeftBasePosition();
+                                                       int j = leftNodeValue.getNode().getRightBasePosition();
+                                                       mapping.addCouple(i, helix);
+                                                       mapping.addCouple(j, helix);
+                                                       
+                                                       // Maybe we have marked nodes as part of the same helix
+                                                       // when we didn't know yet which helix it was.
+                                                       if (nodesInSameHelix.size() > 0) {
+                                                               for (RNANodeValue2 v: nodesInSameHelix) {
+                                                                       int k = v.getNode().getLeftBasePosition();
+                                                                       int l = v.getNode().getRightBasePosition();
+                                                                       // We want to check nodesInSameHelix is a parent helix and not a sibling.
+                                                                       boolean validExtension = (k < i) && (j < l);
+                                                                       if (validExtension) {
+                                                                               mapping.addCouple(v.getNode().getLeftBasePosition(), helix);
+                                                                               mapping.addCouple(v.getNode().getRightBasePosition(), helix);
+                                                                       }       
+                                                               }
+                                                       }
+                                                       nodesInSameHelix.clear();
+                                                       
+                                               } else if (rightNodeValue instanceof RNANodeValueTemplateSequence
+                                                               && canBePartOfASequence(leftNodeValue)) {
+                                                       currentHelix = null;
+                                                       nodesInSameHelix.clear();
+                                                       RNATemplateUnpairedSequence sequence = ((RNANodeValueTemplateSequence) rightNodeValue).getSequence();
+                                                       for (RNANodeValue nodeValue: leftNode.getValue().getNodes()) {
+                                                               mapping.addCouple(nodeValue.getLeftBasePosition(), sequence);
+                                                       }
+                                               } else {
+                                                       // Pseudoknot in template
+                                                       currentHelix = null;
+                                                       nodesInSameHelix.clear();
+                                               }
+                                               
+                                       } else {
+                                               // We have nothing on template side
+                                               
+                                               if (canBePartOfAnHelix(leftNodeValue)) {
+                                                       if (currentHelix != null) {
+                                                               // We may be in this case when the RNA
+                                                               // contains a longer helix than in the template 
+                                                               int i = leftNodeValue.getNode().getLeftBasePosition();
+                                                               int j = leftNodeValue.getNode().getRightBasePosition();
+                                                               int k = Integer.MAX_VALUE;
+                                                               int l = Integer.MIN_VALUE;
+                                                               for (int b: mapping.getAncestor(currentHelix)) {
+                                                                       k = Math.min(k, b);
+                                                                       l = Math.max(l, b);
+                                                               }
+                                                               // We want to check currentHelix is a parent helix and not a sibling.
+                                                               boolean validExtension = (k < i) && (j < l);
+                                                               if (validExtension) {
+                                                                       mapping.addCouple(i, currentHelix);
+                                                                       mapping.addCouple(j, currentHelix);
+                                                               }       
+                                                       } else {
+                                                               // Maybe this left node is part of an helix
+                                                               // which is smaller in the template
+                                                               nodesInSameHelix.add(leftNodeValue);
+                                                       }
+                                               }
+                                       }
+                               }
+                               
+                               
+                               // If this node has children, add them in the stack
+                               List<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> children = node.getChildren();
+                               int n = children.size();
+                               if (n > 0) {
+                                       int helixChildren = 0;
+                                       // For each subtree, we want the sequences (in RNA side) to be treated first
+                                       // and then the helix nodes, because finding an aligned sequence tells
+                                       // us we cannot grow a current helix
+                                       ArrayList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> addToStack1 = new ArrayList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>>();
+                                       ArrayList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> addToStack2 = new ArrayList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>>();
+                                       for (int i=0; i<n; i++) {
+                                               Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>> child = children.get(i);
+                                               Tree<RNANodeValue2> RNAchild = child.getValue().getLeftNode();
+                                               if (RNAchild != null
+                                                               && RNAchild.getValue() != null
+                                                               && (canBePartOfAnHelix(RNAchild.getValue()) || canBePartOfABrokenHelix(RNAchild.getValue()) )) {
+                                                       helixChildren++;
+                                                       addToStack2.add(child);
+                                               } else {
+                                                       addToStack1.add(child);
+                                               }
+                                       }
+                                       // We add the children in their reverse order so they
+                                       // are given in the original order by the iterator
+                                       for (int i=addToStack2.size()-1; i>=0; i--) {
+                                               remainingNodes.add(addToStack2.get(i));
+                                       }
+                                       for (int i=addToStack1.size()-1; i>=0; i--) {
+                                               remainingNodes.add(addToStack1.get(i));
+                                       }
+                                       if (helixChildren >= 2) {
+                                               // We cannot "grow" the current helix, because we have a multiloop
+                                               // in the RNA.
+                                               currentHelix = null;
+                                               //nodesInSameHelix.clear();
+                                       }
+                               }
+                       }
+               }
+               
+               
+               // Now recover pseudoknots (broken helices)
+               {
+                       // First create a temporary mapping with broken helices
+                       LinkedList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> remainingNodes = new LinkedList<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>>();
+                       remainingNodes.add(alignment);
+                       RNATemplateMapping tempPKMapping = new RNATemplateMapping();
+                       while (!remainingNodes.isEmpty()) {
+                               Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>> node = remainingNodes.getLast();
+                               remainingNodes.removeLast();
+                               List<Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>>> children = node.getChildren();
+                               int n = children.size();
+                               if (n > 0) {
+                                       for (int i=n-1; i>=0; i--) {
+                                               // We add the children in their reverse order so they
+                                               // are given in the original order by the iterator
+                                               remainingNodes.add(children.get(i));
+                                       }
+                                       List<RNANodeValue2> nodesInSameHelix = new LinkedList<RNANodeValue2>();
+                                       RNATemplateHelix currentHelix = null;
+                                       for (Tree<AlignedNode<RNANodeValue2,RNANodeValueTemplate>> child: node.getChildren()) {
+                                               Tree<RNANodeValue2> leftNode = child.getValue().getLeftNode();
+                                               Tree<RNANodeValueTemplate> rightNode = child.getValue().getRightNode();
+                                               
+                                               if (leftNode != null && leftNode.getValue() != null) {
+                                                       RNANodeValue2 leftNodeValue = leftNode.getValue();
+                                                       // We have a real left (RNA side) node
+                                                       
+                                                       if (rightNode != null && rightNode.getValue() != null) {
+                                                               // We have a real right (template side) node
+                                                               RNANodeValueTemplate rightNodeValue = rightNode.getValue();
+                                                               
+                                                               if (rightNodeValue instanceof RNANodeValueTemplateBrokenBasePair
+                                                                               && canBePartOfABrokenHelix(leftNodeValue)) {
+                                                                       RNATemplateHelix helix = ((RNANodeValueTemplateBrokenBasePair) rightNodeValue).getHelix();
+                                                                       currentHelix = helix;
+                                                                       tempPKMapping.addCouple(leftNodeValue.getNode().getLeftBasePosition(), helix);
+                                                                       
+                                                                       // Maybe we have marked nodes as part of the same helix
+                                                                       // when we didn't know yet which helix it was.
+                                                                       for (RNANodeValue2 v: nodesInSameHelix) {
+                                                                               tempPKMapping.addCouple(v.getNode().getLeftBasePosition(), helix);
+                                                                       }
+                                                                       nodesInSameHelix.clear();
+                                                               } else {
+                                                                       currentHelix = null;
+                                                                       nodesInSameHelix.clear();
+                                                               }
+                                                       } else {
+                                                               // We have no right (template side) node
+                                                               if (canBePartOfABrokenHelix(leftNodeValue)) {
+                                                                       if (currentHelix != null) {
+                                                                               // We may be in this case if the RNA sequence
+                                                                               // contains a longer helix than in the template
+                                                                               tempPKMapping.addCouple(leftNodeValue.getNode().getLeftBasePosition(), currentHelix);
+                                                                       } else {
+                                                                               // Maybe this left node is part of an helix
+                                                                               // which is smaller in the template
+                                                                               nodesInSameHelix.add(leftNodeValue);
+                                                                       }
+                                                               } else {
+                                                                       currentHelix = null;
+                                                                       nodesInSameHelix.clear();
+                                                               }
+                                                       }
+                                               } else {
+                                                       currentHelix = null;
+                                                       nodesInSameHelix.clear();
+                                               }
+                                       }
+                               }
+                       }
+                       
+                       
+                       // As parts of broken helices were aligned independently,
+                       // we need to check for consistency, ie. keep only bases for
+                       // which the associated base is also aligned with the same helix.
+                       for (RNATemplateElement element: tempPKMapping.getTargetElemsAsSet()) {
+                               RNATemplateHelix helix = (RNATemplateHelix) element;
+                               HashSet<Integer> basesInHelix = new HashSet<Integer>(tempPKMapping.getAncestor(helix));
+                               for (int baseIndex: basesInHelix) {
+                                       System.out.println("PK: " + helix + " aligned with " + baseIndex);
+                                       boolean baseOK = false;
+                                       // Search for an associated base aligned with the same helix
+                                       ArrayList<ModeleBP> auxBasePairs = rna.getAuxBPs(baseIndex);
+                                       for (ModeleBP auxBasePair: auxBasePairs) {
+                                               int partner5 = ((ModeleBaseNucleotide) auxBasePair.getPartner5()).getIndex();
+                                               int partner3 = ((ModeleBaseNucleotide) auxBasePair.getPartner3()).getIndex();
+                                               if (baseIndex == partner5) {
+                                                       if (basesInHelix.contains(partner3)) {
+                                                               baseOK = true;
+                                                               break;
+                                                       }
+                                               } else if (baseIndex == partner3) {
+                                                       if (basesInHelix.contains(partner5)) {
+                                                               baseOK = true;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                                       if (baseOK) {
+                                               // Add it to the real mapping
+                                               mapping.addCouple(baseIndex, helix);
+                                       }
+                               }
+                       }
+               }
+               
+               
+               return mapping;
+       }
+       
+       
+       
+       public static void printMapping(RNATemplateMapping mapping, RNATemplate template, String sequence) {
+               Iterator<RNATemplateElement> iter = template.rnaIterator();
+               while (iter.hasNext()) {
+                       RNATemplateElement element = iter.next();
+                       System.out.println(element.toString());
+                       ArrayList<Integer> A = mapping.getAncestor(element);
+                       if (A != null) {
+                               RNATemplateAlign.printIntArrayList(A);
+                               for (int n=A.size(), i=0; i<n; i++) {
+                                       System.out.print("\t" + sequence.charAt(A.get(i)));
+                               }
+                               System.out.println("");
+                       } else {
+                               System.out.println("\tno match");
+                       }
+               }
+       }
+       
+       
+       /**
+        * Align the given RNA with the given RNA template.
+        */
+       public static TreeAlignResult<RNANodeValue2,RNANodeValueTemplate> alignRNAWithTemplate(RNA rna, RNATemplate template) throws RNATemplateDrawingAlgorithmException {
+               try {
+                       Tree<RNANodeValue2> rnaAsTree = RNATree2.RNATree2FromRNA(rna);
+                       Tree<RNANodeValueTemplate> templateAsTree = template.toTree();
+                       TreeAlign<RNANodeValue2,RNANodeValueTemplate> treeAlign = new TreeAlign<RNANodeValue2,RNANodeValueTemplate>(new RNANodeValue2TemplateDistance());
+                       TreeAlignResult<RNANodeValue2,RNANodeValueTemplate> result = treeAlign.align(rnaAsTree, templateAsTree);
+                       return result;
+               } catch (RNATree2Exception e) {
+                       throw (new RNATemplateDrawingAlgorithmException("RNATree2Exception: " + e.getMessage()));
+               } catch (ExceptionInvalidRNATemplate e) {
+                       throw (new RNATemplateDrawingAlgorithmException("ExceptionInvalidRNATemplate: " + e.getMessage()));
+               } catch (TreeAlignException e) {
+                       throw (new RNATemplateDrawingAlgorithmException("TreeAlignException: " + e.getMessage()));
+               }
+       }
+       
+       /**
+        * Map an RNA with an RNATemplate using tree alignment.
+        */
+       public static RNATemplateMapping mapRNAWithTemplate(RNA rna, RNATemplate template) throws RNATemplateDrawingAlgorithmException {
+               try {
+                       TreeAlignResult<RNANodeValue2,RNANodeValueTemplate> alignResult = RNATemplateAlign.alignRNAWithTemplate(rna, template); 
+                       RNATemplateMapping mapping = RNATemplateAlign.makeTemplateMapping(alignResult, rna);
+                       return mapping;
+               } catch (RNATemplateMappingException e) {
+                       e.printStackTrace();
+                       throw (new RNATemplateDrawingAlgorithmException("RNATemplateMappingException: " + e.getMessage()));
+               }
+       }
+       
+       
+
+       /**
+        * Print an integer array.
+        */
+       public static void printIntArray(int[] A) {
+               for (int i=0; i<A.length; i++) {
+                       System.out.print("\t" + A[i]);
+               }
+               System.out.println("");
+       }
+
+       /**
+        * Print an integer ArrayList.
+        */
+       public static void printIntArrayList(ArrayList<Integer> A) {
+               for (int i=0; i<A.size(); i++) {
+                       System.out.print("\t" + A.get(i));
+               }
+               System.out.println("");
+       }
+
+       /**
+        * Print an matrix of shorts.
+        */
+       public static void printShortMatrix(short[][] M) {
+               System.out.println("Begin matrix");
+               for (int i=0; i<M.length; i++) {
+                       for (int j=0; j<M[i].length; j++) {
+                               System.out.print("\t" + M[i][j]);
+                       }
+                       System.out.println("");
+               }
+               System.out.println("End matrix");
+       }
+       
+       /**
+        * Convert a list of integers into an array of integers.
+        * The returned arrays is freshly allocated.
+        * Returns null if given null.
+        */
+       public static int[] intArrayFromList(List<Integer> l) {
+               if (l != null) {
+                       int n = l.size();
+                       int[] result = new int[n];
+                       for (int i=0; i<n; i++) {
+                               result[i] = l.get(i);
+                       }
+                       return result;
+               } else {
+                       return null;
+               }
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateDrawingAlgorithmException.java b/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateDrawingAlgorithmException.java
new file mode 100644 (file)
index 0000000..818ef0f
--- /dev/null
@@ -0,0 +1,18 @@
+package fr.orsay.lri.varna.models.templates;
+
+import fr.orsay.lri.varna.exceptions.ExceptionDrawingAlgorithm;
+
+/**
+ * Exception thrown in case of failure of the template-based
+ * RNA drawing algorithm.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNATemplateDrawingAlgorithmException extends ExceptionDrawingAlgorithm {
+
+       private static final long serialVersionUID = 1701307036024533400L;
+
+       public RNATemplateDrawingAlgorithmException(String message) {
+               super(message);
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMapping.java b/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMapping.java
new file mode 100644 (file)
index 0000000..39d76d0
--- /dev/null
@@ -0,0 +1,158 @@
+package fr.orsay.lri.varna.models.templates;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import fr.orsay.lri.varna.applications.templateEditor.Couple;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
+import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
+
+/**
+ * A RNATemplateMapping is a mapping between bases in an RNA sequence
+ * and elements in a RNA template.
+ * A base is mapped to only one template element
+ * but a template element can be mapped to several bases.
+ * This class is designed to be similar to the Mapping class.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNATemplateMapping {
+       private Map<Integer, RNATemplateElement> map = new HashMap<Integer, RNATemplateElement>();
+       private Map<RNATemplateElement, ArrayList<Integer>> invmap = new HashMap<RNATemplateElement, ArrayList<Integer>>();
+       
+       /**
+        * Alignment distance.
+        */
+       private double distance;
+       
+       
+       public double getDistance() {
+               return distance;
+       }
+       
+       public void setDistance(double distance) {
+               this.distance = distance;
+       }
+       
+       /**
+        * Tell this mapping object that this base index and this element are
+        * mapped with each other. This will throw RNATemplateMappingException
+        * and do nothing if the base index is already in the mapping.
+        */
+       public void addCouple(int baseIndex, RNATemplateElement templateElement) throws RNATemplateMappingException {
+               if (map.containsKey(baseIndex)) {
+                       throw (new RNATemplateMappingException("Base index already in mapping: " + baseIndex));
+               }
+               if (baseIndex < 0) {
+                       throw (new RNATemplateMappingException("Invalid base index: " + baseIndex));
+               }
+               map.put(baseIndex, templateElement);
+               if (!invmap.containsKey(templateElement)) {
+                       invmap.put(templateElement, new ArrayList<Integer>());
+               }
+               invmap.get(templateElement).add(baseIndex);
+       }
+
+
+       public String showCompact(RNA r)
+       {
+               HashMap<String,Couple<Integer,Integer> > ranges = new HashMap<String,Couple<Integer,Integer> >();
+               for(int i:map.keySet())
+               {
+                       RNATemplateElement t = map.get(i);
+                       String k = t.getName();
+                       if (t instanceof RNATemplate.RNATemplateHelix)
+                       {
+                               k += " (" + ((RNATemplateHelix) t).getCaption() + ")";
+                               ModeleBase mb = r.getBaseAt(i);
+                               if (mb.getElementStructure()>i)
+                                 k = k+":5'";
+                               else
+                                 k = k+":3'";
+                       }
+                       if (!ranges.containsKey(k))
+                       {  ranges.put(k, new Couple<Integer,Integer>(Integer.MAX_VALUE,Integer.MIN_VALUE));  }
+                       Couple<Integer,Integer> c = ranges.get(k);
+                       c.first = Math.min(c.first, i);
+                       c.second = Math.max(c.second, i);                       
+               }
+               String result = "";
+               for(String k:ranges.keySet())
+               {
+                       Couple<Integer,Integer> c = ranges.get(k);
+                       RNATemplateElement t = map.get(c.first);
+                       String type = ((t instanceof RNATemplate.RNATemplateHelix)?"strand":"loop");
+                       if (t instanceof RNATemplate.RNATemplateHelix)
+                       {
+                               if (k.endsWith("5'"))
+                               {
+                                       Couple<Integer,Integer> c3 = ranges.get(k.replace("5'", "3'"));
+                                       result += "dummyID\t1\t"+k.replace(":5'", "")+"\t"+type+"\t"+c.first+"-"+c.second+":"+c3.first+"-"+c3.second+"\n";
+                               }
+                       }
+                       else if (t instanceof RNATemplate.RNATemplateUnpairedSequence)
+                       {
+                         result += "dummyID\t1\t"+k+"\t"+type+"\t"+c.first+"-"+c.second+"\n";
+                       }
+               }
+               result += "alignment distance = " + distance;
+               return result;
+       }
+       
+       
+       /**
+        * If the given base index is in the mapping, return the
+        * corresponding template element, otherwise return null.
+        */
+       public RNATemplateElement getPartner(int baseIndex) {
+               if (map.containsKey(baseIndex)) {
+                       return map.get(baseIndex);
+               } else {
+                       return null;
+               }
+       }
+       
+       
+       
+       /**
+        * If the given template element is in the mapping, return an ArrayList
+        * containing the corresponding base indexes, otherwise return null.
+        * Note that you should not modify the returned ArrayList because
+        * no copy is made, so if you modify it this mapping object will
+        * contain inconsistent data.
+        */
+       public ArrayList<Integer> getAncestor(RNATemplateElement templateElement) {
+               if (invmap.containsKey(templateElement)) {
+                       return invmap.get(templateElement);
+               } else {
+                       return null;
+               }
+       }
+       
+       
+       
+       /**
+        * Return a set containing all the base indexes in the mapping.
+        * You should not modify the returned set.
+        */
+       public Set<Integer> getSourceElemsAsSet() {
+               return map.keySet();
+       }
+       
+       
+       
+       /**
+        * Return a set containing all the template elements in the mapping.
+        * You should not modify the return set.
+        */
+       public Set<RNATemplateElement> getTargetElemsAsSet() {
+               return invmap.keySet();
+       }
+       
+       
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMappingException.java b/srcjar/fr/orsay/lri/varna/models/templates/RNATemplateMappingException.java
new file mode 100644 (file)
index 0000000..3b689f1
--- /dev/null
@@ -0,0 +1,19 @@
+package fr.orsay.lri.varna.models.templates;
+
+/**
+ * This exception is thrown when we discover that a template is invalid
+ * (it contains impossible connections between elements).
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNATemplateMappingException extends Exception {
+       
+       private static final long serialVersionUID = -201638492590138354L;
+
+       public RNATemplateMappingException(String message) {
+               super(message);
+       }
+       
+       public RNATemplateMappingException() {
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/templates/TODO.txt b/srcjar/fr/orsay/lri/varna/models/templates/TODO.txt
new file mode 100644 (file)
index 0000000..1057527
--- /dev/null
@@ -0,0 +1,7 @@
+TODO for the template-based RNA drawing algorithm:
+- Calculer le tableau centers[] entièrement dans l'algo de tracé.
+- RNATemplateAlign.multiPassMap() is currently just an alias
+  for singlePassMap(), but it should me multi-pass.
+- Decalage des helices en absence de PK
+- Ellipses pour non-appariees
+- couts affine
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/AlignedNode.java b/srcjar/fr/orsay/lri/varna/models/treealign/AlignedNode.java
new file mode 100644 (file)
index 0000000..9f42890
--- /dev/null
@@ -0,0 +1,48 @@
+package fr.orsay.lri.varna.models.treealign;
+
+
+/**
+ * The type of node values in an alignment.
+ * Contains a reference to both original nodes.
+ * This class implements GraphvizDrawableNodeValue but it will only work
+ * if the original nodes implement it.
+ * @author Raphael Champeimont
+ * @param <OriginalNodeValueType1> The type of values in the original first tree.
+ * @param <OriginalNodeValueType2> The type of values in the original second tree.
+ */
+public class AlignedNode<OriginalNodeValueType1, OriginalNodeValueType2> implements GraphvizDrawableNodeValue {
+       private Tree<OriginalNodeValueType1> leftNode;
+       private Tree<OriginalNodeValueType2> rightNode;
+
+       public Tree<OriginalNodeValueType1> getLeftNode() {
+               return leftNode;
+       }
+
+       public void setLeftNode(Tree<OriginalNodeValueType1> leftNode) {
+               this.leftNode = leftNode;
+       }
+
+       public Tree<OriginalNodeValueType2> getRightNode() {
+               return rightNode;
+       }
+
+       public void setRightNode(Tree<OriginalNodeValueType2> rightNode) {
+               this.rightNode = rightNode;
+       }
+       
+       private String maybeNodeToGraphvizNodeName(Tree <? extends GraphvizDrawableNodeValue> tree) {
+               return (tree != null && tree.getValue() != null) ? tree.getValue().toGraphvizNodeName() : "_";
+       }
+
+       /**
+        * This method will work only if the left and right node
+        * already implement GraphvizDrawableNodeValue.
+        */
+       @SuppressWarnings("unchecked")
+       public String toGraphvizNodeName() {
+               return "(" + maybeNodeToGraphvizNodeName((Tree) leftNode)
+               + "," + maybeNodeToGraphvizNodeName((Tree) rightNode) + ")";
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance2.java b/srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance2.java
new file mode 100644 (file)
index 0000000..f58bcbb
--- /dev/null
@@ -0,0 +1,39 @@
+package fr.orsay.lri.varna.models.treealign;
+
+
+/**
+ * This distance is such that a substitution costs nothing.
+ * 
+ * @author Raphael Champeimont
+ *
+ */
+public class ExampleDistance2 implements TreeAlignLabelDistanceSymmetric<RNANodeValue2> {
+       public double f(RNANodeValue2 v1, RNANodeValue2 v2) {
+               if (v1 == null) {
+                       if (v2 == null) {
+                               return 0;
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               return v2.getNodes().size();
+                       } else { // v2 is a single node
+                               return 2;
+                       }
+               } else if (!v1.isSingleNode()) { // v1 is a list of bases
+                       if (v2 == null) {
+                               return v1.getNodes().size();
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               return Math.abs(v2.getNodes().size() - v1.getNodes().size());
+                       } else { // v2 is a single node
+                               return 2 + v1.getNodes().size();
+                       }
+               } else { // v1 is a single node
+                       // all the same as when v1 == null
+                       if (v2 == null) {
+                               return 2;
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               return 2 + v2.getNodes().size();
+                       } else { // v2 is a single node
+                               return 0;
+                       }
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance3.java b/srcjar/fr/orsay/lri/varna/models/treealign/ExampleDistance3.java
new file mode 100644 (file)
index 0000000..cba2a8a
--- /dev/null
@@ -0,0 +1,194 @@
+package fr.orsay.lri.varna.models.treealign;
+import java.util.ArrayList;
+
+
+
+/**
+ * 
+ * 
+ * @author Raphael Champeimont
+ */
+public class ExampleDistance3 implements TreeAlignLabelDistanceSymmetric<RNANodeValue2> {
+       public double f(RNANodeValue2 v1, RNANodeValue2 v2) {
+               if (v1 == null) {
+                       if (v2 == null) {
+                               return 0;
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               // We insert all bases, with a cost of 1 for each base.
+                               return v2.getNodes().size();
+                       } else { // v2 is a single node
+                               return 2;
+                       }
+               } else if (!v1.isSingleNode()) { // v1 is a list of bases
+                       if (v2 == null) {
+                               return v1.getNodes().size();
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               // We compute the sequence distance
+                               return alignSequenceNodes(v1, v2).getDistance();
+                       } else { // v2 is a single node
+                               return 2 + v1.getNodes().size();
+                       }
+               } else { // v1 is a single node
+                       // all the same as when v1 == null
+                       if (v2 == null) {
+                               return 2;
+                       } else if (!v2.isSingleNode()) { // v2 is a list of bases
+                               return 2 + v2.getNodes().size();
+                       } else { // v2 is a single node
+                               String l1 = v1.getNode().getLeftNucleotide();
+                               String r1 = v1.getNode().getRightNucleotide();
+                               String l2 = v2.getNode().getLeftNucleotide();
+                               String r2 = v2.getNode().getRightNucleotide();
+                               // We have cost(subst((x,y) to (x',y'))) = 1
+                               // when x != x' and y != y'.
+                               // It means it is less than substituting 2 non-paired bases 
+                               return (!l1.equals(l2) ? 0.5 : 0)
+                                    + (!r1.equals(r2) ? 0.5 : 0);
+                       }
+               }
+       }
+       
+
+       public class SequenceAlignResult {
+               private double distance;
+               private int[][] alignment;
+               
+               public double getDistance() {
+                       return distance;
+               }
+               public void setDistance(double distance) {
+                       this.distance = distance;
+               }
+               
+               /** The result array is a matrix of height 2
+                * and width at most length(sequence A) + length(sequence B).
+                * with result[0] is the alignment for A
+                * and result[1] the alignment for B.
+                * The alignment consists int the indexes of the original
+                * bases positions, with -1 when there is no match.
+                */
+               public int[][] getAlignment() {
+                       return alignment;
+               }
+               public void setAlignment(int[][] alignment) {
+                       this.alignment = alignment;
+               }
+               
+       }
+       
+       /**
+        * Align two sequences contained in nodes.
+        * Both nodes have to be non-single nodes, otherwise an
+        * RNANodeValue2WrongTypeException exception will be thrown.
+        */
+       public SequenceAlignResult alignSequenceNodes(RNANodeValue2 v1, RNANodeValue2 v2) {
+               char[] A = v1.computeSequence();
+               char[] B = v2.computeSequence();
+               return alignSequences(A, B);
+       }
+       
+       /**
+        * Align sequences using the Needleman-Wunsch algorithm.
+        * Time: O(A.length * B.length)
+        * Space: O(A.length * B.length)
+        * Space used by the returned object: O(A.length + B.length)
+        */
+       public SequenceAlignResult alignSequences(char[] A, char[] B) {
+               SequenceAlignResult result = new SequenceAlignResult();
+               
+               final int la = A.length;
+               final int lb = B.length;
+               double[][] F = new double[la+1][lb+1];
+               int[][] decisions = new int[la+1][lb+1];
+               final double d = 1; // insertion/deletion cost
+               final double substCost = 1; // substitution cost
+               for (int i=0; i<=la; i++)
+                       F[i][0] = d*i;
+               for (int j=0; j<=lb; j++)
+                       F[0][j] = d*j;
+               for (int i=1; i<=la; i++)
+                       for (int j=1; j<=lb; j++)
+                       {
+                               double min;
+                               int decision;
+                               double match = F[i-1][j-1] + (A[i-1] == B[j-1] ? 0 : substCost);
+                               double delete = F[i-1][j] + d;
+                               if (match < delete) {
+                                       decision = 1;
+                                       min = match;
+                               } else {
+                                       decision = 2;
+                                       min = delete;
+                               }
+                               double insert = F[i][j-1] + d;
+                               if (insert < min) {
+                                       decision = 3;
+                                       min = insert;
+                               }
+                               F[i][j] = min;
+                               decisions[i][j] = decision;
+                       }
+               
+               result.setDistance(F[la][lb]);
+
+               int[][] alignment = computeAlignment(F, decisions, A, B);
+               result.setAlignment(alignment);
+               
+               return result;
+       }
+       
+       private int[][] computeAlignment(double[][] F, int[][] decisions, char[] A, char[] B) {
+               // At worst the alignment will be of length (A.length + B.length)
+               ArrayList<Integer> AlignmentA = new ArrayList<Integer>(A.length + B.length);
+               ArrayList<Integer> AlignmentB = new ArrayList<Integer>(A.length + B.length);
+               int i = A.length;
+               int j = B.length;
+               while (i > 0 && j > 0)
+               {
+                       int decision = decisions[i][j];
+                       switch (decision) {
+                       case 1:
+                               AlignmentA.add(i-1);
+                               AlignmentB.add(j-1);
+                               i = i - 1;
+                               j = j - 1;
+                               break;
+                       case 2:
+                               AlignmentA.add(i-1);
+                               AlignmentB.add(-1);
+                               i = i - 1;
+                               break;
+                       case 3:
+                               AlignmentA.add(-1);
+                               AlignmentB.add(j-1);
+                               j = j - 1;
+                               break;
+                       default:
+                               throw (new Error("Bug in ExampleDistance3: decision = " + decision));
+                       }
+               }
+               while (i > 0)
+               {
+                       AlignmentA.add(i-1);
+                       AlignmentB.add(-1);
+                       i = i - 1;
+               }
+               while (j > 0)
+               {
+                       AlignmentA.add(-1);
+                       AlignmentB.add(j-1);
+                       j = j - 1;
+               }
+
+               // Convert the ArrayLists to the right format:
+               // We need to reverse the list and change the numbering of
+               int l = AlignmentA.size();
+               int[][] result = new int[2][l];
+               for (i=0; i<l; i++) {
+                       result[0][i] = AlignmentA.get(l-1-i);
+                       result[1][i] = AlignmentB.get(l-1-i);
+               }
+               return result;
+               
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/GraphvizDrawableNodeValue.java b/srcjar/fr/orsay/lri/varna/models/treealign/GraphvizDrawableNodeValue.java
new file mode 100644 (file)
index 0000000..46c50ec
--- /dev/null
@@ -0,0 +1,14 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * A tree can be displayed using graphviz (using class TreeGraphviz)
+ * if the node values in the tree implement this interface.
+ * 
+ * @author Raphael Champeimont
+ */
+public interface GraphvizDrawableNodeValue {
+       /**
+        * Returns a string that will be displayed on the node by graphviz.
+        */
+       public String toGraphvizNodeName();
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue.java
new file mode 100644 (file)
index 0000000..3b26b47
--- /dev/null
@@ -0,0 +1,102 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * We use the following convention: If the node is marked by a couple (n,m)
+ * these integers are stored in leftBasePosition and rightBasePosition
+ * (ie. we have a pair of bases), if only
+ * one value is present, it is stored in leftBasePosition and rightBasePosition
+ * contains -1 (ie. we have a non-paired base).
+ * The right and left nucleotides follow the same rule, but are optional,
+ * and '_' is used as undefined instead of -1.
+ * Note that it is part of the contract of this class that default
+ * values are -1 and _.
+ * 
+ * @author Raphael Champeimont
+ */
+public class RNANodeValue implements GraphvizDrawableNodeValue {
+       private int leftBasePosition = -1;
+       private int rightBasePosition = -1;
+       
+       private String leftNucleotide = "_";
+       private String rightNucleotide = "_";
+       
+       public enum Origin {
+               BASE_PAIR_FROM_HELIX,
+               BASE_FROM_HELIX_STRAND5,
+               BASE_FROM_HELIX_STRAND3,
+               BASE_FROM_UNPAIRED_REGION;
+       }
+       
+       /**
+        * Used to store the origin of this base / base pair.
+        */
+       private Origin origin = null;
+       
+       public Origin getOrigin() {
+               return origin;
+       }
+       public void setOrigin(Origin comesFromAnHelix) {
+               this.origin = comesFromAnHelix;
+       }
+       
+       
+       
+       public String getLeftNucleotide() {
+               return leftNucleotide;
+       }
+       public void setLeftNucleotide(String leftNucleotide) {
+               this.leftNucleotide = leftNucleotide;
+       }
+       public String getRightNucleotide() {
+               return rightNucleotide;
+       }
+       public void setRightNucleotide(String rightNucleotide) {
+               this.rightNucleotide = rightNucleotide;
+       }
+       public int getLeftBasePosition() {
+               return leftBasePosition;
+       }
+       public void setLeftBasePosition(int leftBasePosition) {
+               this.leftBasePosition = leftBasePosition;
+       }
+       public int getRightBasePosition() {
+               return rightBasePosition;
+       }
+       public void setRightBasePosition(int rightBasePosition) {
+               this.rightBasePosition = rightBasePosition;
+       }
+       
+       public String toGraphvizNodeName() {
+               if (rightNucleotide.equals("_")) {
+                       if (leftNucleotide.equals("_")) {
+                               if (rightBasePosition == -1) {
+                                       if (leftBasePosition == -1) {
+                                               return super.toString();
+                                       } else {
+                                               return Integer.toString(leftBasePosition);
+                                       }
+                               } else {
+                                       return "(" + leftBasePosition + "," + rightBasePosition + ")";
+                               }
+                       } else {
+                               return leftNucleotide;
+                       }
+               } else {
+                       return "(" + leftNucleotide + "," + rightNucleotide + ")";
+               }
+       }
+       
+       public String toString() {
+               if (rightBasePosition == -1) {
+                       if (leftBasePosition == -1) {
+                               return super.toString();
+                       } else {
+                               return Integer.toString(leftBasePosition);
+                       }
+               } else {
+                       return "(" + leftBasePosition + "," + rightBasePosition + ")";
+               }
+       }
+       
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2.java
new file mode 100644 (file)
index 0000000..6fff9a9
--- /dev/null
@@ -0,0 +1,136 @@
+package fr.orsay.lri.varna.models.treealign;
+import java.util.ArrayList;
+import java.util.List;
+
+
+
+/**
+ * In this model, nodes are either:
+ * 1. a couple of paired bases, and in that case they may have children,
+ *    in this case singleNode is true 
+ * 2. a single base that comes from a broken base pair
+ *    (broken during planarization), without children,
+ *    in this case singleNode is true
+ * 3. a list of consecutive non-paired bases, without children.
+ *    in this case singleNode is false
+ * Note that case 2 happens only if original sequences contained
+ * pseudoknots, otherwise this case can be ignored.
+ * 
+ * @author Raphael Champeimont
+ *
+ */
+public class RNANodeValue2 implements GraphvizDrawableNodeValue {
+       /**
+        * Says whether we have a single node or a list of nodes.
+        */
+       private boolean singleNode = true;
+       
+       /**
+        * Defined if singleNode is true. 
+        */
+       private RNANodeValue node;
+       
+       /**
+        * Defined if singleNode is false; 
+        */
+       private List<RNANodeValue> nodes;
+       
+       public RNANodeValue2(boolean singleNode) {
+               this.singleNode = singleNode;
+               if (singleNode) {
+                       node = new RNANodeValue();
+               } else {
+                       nodes = new ArrayList<RNANodeValue>();
+               }
+       }
+
+       /**
+        * In case of a single node, return it.
+        * Will throw RNANodeValue2WrongTypeException if singleNode = false.
+        */
+       public RNANodeValue getNode() {
+               if (singleNode) {
+                       return node;
+               } else {
+                       throw (new RNANodeValue2WrongTypeException());
+               }
+       }
+       
+       public void setNode(RNANodeValue node) {
+               if (singleNode) {
+                       this.node = node;
+               } else {
+                       throw (new RNANodeValue2WrongTypeException());
+               }
+       }
+
+       /**
+        * In case of multiple nodes, return them.
+        * Will throw RNANodeValue2WrongTypeException if singleNode = true.
+        */
+       public List<RNANodeValue> getNodes() {
+               if (!singleNode) {
+                       return nodes;
+               } else {
+                       throw (new RNANodeValue2WrongTypeException());
+               }
+       }
+       /**
+        * In case of multiple nodes, return the sequence of nucleotides.
+        */
+       public char[] computeSequence() {
+               if (!singleNode) {
+                       final int n = nodes.size();
+                       char[] sequence = new char[n];
+                       for (int i=0; i<n; i++) {
+                               sequence[i] = nodes.get(i).getLeftNucleotide().charAt(0);
+                       }
+                       return sequence;
+               } else {
+                       throw (new RNANodeValue2WrongTypeException());
+               }
+       }
+       
+       public void setNodes(List<RNANodeValue> nodes) {
+               if (!singleNode) {
+                       this.nodes = nodes;
+               } else {
+                       throw (new RNANodeValue2WrongTypeException());
+               }
+       }
+
+       public boolean isSingleNode() {
+               return singleNode;
+       }
+       
+       public String toString() {
+               if (singleNode) {
+                       return node.toString();
+               } else {
+                       String s = "";
+                       for (RNANodeValue node: nodes) {
+                               if (s != "") {
+                                       s += " ";
+                               }
+                               s += node.toString();
+                       }
+                       return s;
+               }
+       }
+       
+       public String toGraphvizNodeName() {
+               if (singleNode) {
+                       return node.toGraphvizNodeName();
+               } else {
+                       String s = "";
+                       for (RNANodeValue node: nodes) {
+                               if (s != "") {
+                                       s += " ";
+                               }
+                               s += node.toGraphvizNodeName();
+                       }
+                       return s;
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2WrongTypeException.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNANodeValue2WrongTypeException.java
new file mode 100644 (file)
index 0000000..8dd2505
--- /dev/null
@@ -0,0 +1,11 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * @author Raphael Champeimont
+ *
+ */
+public class RNANodeValue2WrongTypeException extends NullPointerException {
+
+       private static final long serialVersionUID = -2709057098523675088L;
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNATree.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNATree.java
new file mode 100644 (file)
index 0000000..8a72fbf
--- /dev/null
@@ -0,0 +1,158 @@
+package fr.orsay.lri.varna.models.treealign;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+
+/**
+ * This class contains all functions that are specific to trees
+ * (class Tree) of RNA.
+ * 
+ * @author Raphael Champeimont
+ *
+ */
+public class RNATree { 
+       /**
+        * Convert an RNA object into a RNA tree.
+        * The root node will have a null value because it is a fake
+        * node added to have a tree (otherwise we would have a forest).
+        */
+       public static Tree<RNANodeValue> RNATreeFromRNA(RNA rna) {
+               ConvertToTree converter = new ConvertToTree(rna);
+               return converter.toTreeAux(0);
+       }
+       
+       private static class ConvertToTree {
+               private RNA rna;
+               
+               private int i = 0;
+               
+               /** Starts at the current position i in the sequence and converts the sequence
+                *  to a tree.
+                * @return the created tree
+                */
+               public Tree<RNANodeValue> toTreeAux(int depth) {
+                       Tree<RNANodeValue> tree = new Tree<RNANodeValue>();
+                       List<Tree<RNANodeValue>> children = tree.getChildren();
+                       // No value because it is a fake root
+                       tree.setValue(null);
+                       
+                       int length = rna.getSize();
+                       while (i < length) {
+                               ModeleBase base = rna.getBaseAt(i);
+                               int indexOfAssociatedBase = base.getElementStructure();
+                               if (indexOfAssociatedBase >= 0) {
+                                       if (indexOfAssociatedBase > i) {
+                                               // left parenthesis, we must analyze the children
+                                               RNANodeValue childValue = new RNANodeValue();
+                                               childValue.setLeftBasePosition(i);
+                                               childValue.setRightBasePosition(indexOfAssociatedBase);
+                                               childValue.setOrigin(RNANodeValue.Origin.BASE_PAIR_FROM_HELIX);
+                                               
+                                               if (base instanceof ModeleBaseNucleotide) {
+                                                       childValue.setLeftNucleotide(((ModeleBaseNucleotide) base).getBase());
+                                                       childValue.setRightNucleotide(((ModeleBaseNucleotide) rna.getBaseAt(indexOfAssociatedBase)).getBase());
+                                               }
+                                               i++;
+                                               Tree<RNANodeValue> child = toTreeAux(depth+1);
+                                               child.setValue(childValue);
+                                               children.add(child);
+                                       } else {
+                                               // right parenthesis, we have finished analyzing the children
+                                               i++;
+                                               break;
+                                       }
+                               } else {
+                                       // we have a non-paired base
+                                       Tree<RNANodeValue> child = new Tree<RNANodeValue>();
+                                       RNANodeValue childValue = new RNANodeValue();
+                                       childValue.setLeftBasePosition(i);
+                                       if (base instanceof ModeleBaseNucleotide) {
+                                               childValue.setLeftNucleotide(((ModeleBaseNucleotide) base).getBase());
+                                       }
+                                       
+                                       // Even in this case (getElementStructure() < 0)
+                                       // this base may still come from an helix which may have
+                                       // been broken to remove a pseudoknot.
+                                       childValue.setOrigin(RNANodeValue.Origin.BASE_FROM_UNPAIRED_REGION);
+                                       ArrayList<ModeleBP> auxBasePairs = rna.getAuxBPs(i);
+                                       for (ModeleBP auxBasePair: auxBasePairs) {
+                                               if (auxBasePair.isCanonical()) {
+                                                       int partner5 = ((ModeleBaseNucleotide) auxBasePair.getPartner5()).getIndex();
+                                                       int partner3 = ((ModeleBaseNucleotide) auxBasePair.getPartner3()).getIndex();
+                                                       if (i == partner5) {
+                                                               childValue.setOrigin(RNANodeValue.Origin.BASE_FROM_HELIX_STRAND5);
+                                                       } else if (i == partner3) {
+                                                               childValue.setOrigin(RNANodeValue.Origin.BASE_FROM_HELIX_STRAND3);
+                                                       } else {
+                                                               System.err.println("Warning: Base index is " + i + " but neither endpoint matches it (edge endpoints are " + partner5 + " and " + partner3 + ").");
+                                                       }
+                                                       
+                                               }
+                                       }
+                                       
+                                       child.setValue(childValue);
+                                       children.add(child);
+                                       i++;
+                               }
+                       }
+                       
+                       return tree;
+               }
+               
+               public ConvertToTree(RNA rna) {
+                       this.rna = rna;
+               }
+       }
+       
+
+       /**
+        * Convert an RNA tree alignment into a Mapping.
+        */
+       public static Mapping mappingFromAlignment(Tree<AlignedNode<RNANodeValue,RNANodeValue>> alignment) throws MappingException {
+               ConvertToMapping converter = new ConvertToMapping();
+               return converter.convert(alignment);
+       }
+       
+       private static class ConvertToMapping {
+               private Mapping m;
+               
+               public Mapping convert(Tree<AlignedNode<RNANodeValue,RNANodeValue>> tree) throws MappingException {
+                       m = new Mapping();
+                       convertSubTree(tree);
+                       return m;
+               }
+               
+               private void convertSubTree(Tree<AlignedNode<RNANodeValue,RNANodeValue>> tree) throws MappingException {
+                       AlignedNode<RNANodeValue,RNANodeValue> alignedNode = tree.getValue();
+                       Tree<RNANodeValue> leftNode  = alignedNode.getLeftNode();
+                       Tree<RNANodeValue> rightNode = alignedNode.getRightNode();
+                       if (leftNode != null && rightNode != null) {
+                               RNANodeValue v1 = leftNode.getValue();
+                               RNANodeValue v2 = rightNode.getValue();
+                               int l1 = v1.getLeftBasePosition();
+                               int r1 = v1.getRightBasePosition();
+                               int l2 = v2.getLeftBasePosition();
+                               int r2 = v2.getRightBasePosition();
+                               if (l1 >= 0 && l2 >= 0) {
+                                       m.addCouple(l1, l2);
+                               }
+                               if (r1 >= 0 && r2 >= 0) {
+                                       m.addCouple(r1, r2);
+                               }
+                       }
+                       for (Tree<AlignedNode<RNANodeValue,RNANodeValue>> child: tree.getChildren()) {
+                               convertSubTree(child);
+                       }
+               }
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNATree2.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNATree2.java
new file mode 100644 (file)
index 0000000..753819b
--- /dev/null
@@ -0,0 +1,172 @@
+package fr.orsay.lri.varna.models.treealign;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import fr.orsay.lri.varna.exceptions.MappingException;
+import fr.orsay.lri.varna.models.rna.Mapping;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+/**
+ * This class contains all functions that are specific to trees
+ * (class Tree) of RNA, with RNANodeValue2.
+ * 
+ * @author Raphael Champeimont
+ *
+ */
+public class RNATree2 {
+       /**
+        * Convert an RNA object into a RNA tree with RNANodeValue2.
+        * @throws RNATree2Exception 
+        */
+       public static Tree<RNANodeValue2> RNATree2FromRNA(RNA rna) throws RNATree2Exception {
+               Tree<RNANodeValue> fullTree = RNATree.RNATreeFromRNA(rna);
+               return RNATree2FromRNATree(fullTree);
+       }
+
+       /**
+        * Convert from RNANodeValue model to RNANodeValue2 model,
+        * ie. compact consecutive non-paired bases.
+        */
+       public static Tree<RNANodeValue2> RNATree2FromRNATree(Tree<RNANodeValue> originalTree) throws RNATree2Exception {
+               Tree<RNANodeValue2> newTree = new Tree<RNANodeValue2>();
+               // Root in original tree is fake, so make a fake root
+               newTree.setValue(null);
+               newTree.replaceChildrenListBy(RNAForest2FromRNAForest(originalTree.getChildren()));
+               return newTree;
+       }
+       
+       private static void RNAForest2FromRNAForestCommitNonPaired(List<Tree<RNANodeValue2>> forest, List<RNANodeValue> consecutiveNonPairedBases) {
+               // add the group of non-paired bases if there is one
+               if (consecutiveNonPairedBases.size() > 0) {
+                       RNANodeValue2 groupOfConsecutiveBases = new RNANodeValue2(false);
+                       groupOfConsecutiveBases.getNodes().addAll(consecutiveNonPairedBases);
+                       Tree<RNANodeValue2> groupOfConsecutiveBasesNode = new Tree<RNANodeValue2>();
+                       groupOfConsecutiveBasesNode.setValue(groupOfConsecutiveBases);
+                       forest.add(groupOfConsecutiveBasesNode);
+                       consecutiveNonPairedBases.clear();
+               }
+       }
+       
+       private static List<Tree<RNANodeValue2>> RNAForest2FromRNAForest(List<Tree<RNANodeValue>> originalForest) throws RNATree2Exception {
+               List<Tree<RNANodeValue2>> forest = new ArrayList<Tree<RNANodeValue2>>();
+               List<RNANodeValue> consecutiveNonPairedBases = new LinkedList<RNANodeValue>();
+               for (Tree<RNANodeValue> originalTree: originalForest) {
+                       if (originalTree.getValue().getRightBasePosition() == -1) {
+                               // non-paired base
+                               if (originalTree.getChildren().size() > 0) {
+                                       throw (new RNATree2Exception("Non-paired base cannot have children."));
+                               }
+                               
+                               switch (originalTree.getValue().getOrigin()) {
+                               case BASE_FROM_HELIX_STRAND5:
+                               case BASE_FROM_HELIX_STRAND3:
+                                       // This base is part of a broken base pair
+                                       
+                                       // if we have gathered some non-paired bases, add a node with
+                                       // the group of them 
+                                       RNAForest2FromRNAForestCommitNonPaired(forest, consecutiveNonPairedBases);
+                                       
+                                       // now add the node
+                                       RNANodeValue2 pairedBase = new RNANodeValue2(true);
+                                       pairedBase.setNode(originalTree.getValue());
+                                       Tree<RNANodeValue2> pairedBaseNode = new Tree<RNANodeValue2>();
+                                       pairedBaseNode.setValue(pairedBase);
+                                       forest.add(pairedBaseNode);
+                                       break;
+                               case BASE_FROM_UNPAIRED_REGION:
+                                       consecutiveNonPairedBases.add(originalTree.getValue());
+                                       break;
+                               case BASE_PAIR_FROM_HELIX:
+                                       throw (new RNATree2Exception("Origin is BASE_PAIR_FROM_HELIX but this is not a pair."));
+                               }
+                       } else {
+                               // paired bases
+                               
+                               // if we have gathered some non-paired bases, add a node with
+                               // the group of them 
+                               RNAForest2FromRNAForestCommitNonPaired(forest, consecutiveNonPairedBases);
+                               
+                               // now add the node
+                               RNANodeValue2 pairedBase = new RNANodeValue2(true);
+                               pairedBase.setNode(originalTree.getValue());
+                               Tree<RNANodeValue2> pairedBaseNode = new Tree<RNANodeValue2>();
+                               pairedBaseNode.setValue(pairedBase);
+                               pairedBaseNode.replaceChildrenListBy(RNAForest2FromRNAForest(originalTree.getChildren()));
+                               forest.add(pairedBaseNode);
+                       }
+               }
+               
+               // if there we have some non-paired bases, add them grouped
+               RNAForest2FromRNAForestCommitNonPaired(forest, consecutiveNonPairedBases);
+               
+               return forest;
+       }
+       
+       
+       /**
+        * Convert an RNA tree (with RNANodeValue2) alignment into a Mapping.
+        */
+       public static Mapping mappingFromAlignment(Tree<AlignedNode<RNANodeValue2,RNANodeValue2>> alignment) throws MappingException {
+               ConvertToMapping converter = new ConvertToMapping();
+               return converter.convert(alignment);
+       }
+       
+       private static class ConvertToMapping {
+               private Mapping m;
+               ExampleDistance3 sequenceAligner = new ExampleDistance3();
+               
+               public Mapping convert(Tree<AlignedNode<RNANodeValue2,RNANodeValue2>> tree) throws MappingException {
+                       m = new Mapping();
+                       convertSubTree(tree);
+                       return m;
+               }
+               
+               private void convertSubTree(Tree<AlignedNode<RNANodeValue2,RNANodeValue2>> tree) throws MappingException {
+                       AlignedNode<RNANodeValue2,RNANodeValue2> alignedNode = tree.getValue();
+                       Tree<RNANodeValue2> leftNode  = alignedNode.getLeftNode();
+                       Tree<RNANodeValue2> rightNode = alignedNode.getRightNode();
+                       if (leftNode != null && rightNode != null) {
+                               RNANodeValue2 v1 = leftNode.getValue();
+                               RNANodeValue2 v2 = rightNode.getValue();
+                               if (v1.isSingleNode() && v2.isSingleNode()) {
+                                       // we have aligned (x,y) with (x',y')
+                                       // so we map x with x' and y with y'
+                                       RNANodeValue vsn1 = v1.getNode();
+                                       RNANodeValue vsn2 = v2.getNode();
+                                       int l1 = vsn1.getLeftBasePosition();
+                                       int r1 = vsn1.getRightBasePosition();
+                                       int l2 = vsn2.getLeftBasePosition();
+                                       int r2 = vsn2.getRightBasePosition();
+                                       if (l1 >= 0 && l2 >= 0) {
+                                               m.addCouple(l1, l2);
+                                       }
+                                       if (r1 >= 0 && r2 >= 0) {
+                                               m.addCouple(r1, r2);
+                                       }
+                               } else if (!v1.isSingleNode() && !v2.isSingleNode()) {
+                                       // We have aligned x1 x2 ... xn with y1 y2 ... ym.
+                                       // So we will now (re-)compute this sequence alignment.
+                                       int[][] sequenceAlignment = sequenceAligner.alignSequenceNodes(v1, v2).getAlignment();
+                                       int l = sequenceAlignment[0].length;
+                                       for (int i=0; i<l; i++) {
+                                               // b1 and b2 are indexes in the aligned sequences
+                                               int b1 = sequenceAlignment[0][i];
+                                               int b2 = sequenceAlignment[1][i];
+                                               if (b1 != -1 && b2 != -1) {
+                                                       int l1 = v1.getNodes().get(b1).getLeftBasePosition();
+                                                       int l2 = v2.getNodes().get(b2).getLeftBasePosition();
+                                                       m.addCouple(l1, l2);
+                                               }
+                                       }
+
+                               }
+                       }
+                       
+                       for (Tree<AlignedNode<RNANodeValue2,RNANodeValue2>> child: tree.getChildren()) {
+                               convertSubTree(child);
+                       }
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/RNATree2Exception.java b/srcjar/fr/orsay/lri/varna/models/treealign/RNATree2Exception.java
new file mode 100644 (file)
index 0000000..81ccce9
--- /dev/null
@@ -0,0 +1,15 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * @author Raphael Champeimont
+ *
+ */
+public class RNATree2Exception extends Exception {
+       
+       private static final long serialVersionUID = 2034802149835891010L;
+
+       public RNATree2Exception(String message) {
+               super(message);
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/Tree.java b/srcjar/fr/orsay/lri/varna/models/treealign/Tree.java
new file mode 100644 (file)
index 0000000..ff00e71
--- /dev/null
@@ -0,0 +1,157 @@
+package fr.orsay.lri.varna.models.treealign;
+import java.util.*;
+
+
+/**
+ * An object of this class is a rooted tree, where children are ordered.
+ * The tree is iterable, and the default iterator is DFS
+ * (depth-first search), with the fathers given before the children.
+ * 
+ * @param <T> The type of values on nodes.
+ * @author Raphael Champeimont
+ */
+public class Tree<T> implements Iterable<Tree<T>> {
+       private List<Tree<T>> children;
+       private T value;
+       private Tree<T> tree = this;
+       
+       public T getValue() {
+               return value;
+       }
+
+       public void setValue(T value) {
+               this.value = value;
+       }
+
+       /**
+        * Returns the list of children.
+        * The return list has a O(1) access time to any of its elements
+        * (ie. it is like an array and unlike a linked list)
+        */
+       public List<Tree<T>> getChildren() {
+               return children;
+       }
+       
+       /**
+        * This method replaces the list of children of a tree with the list given
+        * as argument. Be careful, because it means the list will be kept as a
+        * reference (it will not be copied) so if you later modify the list
+        * you passed as an argument here, it will modify the list of children.
+        * Note that the List object you give must have a 0(1) access time to
+        * elements (because someone calling getChildren() can expect that property).
+        * This method may be useful if you have already built a list of children
+        * and you don't want to use this.getChildren.addAll() to avoid a O(n) copy.
+        * In that case you would simply call the constructor that takes no argument
+        * to create an empty tree and then call replaceChildrenListBy(children)
+        * where children is the list of children you have built.
+        * @param children the new list of children
+        */
+       public void replaceChildrenListBy(List<Tree<T>> children) {
+               this.children = children;
+       }
+
+       /**
+        * Creates a tree, with the given set of children.
+        * The given set is any collection that implements Iterable<Tree>.
+        * The set is iterated on and its elements are copied (as references).
+        */
+       public Tree(Iterable<Tree<T>> children) {
+               this();
+               for (Tree<T> child: children) {
+                       this.children.add(child);
+               }
+       }
+       
+       /**
+        * Creates a tree, with an empty list of children.
+        */
+       public Tree() {
+               children = new ArrayList<Tree<T>>();
+       }
+       
+       /**
+        * Returns the number of children of the root node.
+        */
+       public int rootDegree() {
+               return children.size();
+       }
+       
+       
+       /**
+        * Count the nodes in the tree.
+        * Time: O(n)
+        * @return the number of nodes in the tree
+        */
+       public int countNodes() {
+               int count = 1;
+               for (Tree<T> child: children) {
+                       count += child.countNodes();
+               }
+               return count;
+       }
+       
+       /**
+        * Compute the tree degree, ie. the max over nodes of the node degree.
+        * Time: O(n)
+        * @return the maximum node degree
+        */
+       public int computeDegree() {
+               int max = children.size();
+               for (Tree<T> child: children) {
+                       int maxCandidate = child.computeDegree();
+                       if (maxCandidate > max) {
+                               max = maxCandidate;
+                       }
+               }
+               return max;
+       }
+       
+       /**
+        * Returns a string unique to this node.
+        */
+       public String toGraphvizNodeId() {
+               return super.toString();
+       }
+       
+       
+       public Iterator<Tree<T>> iterator() {
+               return (new DFSPrefixIterator());
+       }
+       
+       /**
+        * An iterator that returns the nodes in prefix (fathers before
+        * children) DFS (go deep first) order.
+        */
+       public class DFSPrefixIterator implements Iterator<Tree<T>> {
+               private LinkedList<Tree<T>> remainingNodes = new LinkedList<Tree<T>>();
+               
+               public boolean hasNext() {
+                       return !remainingNodes.isEmpty();
+               }
+               
+               public Tree<T> next() {
+                       if (remainingNodes.isEmpty()) {
+                               throw (new NoSuchElementException());
+                       }
+                       Tree<T> currentNode = remainingNodes.getLast();
+                       remainingNodes.removeLast();
+                       List<Tree<T>> children = currentNode.getChildren();
+                       int n = children.size();
+                       // The children access is in O(1) so this loop is O(n)
+                       for (int i=n-1; i>=0; i--) {
+                               // We add the children is their reverse order so they
+                               // are given in the original order by the iterator
+                               remainingNodes.add(children.get(i));
+                       }
+                       return currentNode;
+               }
+               
+               public DFSPrefixIterator() {
+                       remainingNodes.add(tree);
+               }
+
+               public void remove() {
+                       throw (new UnsupportedOperationException());
+               }
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlign.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlign.java
new file mode 100644 (file)
index 0000000..ebf89d6
--- /dev/null
@@ -0,0 +1,775 @@
+package fr.orsay.lri.varna.models.treealign;
+
+import java.util.*;
+
+
+/**
+ * Tree alignment algorithm.
+ * This class implements the tree alignment algorithm
+ * for ordered trees explained in article:
+ *   T. Jiang, L. Wang, K. Zhang,
+ *   Alignment of trees - an alternative to tree edit,
+ *   Theoret. Comput. Sci. 143 (1995).
+ * Other references:
+ * - Claire Herrbach, Alain Denise and Serge Dulucq.
+ *   Average complexity of the Jiang-Wang-Zhang pairwise tree alignment
+ *   algorithm and of a RNA secondary structure alignment algorithm.
+ *   Theoretical Computer Science 411 (2010) 2423-2432.
+ *   
+ * Our implementation supposes that the trees will never have more
+ * than 32000 nodes and that the total distance will never require more
+ * significant digits that a float (single precision) has. 
+ * 
+ * @author Raphael Champeimont
+ * @param <ValueType1> The type of values on nodes in the first tree.
+ * @param <ValueType2> The type of values on nodes in the second tree.
+ */
+public class TreeAlign<ValueType1, ValueType2> {
+       
+       private class TreeData<ValueType> {
+               /**
+                * The tree.
+                */
+               public Tree<ValueType> tree;
+               
+               /**
+                * The tree size (number of nodes).
+                */
+               public int size = -1;
+               
+               /**
+                * The number of children of a node is called the node degree.
+                * This variable is the maximum node degree in the tree.
+                */
+               public int degree = -1;
+               
+               /**
+                * The number of children of a node is called the node degree.
+                * degree[i] is the degree of node i, with i being an index in nodes.
+                */
+               public int[] degrees;
+               
+               /**
+                * The trees as an array of its nodes (subtrees rooted at each node
+                * in fact), in postorder. 
+                */
+               public Tree<ValueType>[] nodes;
+               
+               /**
+                * children[i] is the array of children (as indexes in nodes)
+                * of i (an index in nodes)
+                */
+               public int[][] children;
+               
+               /**
+                * Values of nodes.
+                */
+               public ValueType[] values;
+       }
+
+
+       /**
+        * The distance function between labels.
+        */
+       private TreeAlignLabelDistanceAsymmetric<ValueType1,ValueType2> labelDist;
+
+       
+       /**
+        * Create a TreeAlignSymmetric object, which can align trees.
+        * The distance function will be called only once on every pair
+        * of nodes. The result is then kept in a matrix, so you need not manage
+        * yourself a cache of f(value1, value2).
+        * Note that it is permitted to have null values on nodes,
+        * so comparing a node with a non-null value with a node with a null
+        * value will give the same cost as to insert the first node.
+        * This can be useful if you tree has "fake" nodes.
+        * @param labelDist The label distance.
+        */
+       public TreeAlign(TreeAlignLabelDistanceAsymmetric<ValueType1,ValueType2> labelDist) {
+               this.labelDist = labelDist;
+       }
+       
+
+       
+       private class ConvertTreeToArray<ValueType> {
+               private int nextNodeIndex = 0;
+               private TreeData<ValueType> treeData;
+               
+               public ConvertTreeToArray(TreeData<ValueType> treeData) {
+                       this.treeData = treeData;
+               }
+               
+               private void convertTreeToArrayAux(
+                               Tree<ValueType> subtree,
+                               int[] siblingIndexes,
+                               int siblingNumber) throws TreeAlignException {
+                       // We want it in postorder, so first we put the children
+                       List<Tree<ValueType>> children = subtree.getChildren();
+                       int numberOfChildren = children.size();
+                       int[] childrenIndexes = new int[numberOfChildren];
+                       int myIndex = -1;
+                       {
+                               int i = 0;
+                               for (Tree<ValueType> child: children) {
+                                       convertTreeToArrayAux(child, childrenIndexes, i);
+                                       i++;
+                               }
+                       }
+                       // Compute the maximum degree
+                       if (numberOfChildren > treeData.degree) {
+                               treeData.degree = numberOfChildren;
+                       }
+                       // Now we add the node (root of the given subtree).
+                       myIndex = nextNodeIndex;
+                       nextNodeIndex++;
+                       treeData.nodes[myIndex] = subtree;
+                       // Record how many children I have
+                       treeData.degrees[myIndex] = numberOfChildren;
+                       // Store my value in an array
+                       ValueType v = subtree.getValue();
+                       treeData.values[myIndex] = v;
+                       // Tell the caller my index
+                       siblingIndexes[siblingNumber] = myIndex;
+                       // Record my children indexes
+                       treeData.children[myIndex] = childrenIndexes;
+               }
+               
+               /**
+                * Reads: treeData.tree
+                * Computes: treeData.nodes, treeData.degree, treeData.degrees
+                *           treeData.fathers, treeData.children, treeData.size,
+                *           treeData.values
+                * Converts a tree to an array of nodes, in postorder.
+                * We also compute the maximum node degree in the tree.
+                * @throws TreeAlignException 
+                */
+               @SuppressWarnings("unchecked")
+               public void convert() throws TreeAlignException {
+                       treeData.degree = 0;
+                       treeData.size = treeData.tree.countNodes();
+                       // we didn't write new Tree<ValueType>[treeData.size] because
+                       // java does not support generics with arrays
+                       treeData.nodes = new Tree[treeData.size];
+                       treeData.children = new int[treeData.size][];
+                       treeData.degrees = new int[treeData.size];
+                       treeData.values = (ValueType[]) new Object[treeData.size];
+                       int rootIndex[] = new int[1];
+                       convertTreeToArrayAux(treeData.tree, rootIndex, 0);
+               }
+       }
+       
+
+       /**
+        * For arrays that take at least O(|T1|*|T2|) we take care
+        * not to use too big data types.
+        */
+       private class Aligner {
+               /**
+                * The first tree.
+                */
+               private TreeData<ValueType1> treeData1;
+               
+               /**
+                * The second tree. 
+                */
+               private TreeData<ValueType2> treeData2;
+               
+               /**
+                * DF1[i][j_t] is DFL for (i,j,s,t) with s=0.
+                * See description of DFL in Aligner.computeAlignmentP1().
+                * DF1 and DF2 are the "big" arrays, ie. those that may the space
+                * complexity what it is.
+                */
+               private float[][][][] DF1;
+               
+               /**
+                * DF2[j][i_s] is DFL for (i,j,s,t) with t=0.
+                * See description of DFL in Aligner.computeAlignmentP1().
+                */
+               private float[][][][] DF2;
+               
+               /**
+                * This arrays have the same shape as respectively DF1.
+                * They are used to remember which term in the minimum won, so that
+                * we can compute the alignment.
+                * Decision1 is a case number (< 10)
+                * and Decision2 is a child index, hence the types.
+                */
+               private byte[][][][] DF1Decisions1;
+               private short[][][][] DF1Decisions2;
+               
+               /**
+                * This arrays have the same shape as respectively DF2.
+                * They are used to remember which term in the minimum won, so that
+                * we can compute the alignment.
+                */
+               private byte[][][][] DF2Decisions1;
+               private short[][][][] DF2Decisions2;
+               
+               /**
+                * Distances between subtrees.
+                * DT[i][j] is the distance between the subtree rooted at i in the first tree
+                * and the subtree rooted at j in the second tree.
+                */
+               private float[][] DT;
+               
+               /**
+                * This array has the same shape as DT, but is used to remember which
+                * case gave the minimum, so that we can later compute the alignment.
+                */
+               private byte[][] DTDecisions1;
+               private short[][] DTDecisions2;
+               
+               /**
+                * Distances between labels.
+                * DL[i][j] is the distance labelDist.f(value(T1[i]), value(T2[i])).
+                * By convention, we say that value(T1[|T1|]) = null
+                * and value(T2[|T2|]) = null
+                */
+               private float[][] DL;
+               
+               /**
+                * DET1[i] is the distance between the empty tree and T1[i]
+                * (the subtree rooted at node i in the first tree).
+                */
+               private float[] DET1;
+               
+               /**
+                * Same as DET1, but for second tree.
+                */
+               private float[] DET2;
+               
+               /**
+                * DEF1[i] is the distance between the empty forest and F1[i]
+                * (the forest of children of node i in the first tree).
+                */
+               private float[] DEF1;
+               
+               /**
+                * Same as DEF1, but for second tree.
+                */
+               private float[] DEF2;
+               
+               
+               /**
+                * @param i node in T1
+                * @param s number of first child of i to consider
+                * @param m_i degree of i
+                * @param j node in T2
+                * @param t number of first child of j to consider
+                * @param n_j degree of j
+                * @param DFx which array to fill (DF1 or DF2)
+                */
+               private void computeAlignmentP1(int i, int s, int m_i, int j, int t, int n_j, int DFx) {
+                       /**
+                        * DFL[pr][qr] is D(F1[i_s, i_p], F2[j_t, j_q])
+                        * where p=s+pr-1 and q=t+qr-1 (ie. pr=p-s+1 and qr=q-t+1)
+                        * By convention, F1[i_s, i_{s-1}] and F2[j_t, j_{t-1}] are the
+                        * empty forests.
+                        * Said differently, DFL[pr][qr] is the distance between the forest
+                        * of the pr first children of i, starting with child s
+                        * (first child is s = 0), and the forest of the qr first children
+                        * of j, starting with child t (first child is t = 0).
+                        * This array is allocated for a fixed value of (i,j,s,t).
+                        */
+                       float[][] DFL;
+                       
+                       /**
+                        * Same shape as DFL, but to remember which term gave the min,
+                        * so that we can later compute the alignment.
+                        */
+                       byte[][] DFLDecisions1;
+                       short[][] DFLDecisions2;
+                       
+                       DFL = new float[m_i-s+2][n_j-t+2];
+                       DFL[0][0] = 0; // D(empty forest, empty forest) = 0
+                       
+                       DFLDecisions1 = new byte[m_i-s+2][n_j-t+2];
+                       DFLDecisions2 = new short[m_i-s+2][n_j-t+2];
+                       
+                       // Compute indexes of i_s and j_t because we will need them
+                       int i_s = m_i != 0 ? treeData1.children[i][s] : -1;
+                       int j_t = n_j != 0 ? treeData2.children[j][t] : -1;
+                       
+                       for (int p=s; p<m_i; p++) {
+                               DFL[p-s+1][0] = DFL[p-s][0] + DET1[treeData1.children[i][p]];
+                       }
+                       
+                       for (int q=t; q<n_j; q++) {
+                               DFL[0][q-t+1] = DFL[0][q-t] + DET2[treeData2.children[j][q]];
+                       }
+                       
+                       for (int p=s; p<m_i; p++) {
+                               int i_p = treeData1.children[i][p];
+                               for (int q=t; q<n_j; q++) {
+                                       int j_q = treeData2.children[j][q];
+                                       
+                                       float min = Float.POSITIVE_INFINITY;
+                                       int decision1 = -1;
+                                       int decision2 = -1;
+                                       
+                                       // Lemma 3 - Case: We delete the rightmost tree of T1
+                                       {
+                                               float minCandidate = DFL[p-s][q-t+1] + DET1[i_p];
+                                               if (minCandidate < min) {
+                                                       min = minCandidate;
+                                                       decision1 = 1;
+                                               }
+                                       }
+                                       
+                                       // Lemma 3 - Case: We insert the rightmost tree of T2 (symmetric of previous case)
+                                       {
+                                               float minCandidate = DFL[p-s+1][q-t] + DET2[j_q];
+                                               if (minCandidate < min) {
+                                                       min = minCandidate;
+                                                       decision1 = 2;
+                                               }
+                                       }
+                                       
+                                       // Lemma 3 - Case: Align rightmost trees with each other
+                                       {
+                                               float minCandidate = 
+                                                       DFL[p-s][q-t] + DT [i_p] [j_q];
+                                               if (minCandidate < min) {
+                                                       min = minCandidate;
+                                                       decision1 = 3;
+                                               }
+                                       }
+                                       
+                                       // Lemma 3 - Case: We cut the T1 forest and match the first part
+                                       // with the T2 forest except the rightmost tree, and we match the second
+                                       // part with the T2 rightmost tree's forest of children
+                                       {
+                                               float minCandidate = Float.POSITIVE_INFINITY;
+                                               int best_k = -1;
+                                               for (int k=s; k<p; k++) {
+                                                       float d = DFL[k-s][q-t]
+                                                                + DF2 [j_q] [treeData1.children[i][k]] [p-k+1] [treeData2.degrees[j_q]];
+                                                       if (d < minCandidate) {
+                                                               minCandidate = d;
+                                                               best_k = k;
+                                                       }
+                                               }
+                                               minCandidate += DL[treeData1.size][j_q];
+                                               if (minCandidate < min) {
+                                                       min = minCandidate;
+                                                       decision1 = 4;
+                                                       decision2 = best_k;
+                                               }
+                                       }
+                                       
+                                       // Lemma 3 - Case: Syemmetric of preivous case
+                                       {
+                                               float minCandidate = Float.POSITIVE_INFINITY;
+                                               int best_k = -1;
+                                               for (int k=t; k<q; k++) {
+                                                       float d = DFL[p-s][k-t]
+                                                                + DF1 [i_p] [treeData2.children[j][k]] [treeData1.degrees[i_p]] [q-k+1];
+                                                       if (d < minCandidate) {
+                                                               minCandidate = d;
+                                                               best_k = k;
+                                                       }
+                                               }
+                                               minCandidate += DL[i_p][treeData2.size];
+                                               if (minCandidate < min) {
+                                                       min = minCandidate;
+                                                       decision1 = 5;
+                                                       decision2 = best_k;
+                                               }
+                                       }
+                                       
+                                       DFL[p-s+1][q-t+1] = min;
+                                       DFLDecisions1[p-s+1][q-t+1] = (byte) decision1;
+                                       DFLDecisions2[p-s+1][q-t+1] = (short) decision2;
+                               }
+                       }
+                       
+                       // Copy references to DFL to persistent arrays
+                       if (DFx == 2) {
+                               DF2[j][i_s] = DFL;
+                               DF2Decisions1[j][i_s] = DFLDecisions1;
+                               DF2Decisions2[j][i_s] = DFLDecisions2;
+                       } else {
+                               DF1[i][j_t] = DFL;
+                               DF1Decisions1[i][j_t] = DFLDecisions1;
+                               DF1Decisions2[i][j_t] = DFLDecisions2;
+                       }
+                       
+               }
+               
+               public float align() throws TreeAlignException {
+                       (new ConvertTreeToArray<ValueType1>(treeData1)).convert();
+                       (new ConvertTreeToArray<ValueType2>(treeData2)).convert();
+                       
+                       // Allocate necessary arrays
+                       DT = new float[treeData1.size][treeData2.size];
+                       DTDecisions1 = new byte[treeData1.size][treeData2.size];
+                       DTDecisions2 = new short[treeData1.size][treeData2.size];
+                       DL = new float[treeData1.size+1][treeData2.size+1];
+                       DET1 = new float[treeData1.size];
+                       DET2 = new float[treeData2.size];
+                       DEF1 = new float[treeData1.size];
+                       DEF2 = new float[treeData2.size];
+                       DF1 = new float[treeData1.size][treeData2.size][][];
+                       DF1Decisions1 = new byte[treeData1.size][treeData2.size][][];
+                       DF1Decisions2 = new short[treeData1.size][treeData2.size][][];
+                       DF2 = new float[treeData2.size][treeData1.size][][];
+                       DF2Decisions1 = new byte[treeData2.size][treeData1.size][][];
+                       DF2Decisions2 = new short[treeData2.size][treeData1.size][][];
+                       
+                       DL[treeData1.size][treeData2.size] = (float) labelDist.f(null, null);
+
+                       for (int i=0; i<treeData1.size; i++) {
+                               int m_i = treeData1.degrees[i];
+                               DEF1[i] = 0;
+                               for (int k=0; k<m_i; k++) {
+                                       DEF1[i] += DET1[treeData1.children[i][k]];
+                               }
+                               DL[i][treeData2.size] = (float) labelDist.f((ValueType1) treeData1.values[i], null);
+                               DET1[i] = DEF1[i] + DL[i][treeData2.size];
+                       }
+                       
+                       for (int j=0; j<treeData2.size; j++) {
+                               int n_j = treeData2.degrees[j];
+                               DEF2[j] = 0;
+                               for (int k=0; k<n_j; k++) {
+                                       DEF2[j] += DET2[treeData2.children[j][k]];
+                               }
+                               DL[treeData1.size][j] = (float) labelDist.f(null, (ValueType2) treeData2.values[j]);
+                               DET2[j] = DEF2[j] + DL[treeData1.size][j];
+                       }
+                       
+
+                       for (int i=0; i<treeData1.size; i++) {
+                               int m_i = treeData1.degrees[i];
+                               for (int j=0; j<treeData2.size; j++) {
+                                       int n_j = treeData2.degrees[j];
+                                       
+                                       // Precompute f(value(i), value(j)) and keep the result
+                                       // to avoid calling f on the same values several times.
+                                       // This is important in case the computation of f takes
+                                       // long.
+                                       DL[i][j] = (float) labelDist.f((ValueType1) treeData1.values[i], (ValueType2) treeData2.values[j]);
+                                       
+                                       for (int s=0; s<m_i; s++) {
+                                               computeAlignmentP1(i, s, m_i, j, 0, n_j, 2);
+                                       }
+                                       
+                                       for (int t=0; t<n_j; t++) {
+                                               computeAlignmentP1(i, 0, m_i, j, t, n_j, 1);
+                                       }
+                                       
+                                       DT[i][j] = Float.POSITIVE_INFINITY;
+                                       // Lemma 2 - Case: Root is (blank, j)
+                                       {
+                                               float minCandidate = Float.POSITIVE_INFINITY;
+                                               int best_r = -1;
+                                               for (int r=0; r<n_j; r++) {
+                                                       float d = DT[i][treeData2.children[j][r]] - DET2[treeData2.children[j][r]];
+                                                       if (d < minCandidate) {
+                                                               minCandidate = d;
+                                                               best_r = r;
+                                                       }
+                                               }
+                                               minCandidate += DET2[j];
+                                               if (minCandidate < DT[i][j]) {
+                                                       DT[i][j] = minCandidate;
+                                                       DTDecisions1[i][j] = 1;
+                                                       DTDecisions2[i][j] = (short) best_r;
+                                               }
+                                       }
+                                       // Lemma 2 - Case: Root is (i, blank)
+                                       {
+                                               float minCandidate = Float.POSITIVE_INFINITY;
+                                               int best_r = -1;
+                                               for (int r=0; r<m_i; r++) {
+                                                       float d = DT[treeData1.children[i][r]][j] - DET1[treeData1.children[i][r]];
+                                                       if (d < minCandidate) {
+                                                               minCandidate = d;
+                                                               best_r = r;
+                                                       }
+                                               }
+                                               minCandidate += DET1[i];
+                                               if (minCandidate < DT[i][j]) {
+                                                       DT[i][j] = minCandidate;
+                                                       DTDecisions1[i][j] = 2;
+                                                       DTDecisions2[i][j] = (short) best_r;
+                                               }
+                                       }
+                                       // Lemma 2 - Case: Root is (i,j)
+                                       {
+                                               float minCandidate;
+                                               if (n_j != 0) {
+                                                       minCandidate = DF1 [i] [treeData2.children[j][0]] [m_i] [n_j];
+                                               } else {
+                                                       if (m_i != 0) {
+                                                               minCandidate = DF2 [j] [treeData1.children[i][0]] [m_i] [n_j];
+                                                       } else {
+                                                               minCandidate = 0; // D(empty forest, empty forest) = 0
+                                                       }
+                                               }
+                                               minCandidate += DL[i][j];
+                                               if (minCandidate < DT[i][j]) {
+                                                       DT[i][j] = minCandidate;
+                                                       DTDecisions1[i][j] = 3;
+                                               }
+                                       }
+                                       
+                                       
+                               }
+                       }
+
+                       
+                       // We return the distance beetween T1[root] and T2[root].
+                       return DT[treeData1.size-1][treeData2.size-1];
+               }
+               
+               public Aligner(Tree<ValueType1> T1, Tree<ValueType2> T2) {
+                       treeData1 = new TreeData<ValueType1>();
+                       treeData1.tree = T1;
+                       treeData2 = new TreeData<ValueType2>();
+                       treeData2.tree = T2;
+               }
+               
+               /** Align F1[i_s,i_p] with F2[j_t,j_q].
+                * If p = s-1, by convention it means F1[i_s,i_p] = empty forest.
+                * Idem for q=t-1.
+                */
+               private List<Tree<AlignedNode<ValueType1,ValueType2>>> computeForestAlignment(int i, int s, int p, int j, int t, int q) {
+                       if (p == s-1) { // left forest is the empty forest
+                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result = new ArrayList<Tree<AlignedNode<ValueType1,ValueType2>>>();
+                               for (int k=t; k<=q; k++) {
+                                       result.add(treeInserted(treeData2.children[j][k]));
+                               }
+                               return result;
+                       } else {
+                               if (q == t-1) { // right forest is the empty forest
+                                       List<Tree<AlignedNode<ValueType1,ValueType2>>> result = new ArrayList<Tree<AlignedNode<ValueType1,ValueType2>>>();
+                                       for (int k=s; k<=p; k++) {
+                                               result.add(treeDeleted(treeData1.children[i][k]));
+                                       }
+                                       return result;
+                               } else { // both forests are non-empty
+                                       int decision1, k;
+                                       if (s == 0) {
+                                               decision1 =
+                                                       DF1Decisions1 [i] [treeData2.children[j][t]] [p-s+1] [q-t+1];
+                                               k = 
+                                                       DF1Decisions2 [i] [treeData2.children[j][t]] [p-s+1] [q-t+1];
+                                       } else if (t == 0) {
+                                               decision1 =
+                                                       DF2Decisions1 [j] [treeData1.children[i][s]] [p-s+1] [q-t+1];
+                                               k = 
+                                                       DF2Decisions2 [j] [treeData1.children[i][s]] [p-s+1] [q-t+1];
+                                       } else {
+                                               throw (new Error("TreeAlignSymmetric bug: both s and t are non-zero"));
+                                       }
+                                       switch (decision1) {
+                                       case 1:
+                                       {
+                                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result;
+                                               result = computeForestAlignment(i, s, p-1, j, t, q);
+                                               result.add(treeDeleted(treeData1.children[i][p]));
+                                               return result;
+                                       }
+                                       case 2:
+                                       {
+                                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result;
+                                               result = computeForestAlignment(i, s, p, j, t, q-1);
+                                               result.add(treeInserted(treeData2.children[j][q]));
+                                               return result;
+                                       }
+                                       case 3:
+                                       {
+                                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result;
+                                               result = computeForestAlignment(i, s, p-1, j, t, q-1);
+                                               result.add(computeTreeAlignment(treeData1.children[i][p], treeData2.children[j][q]));
+                                               return result;
+                                       }
+                                       case 4:
+                                       {
+                                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result;
+                                               result = computeForestAlignment(i, s, k-1, j, t, q-1);
+                                               
+                                               int j_q = treeData2.children[j][q];
+                                               Tree<AlignedNode<ValueType1,ValueType2>> insertedNode = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                                               AlignedNode<ValueType1,ValueType2> insertedNodeValue = new AlignedNode<ValueType1,ValueType2>();
+                                               insertedNodeValue.setLeftNode(null);
+                                               insertedNodeValue.setRightNode((Tree<ValueType2>) treeData2.nodes[j_q]);
+                                               insertedNode.setValue(insertedNodeValue);
+                                               
+                                               insertedNode.replaceChildrenListBy(computeForestAlignment(i, k, p, j_q, 0, treeData2.degrees[j_q]-1));
+                                               
+                                               result.add(insertedNode);
+                                               
+                                               return result;
+                                       }
+                                       case 5:
+                                       {
+                                               List<Tree<AlignedNode<ValueType1,ValueType2>>> result;
+                                               result = computeForestAlignment(i, s, p-1, j, t, k-1);
+                                               
+                                               int i_p = treeData1.children[i][p];
+                                               Tree<AlignedNode<ValueType1,ValueType2>> deletedNode = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                                               AlignedNode<ValueType1,ValueType2> deletedNodeValue = new AlignedNode<ValueType1,ValueType2>();
+                                               deletedNodeValue.setLeftNode((Tree<ValueType1>) treeData1.nodes[i_p]);
+                                               deletedNodeValue.setRightNode(null);
+                                               deletedNode.setValue(deletedNodeValue);
+                                               
+                                               deletedNode.replaceChildrenListBy(computeForestAlignment(i_p, 0, treeData1.degrees[i_p]-1, j, k, q));
+                                               
+                                               result.add(deletedNode);
+                                               
+                                               return result;
+                                       }
+                                       default:
+                                               throw (new Error("TreeAlign: decision1 = " + decision1));
+                                       }
+                               }
+                       }
+               }
+               
+               /**
+                * Align T1[i] with the empty tree.
+                * @return the alignment
+                */
+               private Tree<AlignedNode<ValueType1,ValueType2>> treeDeleted(int i) {
+                       Tree<AlignedNode<ValueType1,ValueType2>> root = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                       AlignedNode<ValueType1,ValueType2> alignedNode = new AlignedNode<ValueType1,ValueType2>();
+                       alignedNode.setLeftNode(treeData1.nodes[i]);
+                       alignedNode.setRightNode(null);
+                       root.setValue(alignedNode);
+                       for (int r = 0; r<treeData1.degrees[i]; r++) {
+                               root.getChildren().add(treeDeleted(treeData1.children[i][r]));
+                       }
+                       return root;
+               }
+               
+               /**
+                * Align the empty tree with T2[j].
+                * @return the alignment
+                */
+               private Tree<AlignedNode<ValueType1,ValueType2>> treeInserted(int j) {
+                       Tree<AlignedNode<ValueType1,ValueType2>> root = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                       AlignedNode<ValueType1,ValueType2> alignedNode = new AlignedNode<ValueType1,ValueType2>();
+                       alignedNode.setLeftNode(null);
+                       alignedNode.setRightNode(treeData2.nodes[j]);
+                       root.setValue(alignedNode);
+                       for (int r = 0; r<treeData2.degrees[j]; r++) {
+                               root.getChildren().add(treeInserted(treeData2.children[j][r]));
+                       }
+                       return root;
+               }
+               
+               private Tree<AlignedNode<ValueType1,ValueType2>> computeTreeAlignment(int i, int j) {
+                       switch (DTDecisions1[i][j]) {
+                       case 1:
+                       {
+                               Tree<AlignedNode<ValueType1,ValueType2>> root = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                               
+                               // Compute the value of the node
+                               AlignedNode<ValueType1,ValueType2> alignedNode = new AlignedNode<ValueType1,ValueType2>();
+                               alignedNode.setLeftNode(null);
+                               alignedNode.setRightNode(treeData2.nodes[j]);
+                               root.setValue(alignedNode);
+                               
+                               // Compute the children
+                               for (int r = 0; r<treeData2.degrees[j]; r++) {
+                                       if (r == DTDecisions2[i][j]) {
+                                               root.getChildren().add(computeTreeAlignment(i, treeData2.children[j][r]));
+                                       } else {
+                                               root.getChildren().add(treeInserted(treeData2.children[j][r]));
+                                       }
+                               }
+                               return root;
+                       }
+                       case 2:
+                       {
+                               Tree<AlignedNode<ValueType1,ValueType2>> root = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                               
+                               // Compute the value of the node
+                               AlignedNode<ValueType1,ValueType2> alignedNode = new AlignedNode<ValueType1,ValueType2>();
+                               alignedNode.setLeftNode(treeData1.nodes[i]);
+                               alignedNode.setRightNode(null);
+                               root.setValue(alignedNode);
+                               
+                               // Compute the children
+                               for (int r = 0; r<treeData1.degrees[i]; r++) {
+                                       if (r == DTDecisions2[i][j]) {
+                                               root.getChildren().add(computeTreeAlignment(treeData1.children[i][r], j));
+                                       } else {
+                                               root.getChildren().add(treeDeleted(treeData1.children[i][r]));
+                                       }
+                               }
+                               return root;
+                       }
+                       case 3:
+                       {
+                               Tree<AlignedNode<ValueType1,ValueType2>> root = new Tree<AlignedNode<ValueType1,ValueType2>>();
+                               
+                               // Compute the value of the node
+                               AlignedNode<ValueType1,ValueType2> alignedNode = new AlignedNode<ValueType1,ValueType2>();
+                               alignedNode.setLeftNode(treeData1.nodes[i]);
+                               alignedNode.setRightNode(treeData2.nodes[j]);
+                               root.setValue(alignedNode);
+                               
+                               // Compute the children
+                               List<Tree<AlignedNode<ValueType1,ValueType2>>> children =
+                                       computeForestAlignment(i, 0, treeData1.degrees[i]-1, j, 0, treeData2.degrees[j]-1);
+                               root.replaceChildrenListBy(children);
+                               
+                               return root;
+                       }
+                       default:
+                               throw (new Error("TreeAlign: DTDecisions1[i][j] = " + DTDecisions1[i][j]));
+                       }
+               }
+               
+               public Tree<AlignedNode<ValueType1,ValueType2>> computeAlignment() {
+                       return computeTreeAlignment(treeData1.size-1, treeData2.size-1);
+               }
+               
+       }
+
+       
+       /**
+        * Align T1 with T2, computing both the distance and the alignment.
+        * Time:  O(|T1|*|T2|*(deg(T1)+deg(T2))^2)
+        * Space: O(|T1|*|T2|*(deg(T1)+deg(T2)))
+        * Average (over possible trees) time: O(|T1|*|T2|)
+        * @param T1 The first tree.
+        * @param T2 The second tree.
+        * @return The distance and the alignment.
+        * @throws TreeAlignException 
+        */
+       public TreeAlignResult<ValueType1, ValueType2> align(Tree<ValueType1> T1, Tree<ValueType2> T2) throws TreeAlignException {
+               TreeAlignResult<ValueType1, ValueType2> result = new TreeAlignResult<ValueType1, ValueType2>();
+               Aligner aligner = new Aligner(T1, T2);
+               result.setDistance(aligner.align());
+               result.setAlignment(aligner.computeAlignment());
+               return result;
+       }
+       
+       
+       /**
+        * Takes a alignment, and compute the distance between the two 
+        * original trees. If you have called align(), the result object already
+        * contains the distance D and the alignment A. If you call
+        * distanceFromAlignment on the alignment A it will compute the distance D.
+        */
+       public float distanceFromAlignment(Tree<AlignedNode<ValueType1,ValueType2>> alignment) {
+               Tree<ValueType1> originalT1Node;
+               Tree<ValueType2> originalT2Node;
+               originalT1Node = alignment.getValue().getLeftNode();
+               originalT2Node = alignment.getValue().getRightNode();
+               float d = (float) labelDist.f(
+                               originalT1Node != null ? originalT1Node.getValue() : null,
+                               originalT2Node != null ? originalT2Node.getValue() : null);
+               for (Tree<AlignedNode<ValueType1,ValueType2>> child: alignment.getChildren()) {
+                       d += distanceFromAlignment(child);
+               }
+               return d;
+       }
+
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignException.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignException.java
new file mode 100644 (file)
index 0000000..0ad966d
--- /dev/null
@@ -0,0 +1,14 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * @author Raphael Champeimont
+ *
+ */
+public class TreeAlignException extends Exception {
+
+       private static final long serialVersionUID = 7782935062930780231L;
+
+       public TreeAlignException(String message) {
+               super(message);
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceAsymmetric.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceAsymmetric.java
new file mode 100644 (file)
index 0000000..91aff2f
--- /dev/null
@@ -0,0 +1,18 @@
+package fr.orsay.lri.varna.models.treealign;
+
+public interface TreeAlignLabelDistanceAsymmetric<ValueType1, ValueType2> {
+       /** 
+        * Returns the substitution cost between v1 and v2.
+        * We use the convention that a null reference is a blank,
+        * ie. f(x, null) is the cost of deleting x
+        * and f(null, x) is the cost of inserting x.
+        * We won't use f(null, null).
+        * We suppose f is such that:
+        * f(x,x) = 0
+        * 0 <= f(x,y) < +infinity
+        * You may also want to have the triangle inequality,
+        * although the alignment algorithm does not require it:
+        * f(x,z) <= f(x,y) + f(y,z)
+        */
+       public double f(ValueType1 x, ValueType2 y);
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceSymmetric.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignLabelDistanceSymmetric.java
new file mode 100644 (file)
index 0000000..b702bc0
--- /dev/null
@@ -0,0 +1,12 @@
+package fr.orsay.lri.varna.models.treealign;
+
+/**
+ * Same as TreeAlignLabelDistanceAsymmetric, but elements on both
+ * trees are of the same type, and the function is symmetric, ie.
+ * f(x,y) = f(y,x) .
+ *
+ * @param <ValueType>
+ */
+public interface TreeAlignLabelDistanceSymmetric<ValueType> extends TreeAlignLabelDistanceAsymmetric<ValueType, ValueType> {
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignResult.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeAlignResult.java
new file mode 100644 (file)
index 0000000..f8ff988
--- /dev/null
@@ -0,0 +1,27 @@
+package fr.orsay.lri.varna.models.treealign;
+
+
+/**
+ * The result of aligning a tree T1 with a tree T2.
+ * On the resulting tree, each node has a value
+ * of type AlignedNode<original value type>.
+ * @author Raphael Champeimont
+ */
+public class TreeAlignResult<ValueType1, ValueType2> {
+       private Tree<AlignedNode<ValueType1, ValueType2>> alignment;
+       private double distance;
+       
+       public Tree<AlignedNode<ValueType1, ValueType2>> getAlignment() {
+               return alignment;
+       }
+       public void setAlignment(Tree<AlignedNode<ValueType1, ValueType2>> alignment) {
+               this.alignment = alignment;
+       }
+       public double getDistance() {
+               return distance;
+       }
+       public void setDistance(double distance) {
+               this.distance = distance;
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/models/treealign/TreeGraphviz.java b/srcjar/fr/orsay/lri/varna/models/treealign/TreeGraphviz.java
new file mode 100644 (file)
index 0000000..be22782
--- /dev/null
@@ -0,0 +1,114 @@
+package fr.orsay.lri.varna.models.treealign;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+
+
+/**
+ * This class translates a Tree to a graphviz file.
+ * @author Raphael Champeimont
+ *
+ * @param <? extends GraphvizDrawableNodeValue> the type of values in the tree
+ */
+public class TreeGraphviz {
+
+       /**
+        * Generates a PostScript file using graphviz.
+        * The dot command must be available.
+        */
+       public static void treeToGraphvizPostscript(Tree<? extends GraphvizDrawableNodeValue> tree, String filename, String title) throws IOException {
+               // generate graphviz source
+               String graphvizSource = treeToGraphviz(tree, title);
+               
+               // open output file
+               BufferedWriter fbw;
+               fbw = new BufferedWriter(new FileWriter(filename));
+       
+       // execute graphviz
+               Process proc = Runtime.getRuntime().exec("dot -Tps");
+       BufferedWriter bw  = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream()));
+       BufferedReader br  = new BufferedReader(new InputStreamReader(proc.getInputStream()));
+       BufferedReader bre = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
+       bw.write(graphvizSource);
+       bw.close();
+       {
+               String line = null;
+            while ((line = br.readLine()) != null) {
+               fbw.write(line + "\n");
+            }
+       }
+       {
+               String line = null;
+            while ((line = bre.readLine()) != null) {
+               System.err.println(line);
+            }
+       }
+       
+       // wait for graphviz to end
+        try {
+                       proc.waitFor();
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+
+        // close file
+               fbw.close();
+       }
+       
+       /**
+        * Like treeToGraphvizPostscript(Tree,String,String) but with the title
+        * equal to the filename.
+        */
+       public static void treeToGraphvizPostscript(Tree<? extends GraphvizDrawableNodeValue> tree, String filename) throws IOException {
+               treeToGraphvizPostscript(tree, filename, filename);
+       }
+       
+       /**
+        * Creates a graphviz source file from a Tree.
+        * @param title the title of the graph
+        */
+       public static void treeToGraphvizFile(Tree<? extends GraphvizDrawableNodeValue> tree, String filename, String title) throws IOException {
+               BufferedWriter bw;
+               bw = new BufferedWriter(new FileWriter(filename));
+       bw.write(treeToGraphviz(tree, filename));
+       bw.close();
+       }
+       
+       /**
+        * Like treeToGraphvizFile(Tree,String,String) but with the title
+        * equal to the filename.
+        */
+       public static void treeToGraphvizFile(Tree<? extends GraphvizDrawableNodeValue> tree, String filename) throws IOException {
+               treeToGraphvizFile(tree, filename, filename);
+       }
+       
+       /**
+        * Creates a graphviz source from a Tree.
+        * @param title the title of the graph
+        */
+       public static String treeToGraphviz(Tree<? extends GraphvizDrawableNodeValue> tree, String title) {
+               return "digraph \"" + title + "\" {\n" + subtreeToGraphviz(tree) + "}\n";
+       }
+       
+       private static String subtreeToGraphviz(Tree<? extends GraphvizDrawableNodeValue> tree) {
+               String s = "";
+               String myId = tree.toGraphvizNodeId();
+               
+               s +=
+                       "\""
+                       + myId
+                       + "\" [label=\""
+                       + ((tree.getValue() != null) ? tree.getValue().toGraphvizNodeName() : "null")
+                       + "\"]\n";
+               for (Tree<? extends GraphvizDrawableNodeValue> child: tree.getChildren()) {
+                       s += "\"" + myId + "\" -> \"" + child.toGraphvizNodeId() + "\"\n";
+                       s += subtreeToGraphviz(child);
+               }
+               
+               return s;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/utils/RNAMLParser.java b/srcjar/fr/orsay/lri/varna/utils/RNAMLParser.java
new file mode 100644 (file)
index 0000000..d5f6746
--- /dev/null
@@ -0,0 +1,651 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.utils;
+
+import java.awt.Point;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBP.Edge;
+import fr.orsay.lri.varna.models.rna.ModeleBP.Stericity;
+
+public class RNAMLParser extends DefaultHandler {
+       public class HelixTemp {
+               public int pos5, pos3, length;
+               public String name;
+
+               public HelixTemp(int pos5, int pos3, int length, String name) {
+                       this.pos3 = pos3;
+                       this.pos5 = pos5;
+                       this.length = length;
+                       this.name = name;
+               }
+
+               public String toString() {
+                       return ("[" + name + "," + pos5 + "," + pos3 + "," + length + "]");
+               }
+
+       }
+
+       public class BPTemp {
+               public int pos5, pos3;
+               public String edge5, edge3, orientation;
+
+               public BPTemp(int pos5, int pos3, String edge5, String edge3,
+                               String orientation) {
+                       if (edge3 == null) {
+                               edge3 = "+";
+                       }
+                       if (edge5 == null) {
+                               edge5 = "+";
+                       }
+                       if (orientation == null) {
+                               orientation = "c";
+                       }
+                       this.pos5 = pos5;
+                       this.pos3 = pos3;
+                       this.edge5 = edge5;
+                       this.edge3 = edge3;
+                       this.orientation = orientation;
+               }
+
+               public ModeleBP createBPStyle(ModeleBase mb5, ModeleBase mb3) {
+                       ModeleBP.Edge e5, e3;
+                       @SuppressWarnings("unused")
+                       boolean isCanonical = false;
+                       if (edge5.equals("W")) {
+                               e5 = ModeleBP.Edge.WC;
+                       } else if (edge5.equals("H")) {
+                               e5 = ModeleBP.Edge.HOOGSTEEN;
+                       } else if (edge5.equals("S")) {
+                               e5 = ModeleBP.Edge.SUGAR;
+                       } else {
+                               e5 = ModeleBP.Edge.WC;
+                       }
+
+                       if (edge3.equals("W")) {
+                               e3 = ModeleBP.Edge.WC;
+                       } else if (edge3.equals("H")) {
+                               e3 = ModeleBP.Edge.HOOGSTEEN;
+                       } else if (edge3.equals("S")) {
+                               e3 = ModeleBP.Edge.SUGAR;
+                       } else {
+                               e3 = ModeleBP.Edge.WC;
+                       }
+
+                       if ((edge5.equals("+") && edge3.equals("+"))
+                                       || (edge5.equals("-") && edge3.equals("-"))) {
+                               e3 = ModeleBP.Edge.WC;
+                               e5 = ModeleBP.Edge.WC;
+                       }
+
+                       ModeleBP.Stericity ster;
+
+                       if (orientation.equals("c")) {
+                               ster = ModeleBP.Stericity.CIS;
+                       } else if (orientation.equals("t")) {
+                               ster = ModeleBP.Stericity.TRANS;
+                       } else {
+                               ster = ModeleBP.Stericity.CIS;
+                       }
+
+                       return (new ModeleBP(mb5, mb3, e5, e3, ster));
+               }
+
+               public String toString() {
+                       return ("[" + pos5 + "," + pos3 + "," + edge5 + "," + edge3 + ","
+                                       + orientation + "]");
+               }
+       }
+
+       public class RNATmp {
+               public ArrayList<String> _sequence = new ArrayList<String>();
+               public Vector<Integer> _sequenceIDs = new Vector<Integer>();
+               public Vector<BPTemp> _structure = new Vector<BPTemp>();
+               public Vector<HelixTemp> _helices = new Vector<HelixTemp>();
+
+               public ArrayList<String> getSequence() {
+                       return _sequence;
+               }
+
+               public Vector<BPTemp> getStructure() {
+                       return _structure;
+               }
+       };
+
+       private Hashtable<String, RNATmp> _molecules = new Hashtable<String, RNATmp>();
+
+       private boolean _inSequenceIDs, _inLength, _inSequence, _inHelix,
+                       _inStrAnnotation, _inBP, _inBP5, _inBP3, _inEdge5, _inEdge3,
+                       _inPosition, _inBondOrientation, _inMolecule;
+       private StringBuffer _buffer;
+       private String _currentModel = "";
+       private int _id5, _id3, _length;
+       String _edge5, _edge3, _orientation, _helixID;
+
+       public RNAMLParser() {
+               super();
+               _inSequenceIDs = false;
+               _inSequence = false;
+               _inStrAnnotation = false;
+               _inBP = false;
+               _inBP5 = false;
+               _inBP3 = false;
+               _inPosition = false;
+               _inEdge5 = false;
+               _inEdge3 = false;
+               _inBondOrientation = false;
+               _inHelix = false;
+               _inMolecule = false;
+       }
+
+       public InputSource createSourceFromURL(String path) {
+               URL url = null;
+               try {
+                       url = new URL(path);
+                       URLConnection connexion = url.openConnection();
+                       connexion.setUseCaches(false);
+                       InputStream r = connexion.getInputStream();
+                       InputStreamReader inr = new InputStreamReader(r);
+                       return new InputSource(inr);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               return new InputSource(new StringReader(""));
+       }
+
+       public InputSource resolveEntity(String publicId, String systemId) {
+               // System.out.println("[crade]");
+               if (systemId.endsWith("rnaml.dtd"))
+               {
+                       String resourceName = "/rnaml.dtd";
+                       URL url = ClassLoader.getSystemResource(resourceName);
+                       if (url!=null)
+                       {
+                               try {
+                                       InputStream stream = url.openStream();
+                                       if (stream != null)
+                                       {
+                                               return new InputSource(stream );
+                                       }
+                               } catch (IOException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+               return new InputSource(new StringReader(""));
+       }
+
+       public void startElement(String uri, String localName, String qName,
+                       Attributes attributes) throws SAXException {
+               if (qName.equals("numbering-table")) {
+                       _inSequenceIDs = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("helix")) {
+                       _inHelix = true;
+                       _buffer = new StringBuffer();
+                       _helixID = attributes.getValue("id");
+               } else if (qName.equals("seq-data")) {
+                       _inSequence = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("length")) {
+                       _inLength = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("str-annotation")) {
+                       _inStrAnnotation = true;
+               } else if (qName.equals("base-pair")) {
+                       _inBP = true;
+               } else if (qName.equals("base-id-5p")) {
+                       if (_inBP || _inHelix) {
+                               _inBP5 = true;
+                       }
+               } else if (qName.equals("base-id-3p")) {
+                       if (_inBP || _inHelix) {
+                               _inBP3 = true;
+                       }
+               } else if (qName.equals("edge-5p")) {
+                       _inEdge5 = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("edge-3p")) {
+                       _inEdge3 = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("position")) {
+                       _inPosition = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("bond-orientation")) {
+                       _inBondOrientation = true;
+                       _buffer = new StringBuffer();
+               } else if (qName.equals("molecule")) {
+                       _inMolecule = true;
+                       String id = (attributes.getValue("id"));
+                       // System.err.println("Molecule#"+id);
+                       _molecules.put(id, new RNATmp());
+                       _currentModel = id;
+               } else {
+                       // We don't care too much about the rest ...
+               }
+       }
+
+       public void endElement(String uri, String localName, String qName)
+                       throws SAXException {
+               if (qName.equals("numbering-table")) {
+                       _inSequenceIDs = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       String[] tokens = content.split("\\s+");
+                       Vector<Integer> results = new Vector<Integer>();
+                       for (int i = 0; i < tokens.length; i++) {
+                               try {
+                                       results.add(new Integer(Integer.parseInt(tokens[i])));
+                               } catch (NumberFormatException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+                       _molecules.get(_currentModel)._sequenceIDs = results;
+                       _buffer = null;
+               } else if (qName.equals("seq-data")) {
+                       _inSequence = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       String[] tokens = content.split("\\s+");
+                       ArrayList<String> results = new ArrayList<String>();
+                       for (int i = 0; i < tokens.length; i++) {
+                               for (int j = 0; j < tokens[i].length(); j++)
+                                       results.add("" + tokens[i].charAt(j));
+                       }
+                       // System.err.println("  Seq: "+results);
+                       _molecules.get(_currentModel)._sequence = results;
+                       _buffer = null;
+               } else if (qName.equals("bond-orientation")) {
+                       _inBondOrientation = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       _orientation = content;
+                       _buffer = null;
+               } else if (qName.equals("str-annotation")) {
+                       _inStrAnnotation = false;
+               } else if (qName.equals("base-pair")) {
+                       if (_inMolecule) {
+                               _inBP = false;
+                               BPTemp bp = new BPTemp(_id5, _id3, _edge5, _edge3, _orientation);
+                               _molecules.get(_currentModel)._structure.add(bp);
+                               // System.err.println("  "+bp);
+                       }
+               } else if (qName.equals("helix")) {
+                       _inHelix = false;
+                       if (_inMolecule) {
+                               HelixTemp h = new HelixTemp(_id5, _id3, _length, _helixID);
+                               _molecules.get(_currentModel)._helices.add(h);
+                       }
+               } else if (qName.equals("base-id-5p")) {
+                       _inBP5 = false;
+               } else if (qName.equals("base-id-3p")) {
+                       _inBP3 = false;
+               } else if (qName.equals("length")) {
+                       _inLength = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       _length = Integer.parseInt(content);
+                       _buffer = null;
+               } else if (qName.equals("position")) {
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       int pos = Integer.parseInt(content);
+                       if (_inBP5) {
+                               _id5 = pos;
+                       }
+                       if (_inBP3) {
+                               _id3 = pos;
+                       }
+                       _buffer = null;
+               } else if (qName.equals("edge-5p")) {
+                       _inEdge5 = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       _edge5 = content;
+                       _buffer = null;
+               } else if (qName.equals("edge-3p")) {
+                       _inEdge3 = false;
+                       String content = _buffer.toString();
+                       content = content.trim();
+                       _edge3 = content;
+                       _buffer = null;
+               } else if (qName.equals("molecule")) {
+                       _inMolecule = false;
+               } else {
+                       // We don't care too much about the rest ...
+               }
+       }
+
+       public void characters(char[] ch, int start, int length)
+                       throws SAXException {
+               String lecture = new String(ch, start, length);
+               if (_buffer != null)
+                       _buffer.append(lecture);
+       }
+
+       public void startDocument() throws SAXException {
+       }
+
+       public void endDocument() throws SAXException {
+               postProcess();
+       }
+
+       // Discarding stacking interactions...
+       private void discardStacking() {
+               Vector<BPTemp> result = new Vector<BPTemp>();
+               for (int i = 0; i < _molecules.get(_currentModel)._structure.size(); i++) {
+                       BPTemp bp = _molecules.get(_currentModel)._structure.get(i);
+                       if (bp.orientation.equals("c") || bp.orientation.equals("t")) {
+                               result.add(bp);
+                       }
+               }
+               _molecules.get(_currentModel)._structure = result;
+       }
+
+       public static boolean isSelfCrossing(int[] str) {
+               Stack<Point> intervals = new Stack<Point>();
+               intervals.add(new Point(0, str.length - 1));
+               while (!intervals.empty()) {
+                       Point p = intervals.pop();
+                       if (p.x <= p.y) {
+                               if (str[p.x] == -1) {
+                                       intervals.push(new Point(p.x + 1, p.y));
+                               } else {
+                                       int i = p.x;
+                                       int j = p.y;
+                                       int k = str[i];
+                                       if ((k <= i) || (k > j)) {
+                                               return true;
+                                       } else {
+                                               intervals.push(new Point(i + 1, k - 1));
+                                               intervals.push(new Point(k + 1, j));
+                                       }
+                               }
+                       }
+               }
+               return false;
+       }
+
+       @SuppressWarnings("unused")
+       private void debugPrintArray(Object[] str) {
+               StringBuffer s = new StringBuffer("[");
+               for (int i = 0; i < str.length; i++) {
+                       if (i != 0) {
+                               s.append(",");
+                       }
+                       s.append(str[i]);
+
+               }
+               s.append("]");
+               System.out.println(s.toString());
+       }
+
+       /**
+        * Computes and returns a maximal planar subset of the current structure.
+        * 
+        * @param str
+        *            A sequence of base-pairing positions
+        * @return A sequence of non-crossing base-pairing positions
+        */
+
+       public static int[] planarize(int[] str) {
+               if (!isSelfCrossing(str)) {
+                       return str;
+               }
+
+               int length = str.length;
+
+               int[] result = new int[length];
+               for (int i = 0; i < result.length; i++) {
+                       result[i] = -1;
+               }
+
+               short[][] tab = new short[length][length];
+               short[][] backtrack = new short[length][length];
+               int theta = 3;
+
+               for (int i = 0; i < result.length; i++) {
+                       for (int j = i; j < Math.min(i + theta, result.length); j++) {
+                               tab[i][j] = 0;
+                               backtrack[i][j] = -1;
+                       }
+               }
+               for (int n = theta; n < length; n++) {
+                       for (int i = 0; i < length - n; i++) {
+                               int j = i + n;
+                               tab[i][j] = tab[i + 1][j];
+                               backtrack[i][j] = -1;
+                               int k = str[i];
+                               if ((k != -1) && (k <= j) && (i < k)) {
+                                       int tmp = 1;
+                                       if (i + 1 <= k - 1) {
+                                               tmp += tab[i + 1][k - 1];
+                                       }
+                                       if (k + 1 <= j) {
+                                               tmp += tab[k + 1][j];
+                                       }
+                                       if (tmp > tab[i][j]) {
+                                               tab[i][j] = (short) tmp;
+                                               backtrack[i][j] = (short) k;
+                                       }
+                               }
+                       }
+               }
+               Stack<Point> intervals = new Stack<Point>();
+               intervals.add(new Point(0, length - 1));
+               while (!intervals.empty()) {
+                       Point p = intervals.pop();
+                       if (p.x <= p.y) {
+                               if (backtrack[p.x][p.y] == -1) {
+                                       result[p.x] = -1;
+                                       intervals.push(new Point(p.x + 1, p.y));
+                               } else {
+                                       int i = p.x;
+                                       int j = p.y;
+                                       int k = backtrack[p.x][p.y];
+                                       result[i] = k;
+                                       result[k] = i;
+                                       intervals.push(new Point(i + 1, k - 1));
+                                       intervals.push(new Point(k + 1, j));
+                               }
+                       }
+               }
+               return result;
+       }
+
+       public static void planarize(ArrayList<ModeleBP> input,
+                       ArrayList<ModeleBP> planar, ArrayList<ModeleBP> others, int length) {
+               // System.err.println("Planarize: Length:"+length);
+               Hashtable<Integer, ArrayList<ModeleBP>> index2BPs = new Hashtable<Integer, ArrayList<ModeleBP>>();
+               for (ModeleBP msbp : input) {
+                       int i = msbp.getPartner5().getIndex();
+                       if (!index2BPs.containsKey(i)) {
+                               index2BPs.put(i, new ArrayList<ModeleBP>());
+                       }
+                       index2BPs.get(i).add(msbp);
+               }
+               // System.err.println(index2BPs);
+
+               short[][] tab = new short[length][length];
+               short[][] backtrack = new short[length][length];
+               int theta = 3;
+
+               for (int i = 0; i < length; i++) {
+                       for (int j = i; j < Math.min(i + theta, length); j++) {
+                               tab[i][j] = 0;
+                               backtrack[i][j] = -1;
+                       }
+               }
+               for (int n = theta; n < length; n++) {
+                       for (int i = 0; i < length - n; i++) {
+                               int j = i + n;
+                               tab[i][j] = tab[i + 1][j];
+                               backtrack[i][j] = -1;
+                               if (index2BPs.containsKey(i)) {
+                                       ArrayList<ModeleBP> vi = index2BPs.get(i);
+                                       // System.err.print(".");
+                                       for (int numBP = 0; numBP < vi.size(); numBP++) {
+                                               ModeleBP mb = vi.get(numBP);
+                                               int k = mb.getPartner3().getIndex();
+                                               if ((k != -1) && (k <= j) && (i < k)) {
+                                                       int tmp = 1;
+                                                       if (i + 1 <= k - 1) {
+                                                               tmp += tab[i + 1][k - 1];
+                                                       }
+                                                       if (k + 1 <= j) {
+                                                               tmp += tab[k + 1][j];
+                                                       }
+                                                       if (tmp > tab[i][j]) {
+                                                               tab[i][j] = (short) tmp;
+                                                               backtrack[i][j] = (short) numBP;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               // System.err.println("DP table: "+tab[0][length-1]);
+
+               // Backtracking
+               Stack<Point> intervals = new Stack<Point>();
+               intervals.add(new Point(0, length - 1));
+               while (!intervals.empty()) {
+                       Point p = intervals.pop();
+                       if (p.x <= p.y) {
+                               if (backtrack[p.x][p.y] == -1) {
+                                       intervals.push(new Point(p.x + 1, p.y));
+                               } else {
+                                       int i = p.x;
+                                       int j = p.y;
+                                       int nb = backtrack[p.x][p.y];
+                                       ModeleBP mb = index2BPs.get(i).get(nb);
+                                       int k = mb.getPartner3().getIndex();
+                                       planar.add(mb);
+                                       intervals.push(new Point(i + 1, k - 1));
+                                       intervals.push(new Point(k + 1, j));
+                               }
+                       }
+               }
+
+               // Remaining base pairs
+               for (int i : index2BPs.keySet()) {
+                       ArrayList<ModeleBP> vi = index2BPs.get(i);
+                       for (ModeleBP mb : vi) {
+                               if (!planar.contains(mb)) {
+                                       others.add(mb);
+                               }
+                       }
+               }
+       }
+
+       private void postProcess() {
+               for (RNATmp r : _molecules.values()) {
+                       // First, check if base numbers were specified
+                       if (r._sequenceIDs.size() == 0) {
+                               Vector<Integer> results = new Vector<Integer>();
+                               for (int i = 0; i < r._sequence.size(); i++) {
+                                       results.add(new Integer(i + 1));
+                               }
+                               r._sequenceIDs = results;
+                       }
+                       // System.err.println("IDs: "+_sequenceIDs);
+                       // System.err.println("Before remapping: "+_structure);
+
+                       // Then, build inverse mapping ID => index
+                       Hashtable<Integer, Integer> ID2Index = new Hashtable<Integer, Integer>();
+                       for (int i = 0; i < r._sequenceIDs.size(); i++) {
+                               ID2Index.put(r._sequenceIDs.get(i), i);
+                       }
+
+                       // Translate BP coordinates into indices
+                       for (BPTemp bp : r._structure) {
+                               bp.pos3 = bp.pos3 - 1;
+                               bp.pos5 = bp.pos5 - 1;
+                       }
+                       // System.err.println("After remapping: "+_structure);
+
+                       discardStacking();
+                       // System.err.println("  Discard stacking (length="+r._sequence.size()+") => "+r._structure);
+
+                       // Eliminate redundancy
+                       Hashtable<Integer, Hashtable<Integer,BPTemp>> index2BPs = new Hashtable<Integer, Hashtable<Integer,BPTemp>>();
+                       for (BPTemp msbp : r._structure) {
+                               int i = msbp.pos5;
+                               if (!index2BPs.containsKey(i)) {
+                                       index2BPs.put(i, new Hashtable<Integer,BPTemp>());
+                               }
+                               if (!index2BPs.get(i).contains(msbp.pos3)) {
+                                       index2BPs.get(i).put(msbp.pos3,msbp);
+                               }
+                       }
+                       
+                       // Adding helices...
+                       for (int i = 0; i < r._helices.size(); i++) {
+                               HelixTemp h = r._helices.get(i);
+                               for (int j = 0; j < h.length; j++) {
+                                       // System.err.println("Looking for residues: "+(h.pos5+j-1)+" and "+(h.pos3-j-1));
+                                       int a = (h.pos5 + j - 1);
+                                       int b = (h.pos3 - j - 1);
+                                       BPTemp bp = new BPTemp(a, (b), "+", "+", "c");
+                                       if (!index2BPs.containsKey(a)) {
+                                               index2BPs.put(a, new Hashtable<Integer,BPTemp>());
+                                       }
+                                       if (!index2BPs.get(a).contains(b)) {
+                                               index2BPs.get(a).put(b,bp);
+                                       }
+                               }
+                       }
+
+                       Vector<BPTemp> newStructure = new Vector<BPTemp>();
+                       for (int i : index2BPs.keySet()) {
+                               for (int j : index2BPs.get(i).keySet()) {
+                                       BPTemp bp = index2BPs.get(i).get(j);
+                                       newStructure.add(bp);
+                               }
+                       }
+                       r._structure = newStructure;
+
+                       // System.err.println("After Helices => "+_structure);
+
+
+                       // System.err.println("After Postprocess => "+_structure);
+               }
+       }
+
+       public ArrayList<RNATmp> getMolecules() {
+               return new ArrayList<RNATmp>(_molecules.values());
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/utils/TranslateFormatRNaseP.java b/srcjar/fr/orsay/lri/varna/utils/TranslateFormatRNaseP.java
new file mode 100644 (file)
index 0000000..7c999e5
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.utils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TranslateFormatRNaseP {
+       public static void main(String[] args) throws Exception {
+               File templatesDir = new File(new File(System.getProperty("user.dir")), "templates");
+               File infile = new File(new File(templatesDir, "RNaseP_bact_a"), "a_bacterial_rnas.gb");
+               File outfile = new File(new File(templatesDir, "RNaseP_bact_a"), "alignment.fasta");
+               
+               BufferedReader inbuf = new BufferedReader(new FileReader(infile));
+               String line = inbuf.readLine();
+               String seqname;
+               List<String> seqnames = new ArrayList<String>();
+               List<String> sequences = new ArrayList<String>();
+               while (line != null) {
+                       if (line.length() != 0) {
+                               if (line.startsWith("LOCUS")) {
+                                       String parts[] = line.split("\\s+");
+                                       seqname = parts[1];
+                                       seqnames.add(seqname);
+                                       sequences.add("");
+                               }
+                               if (line.startsWith(" ")) {
+                                       String parts[] = line.split("\\s+");
+                                       for (int i=2; i<parts.length; i++) {
+                                               sequences.set(sequences.size()-1, sequences.get(sequences.size()-1) + parts[i]);
+                                       }
+                               }
+                       }
+                       line = inbuf.readLine();
+               }
+               inbuf.close();
+               
+               BufferedWriter outbuf = new BufferedWriter(new FileWriter(outfile));
+               for (int i=2; i<seqnames.size(); i++) {
+                       outbuf.write(">" + seqnames.get(i) + "\n");
+                       outbuf.write(sequences.get(i) + "\n");
+               }
+               outbuf.close();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/utils/VARNASessionParser.java b/srcjar/fr/orsay/lri/varna/utils/VARNASessionParser.java
new file mode 100644 (file)
index 0000000..7eb510a
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.utils;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation.ChemProbAnnotationType;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation.AnchorType;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBP.Edge;
+import fr.orsay.lri.varna.models.rna.ModeleBP.Stericity;
+import fr.orsay.lri.varna.models.rna.ModeleBPStyle;
+import fr.orsay.lri.varna.models.rna.ModeleBackbone;
+import fr.orsay.lri.varna.models.rna.ModeleBackboneElement;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBasesComparison;
+import fr.orsay.lri.varna.models.rna.ModelBaseStyle;
+import fr.orsay.lri.varna.models.rna.RNA;
+import fr.orsay.lri.varna.models.rna.VARNAPoint;
+
+public class VARNASessionParser extends DefaultHandler {
+
+       StringBuffer _buffer = null;
+       ModeleBaseNucleotide mbn = null;
+       ModeleBasesComparison mbc = null;
+       ModeleBP mbp = null;
+       ModeleBPStyle mbps = null;
+       ModelBaseStyle msb = null;
+       TextAnnotation ta = null;
+       ModeleBackbone backbone = null;
+       HighlightRegionAnnotation hra = null;
+       RNA rna = null;
+       Font f = null;
+       VARNAConfig config = null;
+       
+    public VARNASessionParser() {
+               super();
+       }
+
+       public InputSource createSourceFromURL(String path)
+       {
+               URL url = null;
+               try {
+                       url = new URL(path);
+                       URLConnection connexion = url.openConnection();
+                       connexion.setUseCaches(false);
+                       InputStream r = connexion.getInputStream();
+                       InputStreamReader inr = new InputStreamReader(r);
+                       return new InputSource(inr);
+               }
+               catch(Exception e)
+               {
+                       e.printStackTrace();
+               }
+               return new InputSource(new StringReader(""));
+       }
+       
+       public InputSource resolveEntity(String publicId, String systemId) {
+                       return new InputSource(new StringReader(""));
+       }
+       
+
+       private TreeSet<String> _context = new TreeSet<String>();
+       
+       private void addToContext(String s)
+       {
+               _context.add(s);
+       }
+
+       private void removeFromContext(String s)
+       {
+               _context.remove(s);
+       }
+
+       private boolean contextContains(String s)
+       {
+               return _context.contains(s);
+       }
+
+       public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+               if (qName.equals(VARNAPanel.XML_ELEMENT_NAME)) {
+               }
+               else if (qName.equals(VARNAConfig.XML_ELEMENT_NAME)){
+                       config = new VARNAConfig();
+                       config.loadFromXMLAttributes(attributes);
+               }
+               else if (qName.equals(RNA.XML_ELEMENT_NAME)){
+                       rna = new RNA();
+                       int mode = Integer.parseInt(attributes.getValue(RNA.XML_VAR_DRAWN_MODE_NAME));
+                       rna.setDrawMode(mode);
+               }
+               else if (qName.equals(ModeleBackbone.XML_ELEMENT_NAME)){
+                       backbone = new ModeleBackbone();
+                       rna.setBackbone(backbone);
+               }
+               else if (qName.equals(ModeleBackboneElement.XML_ELEMENT_NAME)){
+                       if (backbone!=null){
+                               int index = Integer.parseInt(attributes.getValue(ModeleBackboneElement.XML_VAR_INDEX_NAME));
+                               ModeleBackboneElement.BackboneType type = ModeleBackboneElement.BackboneType.getType(                   
+                                               (attributes.getValue(ModeleBackboneElement.XML_VAR_TYPE_NAME)));
+                               Color c = null;
+                               if (type == ModeleBackboneElement.BackboneType.CUSTOM_COLOR)
+                               {
+                                       c = Color.decode(attributes.getValue(TextAnnotation.XML_VAR_COLOR_NAME));
+                                       backbone.addElement(new ModeleBackboneElement(index, c));
+                               }
+                               else
+                               {
+                                       backbone.addElement(new ModeleBackboneElement(index, type));                                    
+                               }
+                       }
+               }
+               else if (qName.equals(ModeleBackbone.XML_ELEMENT_NAME)){
+                       backbone = new ModeleBackbone();
+               }
+               else if (qName.equals(ModeleBaseNucleotide.XML_ELEMENT_NAME)){
+                       if (rna!=null){
+                               mbn = new ModeleBaseNucleotide(rna.getSize());
+                               if (mbn.getIndex()!=Integer.parseInt(attributes.getValue(ModeleBase.XML_VAR_INDEX_NAME)))
+                                       throw new SAXException("Index mismatch for Base");
+                               mbn.setBaseNumber(Integer.parseInt(attributes.getValue(ModeleBase.XML_VAR_NUMBER_NAME)));
+                               mbn.setLabel(attributes.getValue(ModeleBase.XML_VAR_LABEL_NAME));
+                               mbn.setColorie(Boolean.parseBoolean(attributes.getValue(ModeleBase.XML_VAR_CUSTOM_DRAWN_NAME)));
+                               mbn.setValue(Double.parseDouble(attributes.getValue(ModeleBase.XML_VAR_VALUE_NAME)));
+                               rna.addBase(mbn);
+                       }
+               }
+               else if (qName.equals(XMLUtils.XML_FONT_ELEMENT_NAME)){
+                       f = XMLUtils.getFont(qName,attributes);
+                       if (contextContains(TextAnnotation.XML_ELEMENT_NAME))
+                       {
+                               ta.setFont(f);
+                               f=null;
+                       }
+                       else if (contextContains(VARNAConfig.XML_ELEMENT_NAME))
+                       {
+                               String role = attributes.getValue(XMLUtils.XML_ROLE_NAME);
+                               if (role.equals(VARNAConfig.XML_VAR_TITLE_FONT))
+                               {
+                                       config._titleFont = XMLUtils.getFont(qName, attributes);
+                               }
+                               else if (role.equals(VARNAConfig.XML_VAR_NUMBERS_FONT))
+                               {
+                                       config._numbersFont = XMLUtils.getFont(qName, attributes);
+                               }
+                               else if (role.equals(VARNAConfig.XML_VAR_FONT_BASES))
+                               {
+                                       config._fontBasesGeneral = XMLUtils.getFont(qName, attributes);
+                               }
+                       }
+               }
+               else if (qName.equals(ModeleBaseNucleotide.XML_VAR_CONTENT_NAME)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(ModeleBasesComparison.XML_VAR_FIRST_CONTENT_NAME)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(ModeleBasesComparison.XML_VAR_SECOND_CONTENT_NAME)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(ModeleBasesComparison.XML_ELEMENT_NAME)){
+                       if (rna!=null){
+                               mbc = new ModeleBasesComparison(rna.getSize());
+                               if (mbc.getIndex()!=Integer.parseInt(attributes.getValue(ModeleBase.XML_VAR_INDEX_NAME)))
+                                       throw new SAXException("Index mismatch for Base");
+                               mbc.setBaseNumber(Integer.parseInt(attributes.getValue(ModeleBase.XML_VAR_NUMBER_NAME)));
+                               mbc.setLabel(attributes.getValue(ModeleBase.XML_VAR_LABEL_NAME));
+                               mbc.set_appartenance(Integer.parseInt(attributes.getValue(ModeleBasesComparison.XML_VAR_MEMBERSHIP_NAME)));
+                               mbc.setColorie(Boolean.parseBoolean(attributes.getValue(ModeleBase.XML_VAR_CUSTOM_DRAWN_NAME)));
+                               mbc.setValue(Double.parseDouble(attributes.getValue(ModeleBase.XML_VAR_VALUE_NAME)));
+                               rna.addBase(mbc);
+                       }
+               }
+               else if (qName.equals(RNA.XML_VAR_NAME_NAME))
+               {
+                       if (rna!=null){
+                               _buffer = new StringBuffer();
+                       }                       
+               }
+               else if (qName.equals(ModeleBP.XML_ELEMENT_NAME))
+               {
+                       Edge e5 = Edge.valueOf(attributes.getValue(ModeleBP.XML_VAR_EDGE5_NAME));
+                       Edge e3 = Edge.valueOf(attributes.getValue(ModeleBP.XML_VAR_EDGE3_NAME));
+                       Stericity s = Stericity.valueOf(attributes.getValue(ModeleBP.XML_VAR_STERICITY_NAME));
+                       int i5 = Integer.parseInt(attributes.getValue(ModeleBP.XML_VAR_PARTNER5_NAME)); 
+                       int i3 = Integer.parseInt(attributes.getValue(ModeleBP.XML_VAR_PARTNER3_NAME)); 
+                       boolean inSecStr = Boolean.parseBoolean(attributes.getValue(ModeleBP.XML_VAR_SEC_STR_NAME)); 
+                       mbp = new ModeleBP(rna.getBaseAt(i5),rna.getBaseAt(i3),e5,e3,s);
+                       if (inSecStr)
+                               rna.addBP(i5,i3,mbp);
+                       else
+                               rna.addBPAux(i5,i3,mbp);
+               }
+               else if (qName.equals(ChemProbAnnotation.XML_ELEMENT_NAME))
+               {
+                       int i5 = Integer.parseInt(attributes.getValue(ChemProbAnnotation.XML_VAR_INDEX5_NAME)); 
+                       int i3 = Integer.parseInt(attributes.getValue(ChemProbAnnotation.XML_VAR_INDEX3_NAME));
+                       ChemProbAnnotation cpa = new ChemProbAnnotation(rna.getBaseAt(i5),rna.getBaseAt(i3));
+                       cpa.setColor(Color.decode(attributes.getValue(ChemProbAnnotation.XML_VAR_COLOR_NAME)));
+                       cpa.setIntensity(Double.parseDouble(attributes.getValue(ChemProbAnnotation.XML_VAR_INTENSITY_NAME)));
+                       cpa.setType(ChemProbAnnotationType.valueOf(attributes.getValue(ChemProbAnnotation.XML_VAR_TYPE_NAME)));
+                       cpa.setOut(Boolean.parseBoolean(attributes.getValue(ChemProbAnnotation.XML_VAR_OUTWARD_NAME)));
+                       rna.addChemProbAnnotation(cpa);
+               }
+               else if (qName.equals(TextAnnotation.XML_VAR_TEXT_NAME)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(VARNAConfig.XML_VAR_TITLE)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(VARNAConfig.XML_VAR_CM_CAPTION)){
+                       _buffer = new StringBuffer();
+               }
+               else if (qName.equals(TextAnnotation.XML_ELEMENT_NAME))
+               {
+                       AnchorType t = AnchorType.valueOf(attributes.getValue(TextAnnotation.XML_VAR_TYPE_NAME));
+                       ta = new TextAnnotation("");
+                       ta.setColor(Color.decode(attributes.getValue(TextAnnotation.XML_VAR_COLOR_NAME)));
+                       ta.setAngleInDegres(Double.parseDouble(attributes.getValue(TextAnnotation.XML_VAR_ANGLE_NAME)));
+                       ta.setType(t);
+               }
+               else if (qName.equals(HighlightRegionAnnotation.XML_ELEMENT_NAME))
+               {
+                       hra = new HighlightRegionAnnotation();
+                       rna.addHighlightRegion(hra);
+                       hra.setOutlineColor(Color.decode(attributes.getValue(HighlightRegionAnnotation.XML_VAR_OUTLINE_NAME)));
+                       hra.setFillColor(Color.decode(attributes.getValue(HighlightRegionAnnotation.XML_VAR_FILL_NAME)));
+                       hra.setRadius(Double.parseDouble(attributes.getValue(HighlightRegionAnnotation.XML_VAR_RADIUS_NAME)));
+               }
+               else if (qName.equals(XMLUtils.XML_BASELIST_ELEMENT_NAME))
+               {
+                       _buffer = new StringBuffer();                   
+               }
+               else if (qName.equals(VARNAPoint.XML_ELEMENT_NAME))
+               {
+                       Point2D.Double vp = new Point2D.Double();
+                       vp.x = Double.parseDouble(attributes.getValue(VARNAPoint.XML_VAR_X_NAME));
+                       vp.y = Double.parseDouble(attributes.getValue(VARNAPoint.XML_VAR_Y_NAME));
+                       String role = attributes.getValue(VARNAPoint.XML_VAR_ROLE_NAME);
+                       if (contextContains(ModeleBaseNucleotide.XML_ELEMENT_NAME))
+                       {
+                               if (role != null)
+                               {
+                                       if (role.equals(ModeleBase.XML_VAR_POSITION_NAME))
+                                       {
+                                               if (mbn!=null)
+                                               {  mbn.setCoords(vp);  }
+                                               else throw new SAXException("No Base model for this position Point");
+                                       }
+                                       else if (role.equals(ModeleBase.XML_VAR_CENTER_NAME))
+                                       {
+                                               if (mbn!=null)
+                                               {  mbn.setCenter(vp);  }
+                                               else throw new SAXException("No Base model for this center Point");
+                                       }
+                                       
+                               }                               
+                       }
+                       if (contextContains(ModeleBasesComparison.XML_ELEMENT_NAME))
+                       {
+                               if (role != null)
+                               {
+                                       if (role.equals(ModeleBase.XML_VAR_POSITION_NAME))
+                                       {
+                                               if (mbc!=null)
+                                               {  mbc.setCoords(vp);  }                                        
+                                               else throw new SAXException("No Base model for this position Point");
+                                       }
+                                       else if (role.equals(ModeleBase.XML_VAR_CENTER_NAME))
+                                       {
+                                               if (mbc!=null)
+                                               {  mbc.setCenter(vp);  }
+                                               else throw new SAXException("No Base model for this center Point");
+                                       }
+                               }
+                       }
+                       if (contextContains(TextAnnotation.XML_ELEMENT_NAME))
+                       {
+                               if (ta!=null)
+                               ta.setAncrage(vp.x,vp.y);
+                               else throw new SAXException("No TextAnnotation model for this Point");
+                       }
+               }
+               else if (qName.equals(ModelBaseStyle.XML_ELEMENT_NAME))
+               {
+                       msb = new ModelBaseStyle();
+                       msb.setBaseOutlineColor(Color.decode(attributes.getValue(ModelBaseStyle.XML_VAR_OUTLINE_NAME)));
+                       msb.setBaseInnerColor(Color.decode(attributes.getValue(ModelBaseStyle.XML_VAR_INNER_NAME)));
+                       msb.setBaseNameColor(Color.decode(attributes.getValue(ModelBaseStyle.XML_VAR_NAME_NAME)));
+                       msb.setBaseNumberColor(Color.decode(attributes.getValue(ModelBaseStyle.XML_VAR_NUMBER_NAME)));
+                       if (mbn!=null)
+                       {  mbn.setStyleBase(msb);  }
+                       else if (mbc!=null)
+                       {  mbc.setStyleBase(msb);  }
+                       msb = null;
+               }
+               else if (qName.equals(ModeleBPStyle.XML_ELEMENT_NAME))
+               {
+                       mbps = new ModeleBPStyle();
+                       boolean customColor = Boolean.parseBoolean(attributes.getValue(ModeleBPStyle.XML_VAR_CUSTOM_STYLED_NAME));
+                       if (customColor)
+                               mbps.setCustomColor(Color.decode(attributes.getValue(ModeleBPStyle.XML_VAR_COLOR_NAME)));
+                       mbps.setThickness(Double.parseDouble(attributes.getValue(ModeleBPStyle.XML_VAR_THICKNESS_NAME)));
+                       mbps.setBent(Double.parseDouble(attributes.getValue(ModeleBPStyle.XML_VAR_BENT_NAME)));
+                       if (mbp!=null)
+                       {  mbp.setStyle(mbps);  }
+                       mbps = null;
+               }
+               addToContext(qName);                                                                                                                                                       
+       }
+
+       
+       
+       public void endElement(String uri, String localName, String qName)
+                       throws SAXException {
+               if (qName.equals(ModeleBaseNucleotide.XML_VAR_CONTENT_NAME)){
+                       if (_buffer==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       if (mbn==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       String val = _buffer.toString();
+                       mbn.setContent(val);
+               }
+               else if (qName.equals(ModeleBasesComparison.XML_VAR_FIRST_CONTENT_NAME)){
+                       if (_buffer==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       if (mbc==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       String val = _buffer.toString();
+                       mbc.setBase1(val.trim().charAt(0));
+               }
+               else if (qName.equals(ModeleBasesComparison.XML_VAR_SECOND_CONTENT_NAME)){
+                       if (_buffer==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       if (mbc==null){
+                               throw new SAXException("Invalid location for tag "+ModeleBaseNucleotide.XML_VAR_CONTENT_NAME);
+                       }
+                       String val = _buffer.toString();
+                       mbc.setBase2(val.trim().charAt(0));
+               }
+               else if (qName.equals(ModeleBaseNucleotide.XML_ELEMENT_NAME)){
+                       mbn = null;
+               }
+               else if (qName.equals(ModeleBP.XML_ELEMENT_NAME))
+               {
+                       mbp = null;
+               }
+               else if (qName.equals(HighlightRegionAnnotation.XML_ELEMENT_NAME))
+               {
+                       hra = null;
+               }
+               else if (qName.equals(TextAnnotation.XML_VAR_TEXT_NAME))
+               {
+                       String text = _buffer.toString();
+                       ta.setText(text);
+                       _buffer = null;                 
+               }
+               else if (qName.equals(RNA.XML_VAR_NAME_NAME))
+               {
+                       if (rna!=null){
+                               rna.setName(_buffer.toString());
+                               _buffer = null;
+                       }                       
+               }
+               else if (qName.equals(VARNAConfig.XML_VAR_CM_CAPTION)){
+                       config._colorMapCaption = _buffer.toString();
+                       _buffer = null;                 
+               }
+
+               else if (qName.equals(TextAnnotation.XML_ELEMENT_NAME))
+               {
+                       rna.addAnnotation(ta);
+                       ta = null;
+               }
+               else if (qName.equals(XMLUtils.XML_BASELIST_ELEMENT_NAME))
+               {
+                       String result = _buffer.toString();
+                       ArrayList<ModeleBase> al = XMLUtils.toModeleBaseArray(result, rna);
+                       if (contextContains(TextAnnotation.XML_ELEMENT_NAME))
+                       {
+                               switch(ta.getType())
+                               {
+                               case POSITION:
+                                       break;
+                               case BASE:
+                                       ta.setAncrage(al.get(0));
+                                       break;
+                               case HELIX:
+                               case LOOP:
+                                       try {
+                                               ta.setAncrage(al, ta.getType());
+                                       } catch (Exception e) {
+                                               // TODO Auto-generated catch block
+                                               e.printStackTrace();
+                                       }
+                                       break;
+                               default:
+                               }
+                       }
+                       if (contextContains(HighlightRegionAnnotation.XML_ELEMENT_NAME))
+                       {
+                         hra.setBases(al);
+                       }
+                       _buffer = null;
+               }
+
+               removeFromContext(qName);                                                                                                                                                       
+       }
+
+       public void characters(char[] ch, int start, int length)
+                       throws SAXException {
+               String lecture = new String(ch, start, length);
+               if (_buffer != null)
+                       _buffer.append(lecture);
+       }
+
+       public void startDocument() throws SAXException {
+       }
+
+       public void endDocument() throws SAXException {
+       }
+       
+       public RNA getRNA()
+       {
+               return rna;
+       }
+
+       public VARNAConfig getVARNAConfig()
+       {
+               return config;
+       }
+
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/utils/XMLUtils.java b/srcjar/fr/orsay/lri/varna/utils/XMLUtils.java
new file mode 100644 (file)
index 0000000..e3acefc
--- /dev/null
@@ -0,0 +1,148 @@
+package fr.orsay.lri.varna.utils;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.ArrayList;
+import java.util.Formatter;
+
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class XMLUtils {
+       public static String toHTMLNotation(Color c)
+       {
+               Formatter f = new Formatter();
+               f.format("#%02X%02X%02X", c.getRed(),c.getGreen(),c.getBlue());
+               return f.toString();
+       }
+
+       public static void toXML(TransformerHandler hd, Font f) throws SAXException
+       {
+               toXML(hd, f,"");
+       }
+       
+
+       public static String XML_BASELIST_ELEMENT_NAME = "baselist";
+       public static String XML_FONT_ELEMENT_NAME = "font";
+       public static String XML_ROLE_NAME = "role";
+       public static String XML_NAME_NAME = "name";
+       public static String XML_FAMILY_NAME = "family";
+       public static String XML_STYLE_NAME = "style";
+       public static String XML_SIZE_NAME = "size";
+       
+       public static void toXML(TransformerHandler hd, Font f, String role) throws SAXException
+       {
+               AttributesImpl atts = new AttributesImpl();
+               if (!role.equals(""))
+                 atts.addAttribute("","",XML_ROLE_NAME,"CDATA",""+role);
+               atts.addAttribute("","",XML_NAME_NAME,"CDATA",""+f.getName());
+               //atts.addAttribute("","",XML_FAMILY_NAME,"CDATA",""+f.getFamily());
+               atts.addAttribute("","",XML_STYLE_NAME,"CDATA",""+f.getStyle());
+               atts.addAttribute("","",XML_SIZE_NAME,"CDATA",""+f.getSize2D());
+               hd.startElement("","",XML_FONT_ELEMENT_NAME,atts);
+               hd.endElement("","",XML_FONT_ELEMENT_NAME);
+       }
+
+       public static Font getFont(String qName, Attributes attributes)
+       {
+               if (qName.equals(XMLUtils.XML_FONT_ELEMENT_NAME)){
+                       int style = Integer.parseInt(attributes.getValue(XMLUtils.XML_STYLE_NAME));                     
+                       String name = (attributes.getValue(XMLUtils.XML_NAME_NAME));                    
+                       double size = Double.parseDouble(attributes.getValue(XMLUtils.XML_SIZE_NAME));
+                       Font f = new Font(name, style, (int)size);
+                       return f.deriveFont((float)size);
+               }
+               return null;
+       }
+       
+
+       public static void toXML(TransformerHandler hd, ModeleBase mb) throws SAXException
+       {
+               ArrayList<ModeleBase> m = new ArrayList<ModeleBase>();
+               m.add(mb);
+               toXML(hd, m);
+       }
+
+
+       public static void toXML(TransformerHandler hd, ArrayList<ModeleBase> m) throws SAXException
+       {               
+               AttributesImpl atts = new AttributesImpl();
+               String result = "";
+               for (ModeleBase mb: m)
+               {
+                       if (!result.equals(""))
+                               result+= ",";
+                       result += mb.getIndex();
+                                       
+               }
+               hd.startElement("","",XML_BASELIST_ELEMENT_NAME,atts);
+               exportCDATAString(hd, result);
+               hd.endElement("","",XML_BASELIST_ELEMENT_NAME);
+       }
+
+       public static ArrayList<ModeleBase> toModeleBaseArray(String baselist, RNA rna)
+       {
+               ArrayList<ModeleBase> result = new ArrayList<ModeleBase>();
+               String[] data = baselist.trim().split(",");
+               for(int i=0;i<data.length;i++)
+               {
+                       int index = Integer.parseInt(data[i]);
+                       result.add(rna.getBaseAt(index));
+               }
+                       
+               return result;
+       }
+       
+       public static void exportCDATAElem(TransformerHandler hd,String elem, String s) throws SAXException
+       {
+               char[] t = s.toCharArray();
+               AttributesImpl atts = new AttributesImpl();
+               hd.startElement("","",elem,atts);
+               hd.startCDATA();
+               hd.characters(t, 0, t.length);
+               hd.endCDATA();
+               hd.endElement("","",elem);
+       }
+       
+       public static void exportCDATAString(TransformerHandler hd, String s) throws SAXException
+       {
+               char[] t = s.toCharArray();
+               hd.startCDATA();
+               hd.characters(t, 0, t.length);
+               hd.endCDATA();
+       }
+       public static boolean getBoolean(Attributes attributes, String attName, boolean defVal)
+       {
+               String val = attributes.getValue(attName);
+               if (val!=null)
+               {
+                       return Boolean.parseBoolean(val);
+               }
+               return defVal;
+       }
+       public static int getInt(Attributes attributes, String attName, int defVal)
+       {
+               String val = attributes.getValue(attName);
+               if (val!=null)
+               {
+                       return Integer.parseInt(val);
+               }
+               return defVal;
+       }
+       public static double getDouble(Attributes attributes, String attName, double defVal)
+       {
+               String val = attributes.getValue(attName);
+               if (val!=null)
+               {
+                       return Double.parseDouble(val);
+               }
+               return defVal;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/Imprimer.java b/srcjar/fr/orsay/lri/varna/views/Imprimer.java
new file mode 100644 (file)
index 0000000..1d459e2
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */ 
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterJob;
+
+public class Imprimer implements Printable {
+       private String phrase;
+
+       public Imprimer(String phrase) {
+               this.phrase = phrase;
+       }
+
+       public int print(Graphics g, PageFormat pf, int indexPage) {
+               if (indexPage > 0)
+                       return NO_SUCH_PAGE;
+               Graphics2D g2 = (Graphics2D) g;
+               g2.setPaint(Color.blue);
+               g2.setFont(new Font("Serif", Font.PLAIN, 64));
+               g2.drawString(phrase, 96, 144);
+               return PAGE_EXISTS;
+       }
+
+       public static void main(String args[]) {
+               PrinterJob tache = PrinterJob.getPrinterJob();
+               tache.setPrintable(new Imprimer(
+                               "Ceci est un teste d'impression en java!"));
+
+               // printDialog affiche la fenetre de sélection de l’imprimante,
+               // etc...
+
+               // il retourne true si on clique sur "Ok", false si on clique sur
+               // "Annuler"
+               //System.out.println(PrinterJob.getPrinterJob());
+               if (!tache.printDialog())
+                       return;
+               try {
+                       tache.print();
+               } catch (Exception e) {
+                       System.err.println("impossible d'imprimer");
+               }
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/views/PrintTest.java b/srcjar/fr/orsay/lri/varna/views/PrintTest.java
new file mode 100644 (file)
index 0000000..99bda20
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import javax.print.attribute.HashPrintRequestAttributeSet;
+import javax.print.attribute.PrintRequestAttributeSet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+/**
+ * This program demonstrates how to print 2D graphics
+ */
+public class PrintTest {
+       @SuppressWarnings("deprecation")
+       public static void main(String[] args) {
+               JFrame frame = new PrintTestFrame();
+               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               frame.show();
+       }
+}
+
+/**
+ * This frame shows a panel with 2D graphics and buttons to print the graphics
+ * and to set up the page format.
+ */
+
+@SuppressWarnings("serial")
+class PrintTestFrame extends JFrame {
+       public PrintTestFrame() {
+               setTitle("PrintTest");
+               setSize(WIDTH, HEIGHT);
+
+               Container contentPane = getContentPane();
+               canvas = new PrintPanel();
+               contentPane.add(canvas, BorderLayout.CENTER);
+
+               attributes = new HashPrintRequestAttributeSet();
+
+               JPanel buttonPanel = new JPanel();
+               JButton printButton = new JButton("Print");
+               buttonPanel.add(printButton);
+               printButton.addActionListener(new ActionListener() {
+
+                       public void actionPerformed(ActionEvent event) {
+                               try {
+                                       PrinterJob job = PrinterJob.getPrinterJob();
+                                       job.setPrintable(canvas);
+                                       if (job.printDialog(attributes)) {
+                                               job.print(attributes);
+                                       }
+                               } catch (PrinterException exception) {
+                                       JOptionPane.showMessageDialog(PrintTestFrame.this,
+                                                       exception);
+                               }
+                       }
+               });
+
+               JButton pageSetupButton = new JButton("Page setup");
+               buttonPanel.add(pageSetupButton);
+               pageSetupButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent event) {
+                               PrinterJob job = PrinterJob.getPrinterJob();
+                               job.pageDialog(attributes);
+                       }
+               });
+
+               contentPane.add(buttonPanel, BorderLayout.NORTH);
+       }
+
+       private PrintPanel canvas;
+
+       private PrintRequestAttributeSet attributes;
+
+       private static final int WIDTH = 300;
+
+       private static final int HEIGHT = 300;
+}
+
+/**
+ * This panel generates a 2D graphics image for screen display and printing.
+ */
+
+@SuppressWarnings("serial")
+class PrintPanel extends JPanel implements Printable {
+
+       public void paintComponent(Graphics g) {
+               super.paintComponent(g);
+               Graphics2D g2 = (Graphics2D) g;
+               drawPage(g2);
+       }
+
+       public int print(Graphics g, PageFormat pf, int page)
+                       throws PrinterException {
+               if (page >= 1)
+                       return Printable.NO_SUCH_PAGE;
+               Graphics2D g2 = (Graphics2D) g;
+               g2.translate(pf.getImageableX(), pf.getImageableY());
+               g2.draw(new Rectangle2D.Double(0, 0, pf.getImageableWidth(), pf
+                               .getImageableHeight()));
+
+               drawPage(g2);
+               return Printable.PAGE_EXISTS;
+       }
+
+       /**
+        * This method draws the page both on the screen and the printer graphics
+        * context.
+        * 
+        * @param g2
+        *            the graphics context
+        */
+       public void drawPage(Graphics2D g2) {
+               FontRenderContext context = g2.getFontRenderContext();
+               Font f = new Font("Serif", Font.PLAIN, 72);
+
+               boolean drawOutline = true;
+               /**
+                * textLayout is not implemented 
+                * 
+                * @j2sNative 
+                * 
+                *            drawOutline = false;
+                */
+               {}
+               if (drawOutline) {
+                       // BH: SwingJS HTML5 would have to use a different method for this
+
+                       GeneralPath clipShape = new GeneralPath();
+
+                       TextLayout layout = new TextLayout("Hello", f, context);
+                       AffineTransform transform = AffineTransform.getTranslateInstance(0, 72);
+                       Shape outline = layout.getOutline(transform);
+                       clipShape.append(outline, false);
+
+                       layout = new TextLayout("World", f, context);
+                       transform = AffineTransform.getTranslateInstance(0, 144);
+                       outline = layout.getOutline(transform);
+                       clipShape.append(outline, false);
+
+                       g2.draw(clipShape);
+                       g2.clip(clipShape);
+               } else {
+                       g2.setFont(f);
+                       g2.drawString("Hello", 0, 72);
+                       g2.drawString("World", 0, 144);
+               }
+
+               final int NLINES = 50;
+               Point2D p = new Point2D.Double(0, 0);
+               for (int i = 0; i < NLINES; i++) {
+                       double x = (2 * getWidth() * i) / NLINES;
+                       double y = (2 * getHeight() * (NLINES - 1 - i)) / NLINES;
+                       Point2D q = new Point2D.Double(x, y);
+                       g2.draw(new Line2D.Double(p, q));
+               }
+       }
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/views/VueAboutPanel.java b/srcjar/fr/orsay/lri/varna/views/VueAboutPanel.java
new file mode 100644 (file)
index 0000000..fffbc80
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.Timer;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.models.VARNAConfig;
+
+/**
+ * BH j2s SwingJS replaces thread with simple javax.swing.Timer
+ * 
+ */
+public class VueAboutPanel extends JPanel {
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 4525998278180950602L;
+
+       private AboutAnimator _anim;
+       private JPanel _textPanel;
+       private JTextArea _textArea;
+
+       public VueAboutPanel() {
+               init();
+       }
+
+       private void init() {
+               try {
+                       setBorder(BorderFactory.createEtchedBorder());
+                       setLayout(new BorderLayout());
+                       setBackground(Color.WHITE);
+
+                       String message = "VARNA "
+                                       + VARNAConfig.MAJOR_VERSION
+                                       + "."
+                                       + VARNAConfig.MINOR_VERSION
+                                       + "\n"
+                                       + "\n"
+                                       + "Created by: Kevin Darty, Alain Denise and Yann Ponty\n"
+                                       + "Contact: ponty@lri.fr\n"
+                                       + "\n"
+                                       + "VARNA is freely distributed under the terms of the GNU GPL 3.0 license.\n"
+                                       + "\n"
+                                       + "Supported by the BRASERO project (ANR-06-BLAN-0045)\n";
+
+                       _textArea = new JTextArea();
+                       _textArea.setText(message);
+                       _textArea.setEditable(false);
+
+                       _textPanel = new JPanel();
+                       _textPanel.setBackground(Color.WHITE);
+                       _textPanel.setLayout(new BorderLayout());
+                       _textPanel.setBorder(BorderFactory.createMatteBorder(0, 15, 0, 15,
+                                       getBackground()));
+                       _textPanel.add(_textArea);
+
+                       VARNAPanel vp = new VARNAPanel("GGGGAAAACCCC", "((((....))))");
+                       vp.setModifiable(false);
+                       vp.setPreferredSize(new Dimension(100, 100));
+                       // vp.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+                       _anim = new AboutAnimator(vp);
+                       _anim
+                                       .addRNA("GGGGAAGGGGAAAACCCCAACCCC",
+                                                       "((((..((((....))))..))))");
+                       _anim.addRNA("GGGGAAGGGGAAGGGGAAAACCCCAACCCCAACCCC",
+                                       "((((..((((..((((....))))..))))..))))");
+                       _anim
+                                       .addRNA(
+                                                       "GGGGAGGGGAAAACCCCAGGGGAGGGGAAAACCCCAGGGGAAAACCCCAGGGGAAAACCCCACCCCAGGGGAAAACCCCACCCC",
+                                                       "((((.((((....)))).((((.((((....)))).((((....)))).((((....)))).)))).((((....)))).))))");
+                       _anim
+                                       .addRNA(
+                                                       "GGGGGGGGAAAACCCCAGGGGAAAACCCCAGGGGGGGGAAAACCCCAGGGGAAAACCCCAGGGGAAAACCCCAGGGGAAAACCCCGGGGAAAACCCCACCCCAGGGGAAAACCCCAGGGGAAAACCCCCCCC",
+                                                       "((((((((....)))).((((....)))).((((((((....)))).((((....)))).((((....)))).((((....))))((((....)))).)))).((((....)))).((((....))))))))");
+                       _anim.addRNA("GGGGAAAACCCC", "((((....))))");
+                       _anim.addRNA("GGGGAAGGGGAAAACCCCAGGGGAAAACCCCACCCC",
+                                       "((((..((((....)))).((((....)))).))))");
+                       _anim.addRNA("GGGGAGGGGAAAACCCCAGGGGAAAACCCCAGGGGAAAACCCCACCCC",
+                                       "((((.((((....)))).((((....)))).((((....)))).))))");
+                       _anim
+                                       .addRNA(
+                                                       "GGGGAGGGGAAAAAAACCCCAGGGGAAAAAAACCCCAGGGGAAAAAAACCCCACCCC",
+                                                       "((((.((((.......)))).((((.......)))).((((.......)))).))))");
+                       _anim.start();
+
+                       add(vp, BorderLayout.WEST);
+                       add(_textPanel, BorderLayout.CENTER);
+               } catch (ExceptionNonEqualLength e) {
+               }
+       }
+
+       public void gracefulStop() {
+               _anim.gracefulStop();
+       }
+
+       private class AboutAnimator implements ActionListener {
+               VARNAPanel _vp;
+               ArrayList<String> _structures = new ArrayList<String>();
+               ArrayList<String> _sequences = new ArrayList<String>();
+               int _period = 2000;
+               boolean _over = false;
+
+               public AboutAnimator(VARNAPanel vp) {
+                       super();
+                       _vp = vp;
+               }
+
+               /**
+                * mode pointer for timer cycle -- DELAY1, TASK, DELAY2, STOP
+                */
+               int mode = 0;
+               
+               /**
+                * modes for run()
+                * 
+                */
+               final int DELAY1 = 0, TASK = 1, DELAY2 = 2, STOP = 3;
+               int i = 0;
+
+               @Override
+               public void actionPerformed(ActionEvent e) {
+                       run();
+               }
+
+               public void start() {
+                       int mode = DELAY1;
+                       run();
+               }
+
+               public void addRNA(String seq, String str) {
+                       _sequences.add(seq);
+                       _structures.add(str);
+               }
+
+               public void gracefulStop() {
+                       _over = true;
+               }
+
+               public void run() {
+                       int initialDelay;
+                       if (_over)
+                               mode = STOP;
+                       switch (mode) {
+                       case DELAY1:
+                               mode = TASK;
+                               initialDelay = _period;
+                               break;
+                       case TASK:
+                               String seq = _sequences.get(i);
+                               String str = _structures.get(i);
+                               try {
+                                       _vp.drawRNAInterpolated(seq, str);
+                                       mode = DELAY2;
+                                       initialDelay = 500;
+                               } catch (ExceptionNonEqualLength e) {
+                                       initialDelay = -1;
+                               }
+                               break;
+                       case DELAY2:
+                               i = (i + 1) % _sequences.size();
+                               mode = DELAY1;
+                               initialDelay = 0;
+                               break;
+                       case STOP:
+                       default:
+                               initialDelay = -1;
+                               break;
+                       }
+                       if (initialDelay >= 0) {
+                               Timer t = new Timer(initialDelay, this);
+                               t.setDelay(0);
+                               t.setRepeats(false);
+                               t.start();
+                       } else {
+                               System.out.println("VueAbout done");
+                       }
+
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueAnnotation.java b/srcjar/fr/orsay/lri/varna/views/VueAnnotation.java
new file mode 100644 (file)
index 0000000..95a8cce
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.geom.Point2D.Double;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JTextArea;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicBorders;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+import fr.orsay.lri.varna.controlers.ControleurVueAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+
+/**
+ * 
+ * BH SwingJS using asynchronous JOptionPane.showConfirmDialog
+ * 
+ * annoted text view for edition
+ * 
+ * @author Darty@lri.fr
+ * 
+ */
+public class VueAnnotation {
+
+       protected VARNAPanel _vp;
+       private JSlider ySlider, xSlider;
+       private JButton colorButton;
+       private JTextArea textArea;
+       private JPanel panel;
+       protected TextAnnotation textAnnotation, textAnnotationSave;
+       private VueFont vueFont;
+       private ControleurVueAnnotation _controleurVueAnnotation;
+       protected boolean newAnnotation, limited;
+       private Double position;
+       private JSlider rotationSlider;
+
+       /**
+        * creates a view for a new annoted text
+        * 
+        * @param vp
+        * @param limited
+        *            if true, lets custom position and angle.
+        */
+       public VueAnnotation(VARNAPanel vp, boolean limited) {
+               this(
+                               vp,
+                               (int) (vp.getExtendedRNABBox().x + vp.getExtendedRNABBox().width / 2.0),
+                               (int) (vp.getExtendedRNABBox().y + vp.getExtendedRNABBox().height / 2.0),
+                               limited);
+       }
+
+       /**
+        * creates a view for a new annoted text, without limited option
+        * 
+        * @param vp
+        */
+       public VueAnnotation(VARNAPanel vp) {
+               this(vp, false);
+       }
+
+       /**
+        * creates a view for a new annoted text at a given position, without
+        * limited option
+        * 
+        * @param vp
+        */
+       public VueAnnotation(VARNAPanel vp, int x, int y) {
+               this(vp, x, y, false);
+       }
+
+       /**
+        * creates a view for a new annoted text at a given position, without
+        * limited option
+        * 
+        * @param vp
+        */
+       public VueAnnotation(VARNAPanel vp, int x, int y, boolean limited) {
+               this(vp, new TextAnnotation("", x, y), false, true);
+       }
+
+       /**
+        * creates a view for an annoted text, without limited option
+        * 
+        * @param vp
+        * @param textAnnot
+        */
+       public VueAnnotation(VARNAPanel vp, TextAnnotation textAnnot,
+                       boolean newAnnotation) {
+               this(vp, textAnnot, (textAnnot.getType()!=TextAnnotation.AnchorType.POSITION), newAnnotation);
+       }
+
+       /**
+        * creates a view for an annoted text
+        * 
+        * 
+        * @param vp
+        * @param textAnnot
+        * @param reduite
+        *            if true, lets custom position and angle.
+        * @param newAnnotation
+        *            if true, deleted if cancelled.
+        */
+       public VueAnnotation(VARNAPanel vp, TextAnnotation textAnnot,
+                       boolean reduite, boolean newAnnotation) {
+               this.limited = reduite;
+               this.newAnnotation = newAnnotation;
+               _vp = vp;
+               textAnnotation = textAnnot;
+               textAnnotationSave = textAnnotation.clone();
+
+               if (!_vp.getListeAnnotations().contains(textAnnot)) {
+                       _vp.addAnnotation(textAnnotation);
+               }
+
+               _controleurVueAnnotation = new ControleurVueAnnotation(this);
+
+               position = textAnnotation.getCenterPosition();
+
+               /*
+                * if (textAnnotation.getType() != TextAnnotation.POSITION) { position =
+                * _vp.transformCoord(position); }
+                */
+
+               JPanel py = new JPanel();
+               JPanel px = new JPanel();
+               panel = new JPanel();
+               panel.setLayout(new GridLayout(0, 1));
+               py.setLayout(new FlowLayout(FlowLayout.LEFT));
+               px.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               ySlider = new JSlider(JSlider.HORIZONTAL, 0, (int) (_vp
+                               .getExtendedRNABBox().height), Math.max(0, Math.min((int) (_vp
+                               .getExtendedRNABBox().height), (int) (position.y - _vp
+                               .getExtendedRNABBox().y))));
+               // Turn on labels at major tick marks.
+               ySlider.setMajorTickSpacing(500);
+               ySlider.setMinorTickSpacing(100);
+               ySlider.setPaintTicks(true);
+               ySlider.setPaintLabels(true);
+               ySlider.setPreferredSize(new Dimension(400,
+                               ySlider.getPreferredSize().height));
+
+               JLabel yValueLabel = new JLabel(String.valueOf((int) position.y
+                               - _vp.getExtendedRNABBox().y));
+               yValueLabel.setPreferredSize(new Dimension(50, yValueLabel
+                               .getPreferredSize().height));
+               ySlider
+                               .addChangeListener(new ControleurSliderLabel(yValueLabel, false));
+               ySlider.addChangeListener(_controleurVueAnnotation);
+
+               xSlider = new JSlider(JSlider.HORIZONTAL, 0, (int) (_vp
+                               .getExtendedRNABBox().width), Math.max(0, Math.min((int) _vp
+                               .getExtendedRNABBox().width, (int) (position.x - _vp
+                               .getExtendedRNABBox().x))));
+               // Turn on labels at major tick marks.
+               xSlider.setMajorTickSpacing(500);
+               xSlider.setMinorTickSpacing(100);
+               xSlider.setPaintTicks(true);
+               xSlider.setPaintLabels(true);
+               xSlider.setPreferredSize(new Dimension(400,
+                               xSlider.getPreferredSize().height));
+
+               JLabel xValueLabel = new JLabel(String.valueOf((int) position.x
+                               - _vp.getExtendedRNABBox().x));
+               xValueLabel.setPreferredSize(new Dimension(50, xValueLabel
+                               .getPreferredSize().height));
+               xSlider
+                               .addChangeListener(new ControleurSliderLabel(xValueLabel, false));
+               xSlider.addChangeListener(_controleurVueAnnotation);
+
+               JLabel labelY = new JLabel("Y:");
+               JLabel labelX = new JLabel("X:");
+
+               py.add(labelY);
+               py.add(ySlider);
+               py.add(yValueLabel);
+               px.add(labelX);
+               px.add(xSlider);
+               px.add(xValueLabel);
+
+               /*if (!limited) {
+                       panel.add(px);
+                       panel.add(py);
+               }*/
+
+               JPanel panelTexte = new JPanel();
+               panelTexte.setLayout(new BorderLayout());
+               textArea = new JTextArea(textAnnotation.getTexte());
+               textArea.addCaretListener(_controleurVueAnnotation);
+               textArea.setPreferredSize(panelTexte.getSize());
+               Border border = new BasicBorders.FieldBorder(Color.black, Color.black,
+                               Color.black, Color.black);
+               textArea.setBorder(border);
+               JLabel labelTexte = new JLabel("Text:");
+               panelTexte.add(textArea, BorderLayout.CENTER);
+               panelTexte.add(labelTexte, BorderLayout.NORTH);
+               panel.add(panelTexte);
+
+               vueFont = new VueFont(textAnnot.getFont());
+               vueFont.getBoxPolice().addActionListener(_controleurVueAnnotation);
+               vueFont.getSizeSlider().addChangeListener(_controleurVueAnnotation);
+               vueFont.getStylesBox().addActionListener(_controleurVueAnnotation);
+
+               colorButton = new JButton("Set color");
+               colorButton.setActionCommand("setcolor");
+               colorButton.setForeground(textAnnot.getColor());
+               colorButton.addActionListener(_controleurVueAnnotation);
+
+               JPanel fontAndColor = new JPanel();
+               fontAndColor.add(vueFont.getPanel());
+               fontAndColor.add(colorButton);
+
+               panel.add(fontAndColor);
+
+               JPanel rotationPanel = new JPanel();
+
+               rotationSlider = new JSlider(JSlider.HORIZONTAL, -360, 360,
+                               (int) textAnnotation.getAngleInDegres());
+               rotationSlider.setMajorTickSpacing(60);
+               rotationSlider.setPaintTicks(true);
+               rotationSlider.setPaintLabels(true);
+               rotationSlider.setPreferredSize(new Dimension(500, 50));
+
+               JLabel rotationLabel = new JLabel(String.valueOf(0));
+               rotationLabel.setPreferredSize(new Dimension(50, rotationLabel
+                               .getPreferredSize().height));
+               rotationSlider.addChangeListener(new ControleurSliderLabel(
+                               rotationLabel, false));
+               rotationSlider.addChangeListener(_controleurVueAnnotation);
+
+               JLabel labelZ = new JLabel("Rotation (degrees):");
+
+               rotationPanel.add(labelZ);
+               rotationPanel.add(rotationSlider);
+               rotationPanel.add(rotationLabel);
+
+               /*
+                * if (!limited) { panel.add(rotationPanel); }
+                */
+
+               if (limited) {
+                       ySlider.setEnabled(false);
+                       xSlider.setEnabled(false);
+                       rotationSlider.setEnabled(false);
+               }
+               textArea.requestFocusInWindow();
+               
+       }
+
+       private void applyFont() {
+               textAnnotation.setFont(vueFont.getFont());
+       }
+
+       /**
+        * update the annoted text on the VARNAPanel
+        */
+       public void update() {
+               applyFont();
+               if (textAnnotation.getType() == TextAnnotation.AnchorType.POSITION)
+                       textAnnotation.setAncrage((double) xSlider.getValue()
+                                       + _vp.getExtendedRNABBox().x, ySlider.getValue()
+                                       + _vp.getExtendedRNABBox().y);
+               textAnnotation.setText(textArea.getText());
+               textAnnotation.setAngleInDegres(rotationSlider.getValue());
+               _vp.clearSelection();
+               _vp.repaint();
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       /**
+        * 
+        * @return the annoted text
+        */
+       public TextAnnotation getTextAnnotation() {
+               return textAnnotation;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       /**
+        * shows the dialog which add it to the VARNAPanel for previsualization.
+        * <p>
+        * if validate, just update the annoted text
+        * <p>
+        * if cancelled : remove the annoted text if it was a new one, otherwise
+        * cancel modifications
+        * <p>
+        * 
+        */
+       public void show() {
+               _vp.set_selectedAnnotation(textAnnotation);
+               _vp.highlightSelectedAnnotation();
+               
+               // BH SwingjS using asynchronous dialog
+               Runnable ok = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               update();
+                       }
+                       
+               };
+
+               Runnable cancel = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               if (newAnnotation) {
+                                       _vp.set_selectedAnnotation(null);
+                                       if (!_vp.removeAnnotation(textAnnotation))
+                                               _vp.errorDialog(new Exception("Impossible de supprimer"));
+                               } else {
+                                       textAnnotation.copy(textAnnotationSave);
+                               }
+                       }
+                       
+               };
+
+               Runnable final_ = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               _vp.resetAnnotationHighlight();
+                               _vp.set_selectedAnnotation(null);
+                               _vp.repaint();
+                       }
+                       
+               };
+               
+               _vp.getVARNAUI().showConfirmDialog(getPanel(), "Add/edit annotation", ok, cancel, cancel, final_);
+       }
+
+       public boolean isLimited() {
+               return limited;
+       }
+
+       public void setLimited(boolean limited) {
+               this.limited = limited;
+       }
+
+       public boolean isNewAnnotation() {
+               return this.newAnnotation;
+       }
+
+       public void updateColor(Color c) {
+               colorButton.setForeground(c);
+               textAnnotation.setColor(c);
+
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBPHeightIncrement.java b/srcjar/fr/orsay/lri/varna/views/VueBPHeightIncrement.java
new file mode 100644 (file)
index 0000000..342608f
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurBPHeightIncrement;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VueBPHeightIncrement {
+       private VARNAPanel _vp;
+       private JPanel panel;
+       private JSlider spaceSlider;
+
+       public VueBPHeightIncrement(VARNAPanel vp) {
+               _vp = vp;
+               panel = new JPanel();
+
+               spaceSlider = new JSlider(JSlider.HORIZONTAL, 10, 200, Integer
+                               .valueOf(String.valueOf(Math
+                                               .round(vp.getRNA().getBPHeightIncrement() * 100))));
+               // Turn on labels at major tick marks.
+               spaceSlider.setMajorTickSpacing(30);
+               spaceSlider.setPaintTicks(true);
+               spaceSlider.setPaintLabels(true);
+
+               JLabel spaceLabel = new JLabel(String
+                               .valueOf(vp.getRNA().getBPHeightIncrement() * 100.0));
+               spaceLabel.setPreferredSize(new Dimension(50, spaceLabel
+                               .getPreferredSize().height));
+               spaceSlider.addChangeListener(new ControleurSliderLabel(spaceLabel,
+                               false));
+               spaceSlider.addChangeListener(new ControleurBPHeightIncrement(this));
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelS = new JLabel("HeighInc:");
+
+               panel.add(labelS);
+               panel.add(spaceSlider);
+               panel.add(spaceLabel);
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public Double getIncrement() {
+               return spaceSlider.getValue() / 100.0;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBPList.java b/srcjar/fr/orsay/lri/varna/views/VueBPList.java
new file mode 100644 (file)
index 0000000..7c18d9c
--- /dev/null
@@ -0,0 +1,221 @@
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Vector;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.ActionEditor;
+import fr.orsay.lri.varna.components.ActionRenderer;
+import fr.orsay.lri.varna.components.ColorRenderer;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VueBPList extends JPanel implements TableModelListener, ActionListener
+{
+
+       private JTable table;
+       private BPTableModel _tm;
+       private VARNAPanel _vp;
+       private ArrayList<ModeleBP> data;
+       private ArrayList<Double> _backup;
+       private ArrayList<Object> columns;
+       
+       
+       public enum Actions{
+               ACTION_DELETE,
+               ACTION_EDIT_STYLE;
+               
+               public String toString()
+               {
+                       switch(this)
+                       {
+                         case ACTION_DELETE:
+                                 return "Delete";
+                         case ACTION_EDIT_STYLE:
+                                 return "Edit Style";
+                       }
+                       return "N/A";
+               }
+       };
+       
+       public VueBPList(VARNAPanel vp)
+       {
+               super(new GridLayout(1, 0));
+               _vp = vp;
+               init();
+       }
+       
+       private void init()
+       {
+               Object[] col = {"Sec.Str.","5' partner","3' partner","5' edge","3' edge","Orientation","Remove"};
+               columns = new ArrayList<Object>();
+               for (int i = 0; i < col.length; i++)
+               {
+                       columns.add(col[i]);
+               }
+               
+               _backup = new ArrayList<Double>();
+               data = new ArrayList<ModeleBP>();
+               for (ModeleBP ms: _vp.getRNA().getAllBPs()) 
+               {
+                       data.add(ms);
+               }
+               Collections.sort(data);
+               _tm = new BPTableModel();
+               table = new JTable(_tm);
+               
+               table.setDefaultRenderer(Color.class, new ColorRenderer(true)); 
+               table.setDefaultRenderer(Actions.class, new ActionRenderer());
+               
+               table.setDefaultEditor(ModeleBP.Edge.class, new DefaultCellEditor(new JComboBox(ModeleBP.Edge.values())));
+               table.setDefaultEditor(ModeleBP.Stericity.class, new DefaultCellEditor(new JComboBox(ModeleBP.Stericity.values())));
+               table.setDefaultEditor(Actions.class, new ActionEditor(this));
+
+               table.setPreferredScrollableViewportSize(new Dimension(500, 500));
+               table.getModel().addTableModelListener(this);
+               
+               table.setRowHeight(25);
+               
+               JScrollPane scrollPane = new JScrollPane(table);
+               add(scrollPane);
+               setOpaque(true); // content panes must be opaque
+               this.doLayout();
+
+               JOptionPane.showMessageDialog(_vp, this, "Base pairs Edition", JOptionPane.PLAIN_MESSAGE);
+
+       }
+       
+       public void cancelChanges()
+       {
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) 
+               {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       mb.setValue(_backup.get(i));
+               }
+         _vp.getRNA().rescaleColorMap(_vp.getColorMap());
+       }
+
+       private class BPTableModel extends AbstractTableModel {
+           /**
+                * 
+                */
+               private static final long serialVersionUID = 1L;
+               public String getColumnName(int col) {
+               return columns.get(col).toString();
+           }
+           public int getRowCount() { return data.size(); }
+           public int getColumnCount() { return columns.size(); }
+           public Object getValueAt(int row, int col) {
+               ModeleBP mb = data.get(row);
+               if (col==0)
+               {
+                       return new Boolean(mb.getPartner3().getElementStructure()==mb.getPartner5().getIndex());
+               }
+               else if (col==1)
+               {
+                       return new String(""+mb.getPartner5().getBaseNumber()+"-"+mb.getPartner5().getContent());
+               } 
+               else if (col==2)
+               {
+                       return new String(""+mb.getPartner3().getBaseNumber()+"-"+mb.getPartner3().getContent());
+               } 
+               else if (col==3)
+               {
+                       return mb.getEdgePartner5();
+               }
+               else if (col==4)
+               {
+                       return mb.getEdgePartner3();
+               }
+               else if (col==5)
+               {
+                       return mb.getStericity();
+               }
+               else if (col==6)
+               {
+                       return Actions.ACTION_DELETE;
+               }
+               return "N/A";
+           }
+           public boolean isCellEditable(int row, int col)
+               { 
+                       if ( col == 3 || col ==4 || col ==5 || col ==6) 
+                               return true;
+                       return false;
+               }
+           public void setValueAt(Object value, int row, int col) {
+               if ( col == 3 || col ==4 || col ==5) 
+               {
+                       ModeleBP mb = data.get(row);
+                       if ( col == 3)
+                       {
+                         mb.setEdge5((ModeleBP.Edge)value);    
+                       }
+                       else if ( col == 4)
+                       {
+                                 mb.setEdge3((ModeleBP.Edge)value);    
+                       }
+                       else if ( col == 5)
+                       {
+                                 mb.setStericity((ModeleBP.Stericity)value);                                   
+                       }
+                 fireTableCellUpdated(row, col);
+                 _vp.repaint();
+                 
+               }
+           }
+           
+           public Class getColumnClass(int c) {
+               return getValueAt(0, c).getClass();
+           }
+       }
+
+       public void tableChanged(TableModelEvent e) {
+               if (e.getType() == TableModelEvent.UPDATE)
+               {
+                       table.repaint();
+               }
+               
+       }
+
+       public void actionPerformed(ActionEvent arg0) {
+               //System.out.println(""+arg0.toString());
+               String[] data2 = arg0.getActionCommand().split("-");
+               int row = Integer.parseInt(data2[data2.length-1]);
+               if (data2[0].equals("Delete"))
+               {
+                       ModeleBP ms = data.get(row);
+                       _vp.getVARNAUI().UIRemoveBP(ms);
+                       
+                       
+                       data.remove(row);
+                       
+                       _tm.fireTableRowsDeleted(row, row);
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBPThickness.java b/srcjar/fr/orsay/lri/varna/views/VueBPThickness.java
new file mode 100644 (file)
index 0000000..af35a03
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.util.ArrayList;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+public class VueBPThickness implements ChangeListener {
+
+       private VARNAPanel _vp;
+       ArrayList<ModeleBP> _msbp;
+       private JSlider _thicknessSlider;
+       private JPanel panel;
+
+       private ArrayList<Double> _backupThicknesses = new ArrayList<Double>();
+
+       private double FACTOR = 10.0;
+
+       public VueBPThickness(VARNAPanel vp, ArrayList<ModeleBP> msbp) {
+               _vp = vp;
+               _msbp = msbp;
+               backupThicknesses();
+
+               _thicknessSlider = new JSlider(JSlider.HORIZONTAL, 1, 100,
+                               (int) (msbp.get(0).getStyle().getThickness(
+                                               VARNAConfig.DEFAULT_BP_THICKNESS) * FACTOR));
+               _thicknessSlider.setMajorTickSpacing(10);
+               _thicknessSlider.setPaintTicks(true);
+               _thicknessSlider.setPaintLabels(false);
+               _thicknessSlider.setPreferredSize(new Dimension(500, 50));
+
+               JLabel thicknessLabel = new JLabel(String.valueOf(msbp.get(0).getStyle()
+                               .getThickness(VARNAConfig.DEFAULT_BP_THICKNESS)));
+               thicknessLabel.setPreferredSize(new Dimension(50, thicknessLabel
+                               .getPreferredSize().height));
+               _thicknessSlider.addChangeListener(new ControleurSliderLabel(
+                               thicknessLabel, 1.0 / FACTOR));
+               _thicknessSlider.addChangeListener(this);
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelZ = new JLabel("Thickness:");
+
+               panel.add(labelZ);
+               panel.add(_thicknessSlider);
+               panel.add(thicknessLabel);
+       }
+
+       private void backupThicknesses() {
+               for (int i = 0; i < _msbp.size(); i++) {
+                       this._backupThicknesses.add(_msbp.get(i).getStyle().getThickness(
+                                       VARNAConfig.DEFAULT_BP_THICKNESS));
+               }
+       }
+
+       public void restoreThicknesses() {
+               for (int i = 0; i < _msbp.size(); i++) {
+                       _msbp.get(i).getStyle().setThickness(_backupThicknesses.get(i));
+               }
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public double getThickness() {
+               return (double) _thicknessSlider.getValue() / FACTOR;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               for (int i = 0; i < _msbp.size(); i++) {
+                       _msbp.get(i).getStyle().setThickness(
+                                       ((double) _thicknessSlider.getValue()) / FACTOR);
+               }
+               _vp.repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBPType.java b/srcjar/fr/orsay/lri/varna/views/VueBPType.java
new file mode 100644 (file)
index 0000000..93d5b33
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+
+
+public class VueBPType implements ActionListener {
+
+       private VARNAPanel _vp;
+       ModeleBP _msbp;
+       private JComboBox _edge5;
+       private JComboBox _edge3;
+       private JComboBox _stericity;
+       private JPanel panel;
+
+       public VueBPType(VARNAPanel vp, ModeleBP msbp) {
+               _vp = vp;
+               _msbp = msbp;
+
+               ModeleBP.Edge[] edges = ModeleBP.Edge.values();
+               ModeleBP.Edge bck = msbp.getEdgePartner5();
+               _edge5 = new JComboBox(edges);
+               for (int i = 0; i < edges.length; i++) {
+                       if (edges[i] == bck)
+                               _edge5.setSelectedIndex(i);
+               }
+
+               bck = msbp.getEdgePartner3();
+               _edge3 = new JComboBox(edges);
+               for (int i = 0; i < edges.length; i++) {
+                       if (edges[i] == bck)
+                               _edge3.setSelectedIndex(i);
+               }
+
+               ModeleBP.Stericity[] sters = ModeleBP.Stericity.values();
+               ModeleBP.Stericity bcks = msbp.getStericity();
+               _stericity = new JComboBox(sters);
+               for (int i = 0; i < sters.length; i++) {
+                       if (sters[i] == bcks)
+                               _stericity.setSelectedIndex(i);
+               }
+
+               _edge5.addActionListener(this);
+               _edge3.addActionListener(this);
+               _stericity.addActionListener(this);
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel label5 = new JLabel("5' edge: ");
+               JLabel label3 = new JLabel("3' edge: ");
+               JLabel labelS = new JLabel("Stericity: ");
+
+               panel.add(label5);
+               panel.add(_edge5);
+               panel.add(label3);
+               panel.add(_edge3);
+               panel.add(labelS);
+               panel.add(_stericity);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public ModeleBP.Edge getEdge5() {
+               return (ModeleBP.Edge) _edge5.getSelectedItem();
+       }
+
+       public ModeleBP.Edge getEdge3() {
+               return (ModeleBP.Edge) _edge3.getSelectedItem();
+       }
+
+       public ModeleBP.Stericity getStericity() {
+               return (ModeleBP.Stericity) _stericity.getSelectedItem();
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               _msbp.setEdge5(getEdge5());
+               _msbp.setEdge3(getEdge3());
+               _msbp.setStericity(getStericity());
+               _vp.repaint();
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBaseValues.java b/srcjar/fr/orsay/lri/varna/views/VueBaseValues.java
new file mode 100644 (file)
index 0000000..980ac65
--- /dev/null
@@ -0,0 +1,134 @@
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.util.ArrayList;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.ColorRenderer;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VueBaseValues extends JPanel implements TableModelListener {
+
+       private JTable table;
+       private ValueTableModel _tm;
+       private VARNAPanel _vp;
+       private ArrayList<ModeleBase> data;
+       private ArrayList<Double> _backup;
+       private ArrayList<Object> columns;
+       
+       
+       public VueBaseValues(VARNAPanel vp)
+       {
+               super(new GridLayout(1, 0));
+               _vp = vp;
+               init();
+       }
+       
+       private void init()
+       {
+               Object[] col = {"Number","Base","Value","Preview"};
+               columns = new ArrayList<Object>();
+               for (int i = 0; i < col.length; i++)
+               {
+                       columns.add(col[i]);
+               }
+               
+               _backup = new ArrayList<Double>();
+               data = new ArrayList<ModeleBase>();
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) 
+               {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       data.add(mb);
+                       _backup.add(mb.getValue());
+               }
+               _tm = new ValueTableModel();
+               table = new JTable(_tm);
+               table.setDefaultRenderer(Color.class, new ColorRenderer(true)); 
+               table.setPreferredScrollableViewportSize(new Dimension(300, 300));
+               table.getModel().addTableModelListener(this);
+               
+               JScrollPane scrollPane = new JScrollPane(table);
+               this.add(scrollPane);
+       }
+       
+       public void cancelChanges()
+       {
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) 
+               {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       mb.setValue(_backup.get(i));
+               }
+         _vp.getRNA().rescaleColorMap(_vp.getColorMap());
+       }
+
+       private class ValueTableModel extends AbstractTableModel {
+           public String getColumnName(int col) {
+               return columns.get(col).toString();
+           }
+           public int getRowCount() { return data.size(); }
+           public int getColumnCount() { return columns.size(); }
+           public Object getValueAt(int row, int col) {
+               ModeleBase mb = data.get(row);
+               if (col==0)
+               {
+                       return new Integer(mb.getBaseNumber());
+               }
+               else if (col==1)
+               {
+                       return new String(mb.getContent());
+               } 
+               else if (col==2)
+               {
+                       return new Double(mb.getValue());
+               } 
+               else if (col==3)
+               {
+                       return _vp.getColorMap().getColorForValue(mb.getValue());
+               }
+               return "N/A";
+           }
+           public boolean isCellEditable(int row, int col)
+               { 
+                       if (getColumnName(col).equals("Value")) 
+                               return true;
+                       return false;
+               }
+           public void setValueAt(Object value, int row, int col) {
+               if (getColumnName(col).equals("Value"))
+               {
+                 data.get(row).setValue(((Double)value));
+                 _vp.getRNA().rescaleColorMap(_vp.getColorMap());
+                 _vp.repaint();
+                 fireTableCellUpdated(row, col);
+               }
+           }
+           public Class getColumnClass(int c) {
+               return getValueAt(0, c).getClass();
+           }
+       }
+
+       public void tableChanged(TableModelEvent e) {
+               if (e.getType() == TableModelEvent.UPDATE)
+               {
+                       table.repaint();
+               }
+               
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBases.java b/srcjar/fr/orsay/lri/varna/views/VueBases.java
new file mode 100644 (file)
index 0000000..6f9d95a
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.BaseSpecialColorEditor;
+import fr.orsay.lri.varna.components.BaseTableModel;
+import fr.orsay.lri.varna.components.ColorRenderer;
+import fr.orsay.lri.varna.models.BaseList;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+
+public class VueBases extends JPanel {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * for bases by kind
+        */
+       public final static int KIND_MODE = 1;
+       /**
+        * for all bases 
+        */
+       public final static int ALL_MODE = 2;
+       /**
+        * for base pairs by king
+        */
+       public final static int COUPLE_MODE = 3;
+
+       private int _mode;
+
+       private VARNAPanel _vp;
+
+       private ArrayList<BaseList> data = new ArrayList<BaseList>();
+       
+       private Hashtable<String,BaseList> revdata = new Hashtable<String,BaseList>();
+       
+       private JTable table;
+
+       private BaseTableModel specialTableModel;
+
+       public VueBases(VARNAPanel vp, int mode) {
+               super(new GridLayout(1, 0));
+               _vp = vp;
+               switch (mode) {
+               case (KIND_MODE):
+                       _mode = KIND_MODE;
+                       kindMode();
+                       break;
+               case (ALL_MODE):
+                       _mode = ALL_MODE;
+                       allMode();
+                       break;
+               case (COUPLE_MODE):
+                       _mode = COUPLE_MODE;
+                       coupleMode();
+                       break;
+               default:
+                       break;
+               }
+       }
+       
+       private BaseList locateOrAddList(String caption)
+       {
+         if (!revdata.containsKey(caption))
+         {
+                 BaseList mbl = new BaseList(caption);
+                 revdata.put(caption,mbl);
+                 data.add(mbl);
+         }
+         return revdata.get(caption);
+       }
+
+       private void coupleMode() {
+               String pairString;
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) {
+                       
+                       int j = _vp.getRNA().get_listeBases().get(i).getElementStructure();
+                       if (j > i) {
+                               String tmp1 = (_vp.getRNA().get_listeBases().get(i).getContent()); 
+                               String tmp2 = (_vp.getRNA().get_listeBases().get(j).getContent()); 
+                               pairString = tmp1 +"-"+ tmp2;
+                               BaseList bl = locateOrAddList(pairString);
+                               bl.addBase(_vp.getRNA().get_listeBases().get(i));
+                               bl.addBase(_vp.getRNA().get_listeBases().get(j));
+                       }
+               }
+               createView();
+       }
+
+       private void allMode() {
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       BaseList bl = locateOrAddList(""+i);
+                       bl.addBase(mb);
+               }
+               createView();
+       }
+
+       private void kindMode() {
+               for (int i = 0; i < _vp.getRNA().get_listeBases().size(); i++) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(i);
+                       String tmp1 = (mb.getContent()); 
+                       BaseList bl = locateOrAddList(tmp1);
+                       bl.addBase(mb);
+               }
+               createView();
+       }
+
+       private void createView() {
+               specialTableModel = new BaseTableModel(data);
+               table = new JTable(specialTableModel);
+               table.setPreferredScrollableViewportSize(new Dimension(500, 300));
+               // TODO: Find equivalent in JRE 1.5
+               //table.setFillsViewportHeight(true);
+               // Create the scroll pane and add the table to it.
+               JScrollPane scrollPane = new JScrollPane(table);
+
+               // Set up renderer and editor for the Favorite Color column.
+               table.setDefaultRenderer(Color.class, new ColorRenderer(true));
+               table.setDefaultEditor(Color.class, new BaseSpecialColorEditor(this));
+               specialTableModel.addTableModelListener(new TableModelListener(){
+
+                       public void tableChanged(TableModelEvent e) {
+                               _vp.repaint();
+                       }
+                       
+               });
+
+               // Add the scroll pane to this panel.
+               add(scrollPane);
+
+               UIvueBases();
+       }
+
+       /**
+        * Create the GUI and show it. For thread safety, this method should be
+        * invoked from the event-dispatching thread.
+        */
+       public void UIvueBases() {
+               // Create and set up the content pane.
+               JComponent newContentPane = this;
+               newContentPane.setOpaque(true); // content panes must be opaque
+
+               JOptionPane.showMessageDialog(_vp, newContentPane,
+                               "Base Colors Edition", JOptionPane.PLAIN_MESSAGE);
+
+       }
+
+       public int getMode() {
+               return _mode;
+       }
+
+       public BaseList getDataAt(int i) {
+               return data.get(i);
+       }
+
+       
+       public ArrayList<BaseList> getData() {
+               return data;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public JTable getTable() {
+               return table;
+       }
+
+       public void setTable(JTable table) {
+               this.table = table;
+       }
+
+       public BaseTableModel getSpecialTableModel() {
+               return specialTableModel;
+       }
+
+       public void setSpecialTableModel(BaseTableModel specialTableModel) {
+               this.specialTableModel = specialTableModel;
+       }
+       
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueBorder.java b/srcjar/fr/orsay/lri/varna/views/VueBorder.java
new file mode 100644 (file)
index 0000000..b0f9ca6
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurBorder;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+
+public class VueBorder {
+
+       private VARNAPanel _vp;
+       private JSlider borderHeightSlider, borderWidthSlider;
+       private JPanel panel;
+
+       public VueBorder(VARNAPanel vp) {
+               _vp = vp;
+
+               JPanel pup = new JPanel();
+               JPanel pdown = new JPanel();
+               panel = new JPanel();
+               panel.setLayout(new GridLayout(2, 1));
+               pup.setLayout(new FlowLayout(FlowLayout.LEFT));
+               pdown.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               borderHeightSlider = new JSlider(JSlider.HORIZONTAL, 0,
+                               _vp.getHeight() / 2 - 10, _vp.getBorderSize().height);
+               // Turn on labels at major tick marks.
+               borderHeightSlider.setMajorTickSpacing(50);
+               borderHeightSlider.setMinorTickSpacing(10);
+               borderHeightSlider.setPaintTicks(true);
+               borderHeightSlider.setPaintLabels(true);
+               JLabel borderHeightLabel = new JLabel(String.valueOf(_vp
+                               .getBorderSize().height));
+               borderHeightLabel.setPreferredSize(new Dimension(50, borderHeightLabel
+                               .getPreferredSize().height));
+               borderHeightSlider.addChangeListener(new ControleurSliderLabel(
+                               borderHeightLabel, false));
+               borderHeightSlider.addChangeListener(new ControleurBorder(this));
+
+               borderWidthSlider = new JSlider(JSlider.HORIZONTAL, 0,
+                               _vp.getWidth() / 2 - 10, _vp.getBorderSize().width);
+               // Turn on labels at major tick marks.
+               borderWidthSlider.setMajorTickSpacing(50);
+               borderWidthSlider.setMinorTickSpacing(10);
+               borderWidthSlider.setPaintTicks(true);
+               borderWidthSlider.setPaintLabels(true);
+               JLabel borderWidthLabel = new JLabel(String
+                               .valueOf(_vp.getBorderSize().width));
+               borderWidthLabel.setPreferredSize(new Dimension(50, borderWidthLabel
+                               .getPreferredSize().height));
+               borderWidthSlider.addChangeListener(new ControleurSliderLabel(
+                               borderWidthLabel, false));
+               borderWidthSlider.addChangeListener(new ControleurBorder(this));
+
+               JLabel labelW = new JLabel("Width:");
+               JLabel labelH = new JLabel("Height:");
+
+               pup.add(labelW);
+               pup.add(borderWidthSlider);
+               pup.add(borderWidthLabel);
+
+               pdown.add(labelH);
+               pdown.add(borderHeightSlider);
+               pdown.add(borderHeightLabel);
+
+               panel.add(pup);
+               panel.add(pdown);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public Dimension getDimension() {
+               return new Dimension(borderWidthSlider.getValue(), borderHeightSlider
+                               .getValue());
+       }
+
+       public int getHeight() {
+               return borderHeightSlider.getValue();
+       }
+
+       public int getWidth() {
+               return borderWidthSlider.getValue();
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueChemProbAnnotation.java b/srcjar/fr/orsay/lri/varna/views/VueChemProbAnnotation.java
new file mode 100644 (file)
index 0000000..536d3fa
--- /dev/null
@@ -0,0 +1,172 @@
+       package fr.orsay.lri.varna.views;
+
+
+       import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+
+
+public class VueChemProbAnnotation implements ChangeListener, ActionListener, ItemListener {
+
+               protected VARNAPanel _vp;
+               private JPanel panel;
+               protected ChemProbAnnotation _an;
+               private static int CONTROL_HEIGHT = 50;
+               private static int TITLE_WIDTH = 70;
+               private static int CONTROL_WIDTH = 200;
+               protected JButton color  = new JButton();
+               JSpinner intensity;
+               JComboBox outward = new JComboBox(new String[]{"Inward","Outward"});
+               JComboBox type = new JComboBox(ChemProbAnnotation.ChemProbAnnotationType.values());
+
+               public VueChemProbAnnotation(VARNAPanel vp, ChemProbAnnotation an) {
+                       _an = an;
+                       _vp = vp;
+
+                       panel = new JPanel();
+                       panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+
+                       JPanel outlinep = new JPanel();
+                       JLabel l1 = new JLabel("Color: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       color.setContentAreaFilled(false);
+                       color.setOpaque(true);
+                       color.setPreferredSize(new Dimension(CONTROL_WIDTH,CONTROL_HEIGHT));
+                       color.setBackground(_an.getColor());
+                       color.addActionListener(this);
+                       color.setActionCommand("outline");
+                       outlinep.add(l1);
+                       outlinep.add(color);
+                       
+                       
+                       JPanel radiusp = new JPanel();
+                       l1 = new JLabel("Intensity: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       SpinnerNumberModel jm = new SpinnerNumberModel(_an.getIntensity(),0.01,10.0,0.01);
+                       intensity = new JSpinner(jm);
+                       radiusp.add(l1);
+                       radiusp.add(intensity);
+                       intensity.addChangeListener(this);
+
+                       JPanel dirp = new JPanel();
+                       l1 = new JLabel("Direction: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       outward.addItemListener(this);
+                       dirp.add(l1);
+                       dirp.add(outward);
+
+                       JPanel typep = new JPanel();
+                       l1 = new JLabel("Type: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       type.addItemListener(this);
+                       typep.add(l1);
+                       typep.add(type);
+                       
+                       
+                       
+                       JPanel jp = new JPanel();
+                       jp.setLayout(new GridLayout(4,1));
+                       jp.add(outlinep);
+                       jp.add(radiusp);
+                       jp.add(dirp);
+                       jp.add(typep);
+                       panel.add(jp);
+               }
+
+               public JPanel getPanel() {
+                       return panel;
+               }
+
+               public VARNAPanel get_vp() {
+                       return _vp;
+               }
+               
+               HighlightRegionAnnotation _backup = null;
+               
+               public boolean show() {
+                       boolean accept = false;
+                       intensity.setValue(_an.getIntensity());
+                       color.setBackground(_an.getColor());
+                       type.setSelectedItem(_an.getType());
+                       outward.setSelectedItem((_an.isOut()?"Inward":"Outward"));
+                       
+                       if (JOptionPane.showConfirmDialog(_vp, getPanel(),
+                                       "Edit chemical probing annotation", JOptionPane.OK_CANCEL_OPTION,
+                                       JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION) 
+                       {
+                               accept = true;
+                       } 
+                       _vp.repaint();
+                       return accept;
+               }
+
+               public void stateChanged(ChangeEvent e) {
+                       if (e.getSource().equals(intensity))
+                       {
+                               Object val = intensity.getValue();
+                               if (val instanceof Double)
+                               {                                       
+                                       _an.setIntensity(((Double)val).doubleValue());
+                                       _vp.repaint();
+                               }
+                       }
+                       
+               }
+
+       public void actionPerformed(ActionEvent e) {
+               if (e.getActionCommand().equals("outline")) {
+                       // BH j2s SwingJS asynchronous for JavaScript; synchronous for Java
+                       _vp.getVARNAUI().showColorDialog("Choose new outline color", _an.getColor(), new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       Color c = (Color) _vp.getVARNAUI().dialogReturnValue;
+                                       if (c != null) {
+                                               _an.setColor(c);
+                                               color.setBackground(_an.getColor());
+                                               _vp.repaint();
+                                       }
+                               }
+
+                       });
+               }
+
+       }
+
+               public void itemStateChanged(ItemEvent e) {
+                       if (e.getSource()==outward)
+                       {
+                               _an.setOut(!e.getItem().equals("Outward"));
+                               _vp.repaint();
+                       }
+                       else if ((e.getSource()==type)&&(e.getItem() instanceof ChemProbAnnotation.ChemProbAnnotationType))
+                       {
+                               ChemProbAnnotation.ChemProbAnnotationType t = (ChemProbAnnotation.ChemProbAnnotationType) e.getItem();
+                               _an.setType(t);
+                               _vp.repaint();
+                       }
+                       
+               }
+       }
+
+
diff --git a/srcjar/fr/orsay/lri/varna/views/VueColorMapStyle.java b/srcjar/fr/orsay/lri/varna/views/VueColorMapStyle.java
new file mode 100644 (file)
index 0000000..6a1a836
--- /dev/null
@@ -0,0 +1,305 @@
+package fr.orsay.lri.varna.views;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.filechooser.FileFilter;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.GradientEditorPanel;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap.NamedColorMapTypes;
+
+public class VueColorMapStyle extends JPanel implements ActionListener, ItemListener, PropertyChangeListener {
+       private VARNAPanel _vp;
+       private GradientEditorPanel _gp;
+       private JComboBox _cb; 
+       private JTextField _code; 
+       private ModeleColorMap _backup;
+       // TODO BH SwingJS note that the save dialog is never used in JavaScript 
+       private static JFileChooser fc = new JFileChooser(){
+           public void approveSelection(){
+               File f = getSelectedFile();
+               if(f.exists() && getDialogType() == SAVE_DIALOG){
+                   int result = JOptionPane.showConfirmDialog(this,"The file exists, overwrite?","Existing file",JOptionPane.YES_NO_OPTION);
+                   switch(result){
+                       case JOptionPane.YES_OPTION:
+                           super.approveSelection();
+                           return;
+                       case JOptionPane.NO_OPTION:
+                           return;
+                       case JOptionPane.CLOSED_OPTION:
+                           return;
+                       case JOptionPane.CANCEL_OPTION:
+                           cancelSelection();
+                           return;
+                   }
+               }
+               super.approveSelection();
+           }        
+       };
+       
+       public VueColorMapStyle(VARNAPanel vp)
+       {
+               super();
+               _vp = vp;
+               init();
+       }
+
+       private void init()
+       {
+               JLabel gradientCaption = new JLabel("Click gradient to add new color...");
+               _gp = new GradientEditorPanel(_vp.getColorMap().clone());
+               _backup = _vp.getColorMap();
+               _gp.setPreferredSize(new Dimension(300,70));
+               _gp.addPropertyChangeListener(this);
+
+               JPanel codePanel = new JPanel();
+               JLabel codeCaption = new JLabel("Param. code: ");
+               codeCaption.setVisible(true);
+               _code = new JTextField("");
+               _code.setFont(Font.decode("Monospaced-PLAIN-12"));
+               _code.setEditable(true);
+               _code.addFocusListener(new FocusListener(){
+
+                       public void focusGained(FocusEvent arg0) {
+                                               _code.setSelectionStart(0);
+                                               _code.setSelectionEnd(_code.getText().length());
+                       }
+
+                       public void focusLost(FocusEvent arg0) {
+                       }                       
+               });             
+               _code.setVisible(false);
+               
+               NamedColorMapTypes[] palettes =  ModeleColorMap.NamedColorMapTypes.values();
+               Arrays.sort(palettes,new Comparator<ModeleColorMap.NamedColorMapTypes>(){
+                       public int compare(ModeleColorMap.NamedColorMapTypes arg0, ModeleColorMap.NamedColorMapTypes arg1) {
+                               return arg0.getId().compareTo(arg1.getId());
+                       }                       
+               });
+               Object[] finalArray = new Object[palettes.length+1];
+               int selected = -1;
+               for (int i=0;i<palettes.length;i++)
+               { 
+                       if (palettes[i].getColorMap().equals(_vp.getColorMap()))
+                       {
+                               selected = i; 
+                               //System.out.println(selected);
+                       }
+                       finalArray[i] = palettes[i]; 
+               }
+               String custom = new String("Custom...");
+               finalArray[palettes.length] = custom;
+               _cb = new JComboBox(finalArray);
+               if (selected!=-1)
+               {
+                       _cb.setSelectedIndex(selected);
+               }
+               else
+               {
+                       _cb.setSelectedItem(finalArray.length-1);
+               }
+               _cb.addItemListener(this);
+               
+               _code.setText(getTextRepresentation());
+               
+               
+               
+               FileFilter CMSFiles = new FileFilter(){
+                       public boolean accept(File f) {
+                               return f.getName().toLowerCase().endsWith(".cms") || f.isDirectory();
+                       }
+
+                       public String getDescription() {
+                               return "Color Map (*.cms) Files";
+                       }
+                       
+               };
+               fc.addChoosableFileFilter(CMSFiles);
+               fc.setFileFilter(CMSFiles);
+               
+               codePanel.setLayout(new BoxLayout(codePanel,BoxLayout.LINE_AXIS));
+               codePanel.add(codeCaption);
+               codePanel.add(_code);
+               JButton loadStyleButton = new JButton("Load");
+               loadStyleButton.addActionListener(new ActionListener(){
+                       public void actionPerformed(ActionEvent e) {
+                               if (fc.showOpenDialog(VueColorMapStyle.this)==JFileChooser.APPROVE_OPTION)
+                               {
+                                       File file = fc.getSelectedFile();
+                                       try {
+                                               FileInputStream fis = new FileInputStream(file);
+                                               byte[] data = new byte[(int) file.length()];
+                                               fis.read(data);
+                                               fis.close();
+                                               String str = new String(data).trim();
+                                               ModeleColorMap ns = ModeleColorMap.parseColorMap(str);
+                                               _gp.setColorMap(ns);
+                                               refreshCode();
+                                       } catch (FileNotFoundException e1) {
+                                               e1.printStackTrace();
+                                       } catch (IOException e1) {
+                                               e1.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               JButton saveStyleButton = new JButton("Save");
+               saveStyleButton.addActionListener(new ActionListener(){
+                       public void actionPerformed(ActionEvent e) {
+                               if (fc.showSaveDialog(VueColorMapStyle.this)==JFileChooser.APPROVE_OPTION)
+                               {
+                                       try {
+                                               PrintWriter out = new PrintWriter(fc.getSelectedFile());
+                                               out.println(_gp.getColorMap().getParamEncoding());
+                                               out.close();
+                                       } catch (FileNotFoundException e1) {
+                                               e1.printStackTrace();
+                                       } catch (IOException e1) {
+                                               e1.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               saveStyleButton.setAlignmentX(CENTER_ALIGNMENT);
+               loadStyleButton.setAlignmentX(CENTER_ALIGNMENT);
+
+               JPanel jp = new JPanel(new BorderLayout());
+               jp.add(_cb,BorderLayout.CENTER);
+               JPanel jp2 = new JPanel();
+               jp2.setLayout(new BoxLayout(jp2,BoxLayout.X_AXIS));
+               jp2.add(Box.createRigidArea(new Dimension(5,0)));
+               jp2.add(loadStyleButton);
+               jp2.add(Box.createRigidArea(new Dimension(5,0)));
+               jp2.add(saveStyleButton);
+               jp.add(jp2,BorderLayout.EAST);
+               
+               setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+               add(jp);
+               add(Box.createVerticalStrut(10));
+               add(_gp);
+               add(gradientCaption);
+               //add(Box.createVerticalStrut(10));
+               //add(codePanel);
+       }
+       
+       public String getTextRepresentation()
+       {
+               int selected = _cb.getSelectedIndex();
+               if ((selected!=-1) && (selected<_cb.getItemCount()-1))
+               {
+                       return ((NamedColorMapTypes) _cb.getSelectedItem()).getId(); 
+               }
+               else
+               {
+                       return _gp.getColorMap().getParamEncoding();
+               }
+       }
+       
+       public void cancelChanges()
+       {
+               _vp.setColorMap(_backup);
+       }
+       
+       public ModeleColorMap getColorMap()
+       {
+               return _gp.getColorMap();
+       }
+       
+       public void actionPerformed(ActionEvent e) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       
+       private void refreshCode()
+       {
+               int selected = -1;
+               NamedColorMapTypes n = null;
+               for (int i=0;i<_cb.getItemCount()-1;i++)
+               { 
+                       Object o = _cb.getItemAt(i);
+                       if (o instanceof NamedColorMapTypes)
+                       {
+                               NamedColorMapTypes ni = (NamedColorMapTypes) o;
+                               if (ni.getColorMap().equals(_gp.getColorMap()))
+                               { 
+                                       selected = i;   n = ni;
+                               }
+                       }
+               }
+               if (selected!=-1)
+               {
+                       _code.setText(n.getId());
+               }
+               _code.setText(getTextRepresentation());
+               _vp.setColorMap(_gp.getColorMap());
+               _gp.repaint();
+       }
+
+       public void itemStateChanged(ItemEvent arg0) {
+               if (arg0.getStateChange()==ItemEvent.SELECTED)
+               {
+               Object o = arg0.getItem();
+               if (o instanceof ModeleColorMap.NamedColorMapTypes)
+               {
+                       ModeleColorMap.NamedColorMapTypes n = ((ModeleColorMap.NamedColorMapTypes) o);
+                       ModeleColorMap m = n.getColorMap().clone();
+                       m.setMinValue(_backup.getMinValue());
+                       m.setMaxValue(_backup.getMaxValue());
+                       _gp.setColorMap(m);
+                       refreshCode();
+               }
+               }
+       }
+
+       public void propertyChange(PropertyChangeEvent arg0) {
+               if (arg0.getPropertyName().equals("PaletteChanged"))
+               {
+                       _cb.setSelectedIndex(_cb.getItemCount()-1);
+                       refreshCode();
+               };
+       }
+
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueFont.java b/srcjar/fr/orsay/lri/varna/views/VueFont.java
new file mode 100644 (file)
index 0000000..7e9bd29
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+public class VueFont {
+
+       private VARNAPanel _vp;
+       private Font font;
+       private JComboBox stylesBox;
+       private JComboBox boxPolice;
+       private JPanel panel;
+       private JSlider sizeSlider;
+
+       public VueFont(VARNAPanel vp) {
+               _vp = vp;
+               init();
+               buildViewVPTitle();
+       }
+
+       public VueFont(Font f) {
+               font = f;
+               init();
+               buildViewFont();
+       }
+
+       private void init() {
+               GraphicsEnvironment ge = GraphicsEnvironment
+                               .getLocalGraphicsEnvironment();
+               String[] polices = ge.getAvailableFontFamilyNames();
+               boxPolice = new JComboBox(polices);
+
+               sizeSlider = new JSlider(JSlider.HORIZONTAL, 4, 88, 14);
+               // Turn on labels at major tick marks.
+               sizeSlider.setMajorTickSpacing(10);
+               sizeSlider.setMinorTickSpacing(5);
+               sizeSlider.setPaintTicks(true);
+               sizeSlider.setPaintLabels(true);
+
+               String[] styles = { "Plain", "Italic", "Bold" };
+               stylesBox = new JComboBox(styles);
+
+               panel = new JPanel();
+               panel.add(boxPolice);
+               panel.add(sizeSlider);
+               panel.add(stylesBox);
+       }
+
+       private void buildViewFont() {
+               boxPolice.setSelectedItem(font.getFamily());
+               sizeSlider.setValue(font.getSize());
+               stylesBox.setSelectedItem(styleIntToString(font.getStyle()));
+       }
+
+       private void buildViewVPTitle() {
+               boxPolice.setSelectedItem(_vp.getTitleFont().getFamily());
+               sizeSlider.setValue(_vp.getTitleFont().getSize());
+               stylesBox.setSelectedItem(styleIntToString(_vp.getTitleFont()
+                               .getStyle()));
+       }
+
+       public String styleIntToString(int styleInt) {
+               switch (styleInt) {
+               case Font.PLAIN:// Plain
+                       return "Plain";
+               case Font.ITALIC:// Italic
+                       return "Italic";
+               case Font.BOLD:// Bold
+                       return "Bold";
+               default:// Plain
+                       return "Plain";
+               }
+       }
+
+       public JComboBox getStylesBox() {
+               return stylesBox;
+       }
+
+       public JComboBox getBoxPolice() {
+               return boxPolice;
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public JSlider getSizeSlider() {
+               return sizeSlider;
+       }
+
+       public Font getFont() {
+               int style;
+               switch (getStylesBox().getSelectedIndex()) {
+               case 0:// Plain
+                       style = Font.PLAIN;
+                       break;
+               case 1:// Italic
+                       style = Font.ITALIC;
+                       break;
+               case 2:// Bold
+                       style = Font.BOLD;
+                       break;
+               default:// Plain
+                       style = Font.PLAIN;
+                       break;
+               }
+               return new Font((String) getBoxPolice().getSelectedItem(), style,
+                               getSizeSlider().getValue());
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueGlobalRescale.java b/srcjar/fr/orsay/lri/varna/views/VueGlobalRescale.java
new file mode 100644 (file)
index 0000000..02eba49
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurGlobalRescale;
+import fr.orsay.lri.varna.controlers.ControleurGlobalRotation;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+
+public class VueGlobalRescale {
+
+       private VARNAPanel _vp;
+       private JSlider rescaleSlider;
+       private JPanel panel;
+
+       public VueGlobalRescale(VARNAPanel vp) {
+               _vp = vp;
+               rescaleSlider = new JSlider(JSlider.HORIZONTAL, 1, 500, 100);
+               rescaleSlider.setMajorTickSpacing(100);
+               rescaleSlider.setPaintTicks(true);
+               rescaleSlider.setPaintLabels(true);
+               rescaleSlider.setPreferredSize(new Dimension(500, 50));
+
+               JLabel rescaleLabel = new JLabel(String.valueOf(0));
+               rescaleLabel.setPreferredSize(new Dimension(50, rescaleLabel
+                               .getPreferredSize().height));
+               rescaleSlider.addChangeListener(new ControleurSliderLabel(
+                               rescaleLabel, false));
+               rescaleSlider.addChangeListener(new ControleurGlobalRescale(this,vp));
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelZ = new JLabel("Scale (%):");
+
+               panel.add(labelZ);
+               panel.add(rescaleSlider);
+               panel.add(rescaleLabel);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public double getScale() {
+               return rescaleSlider.getValue()/100.0;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueGlobalRotation.java b/srcjar/fr/orsay/lri/varna/views/VueGlobalRotation.java
new file mode 100644 (file)
index 0000000..5459d33
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurGlobalRotation;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+
+public class VueGlobalRotation {
+
+       private VARNAPanel _vp;
+       private JSlider rotationSlider;
+       private JPanel panel;
+
+       public VueGlobalRotation(VARNAPanel vp) {
+               _vp = vp;
+               rotationSlider = new JSlider(JSlider.HORIZONTAL, -360, 360, 0);
+               rotationSlider.setMajorTickSpacing(60);
+               rotationSlider.setPaintTicks(true);
+               rotationSlider.setPaintLabels(true);
+               rotationSlider.setPreferredSize(new Dimension(500, 50));
+
+               JLabel rotationLabel = new JLabel(String.valueOf(0));
+               rotationLabel.setPreferredSize(new Dimension(50, rotationLabel
+                               .getPreferredSize().height));
+               rotationSlider.addChangeListener(new ControleurSliderLabel(
+                               rotationLabel, false));
+               rotationSlider.addChangeListener(new ControleurGlobalRotation(this,vp));
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelZ = new JLabel("Rotation (degrees):");
+
+               panel.add(labelZ);
+               panel.add(rotationSlider);
+               panel.add(rotationLabel);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public double getAngle() {
+               return rotationSlider.getValue();
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueHighlightRegionEdit.java b/srcjar/fr/orsay/lri/varna/views/VueHighlightRegionEdit.java
new file mode 100644 (file)
index 0000000..ca01a25
--- /dev/null
@@ -0,0 +1,200 @@
+package fr.orsay.lri.varna.views;
+
+
+       import java.awt.Color;
+import java.awt.Dimension;
+       import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+       import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+       import javax.swing.JPanel;
+       import javax.swing.JSlider;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+
+       public class VueHighlightRegionEdit implements ChangeListener, ActionListener {
+
+               private VARNAPanel _vp;
+               private JSlider _fromSlider;
+               private JSlider _toSlider;
+               private JPanel panel;
+               private HighlightRegionAnnotation _an;
+               private static int CONTROL_HEIGHT = 50;
+               private static int TITLE_WIDTH = 70;
+               private static int CONTROL_WIDTH = 200;
+               private JButton fillShow  = new JButton();
+               private JButton outlineShow  = new JButton();
+               JSpinner rad;
+
+               public VueHighlightRegionEdit(VARNAPanel vp, HighlightRegionAnnotation an) {
+                       _an = an;
+                       _vp = vp;
+                       _toSlider = new JSlider(JSlider.HORIZONTAL, 0,vp.getRNA().getSize()-1,0);
+                       _toSlider.setMajorTickSpacing(10);
+                       _toSlider.setPaintTicks(true);
+                       _toSlider.setPaintLabels(true);
+                       _toSlider.setPreferredSize(new Dimension(CONTROL_WIDTH, CONTROL_HEIGHT));
+
+                       _fromSlider = new JSlider(JSlider.HORIZONTAL, 0,vp.getRNA().getSize()-1,0);
+                       _fromSlider.setMajorTickSpacing(10);
+                       _fromSlider.setPaintTicks(true);
+                       _fromSlider.setPaintLabels(true);
+                       _fromSlider.setPreferredSize(new Dimension(CONTROL_WIDTH, CONTROL_HEIGHT));
+
+                       _fromSlider.addChangeListener(this);
+                       _toSlider.addChangeListener(this);
+
+                       panel = new JPanel();
+                       panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+                       JPanel fromp = new JPanel();
+                       JLabel l1 = new JLabel("From: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       fromp.add(l1);
+                       fromp.add(_fromSlider);
+
+                       JPanel top = new JPanel();
+                       l1 = new JLabel("To: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       top.add(l1);
+                       top.add(_toSlider);
+
+                       JPanel outlinep = new JPanel();
+                       l1 = new JLabel("Outline color: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       outlineShow.setContentAreaFilled(false);
+                       outlineShow.setOpaque(true);
+                       outlineShow.setPreferredSize(new Dimension(CONTROL_WIDTH,CONTROL_HEIGHT));
+                       outlineShow.setBackground(an.getOutlineColor());
+                       outlineShow.addActionListener(this);
+                       outlineShow.setActionCommand("outline");
+                       outlinep.add(l1);
+                       outlinep.add(outlineShow);
+                       
+                       JPanel fillp = new JPanel();
+                       l1 = new JLabel("Fill color: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       fillShow.setContentAreaFilled(false);
+                       fillShow.setOpaque(true);
+                       fillShow.setPreferredSize(new Dimension(CONTROL_WIDTH,CONTROL_HEIGHT));
+                       fillShow.setBackground(an.getFillColor());
+                       fillShow.addActionListener(this);
+                       fillShow.setActionCommand("fill");
+                       fillp.add(l1);
+                       fillp.add(fillShow);
+                       
+                       JPanel radiusp = new JPanel();
+                       l1 = new JLabel("Radius: ");
+                       l1.setPreferredSize(new Dimension(TITLE_WIDTH,CONTROL_HEIGHT));
+                       SpinnerNumberModel jm = new SpinnerNumberModel(_an.getRadius(),1.0,50.0,0.1);
+                       rad = new JSpinner(jm);
+                       rad.setPreferredSize(new Dimension(CONTROL_WIDTH,CONTROL_HEIGHT));
+                       radiusp.add(l1);
+                       radiusp.add(rad);
+                       rad.addChangeListener(this);
+                       
+                       JPanel jp = new JPanel();
+                       jp.setLayout(new GridLayout(5,1));
+                       jp.add(fromp);
+                       jp.add(top);
+                       jp.add(outlinep);
+                       jp.add(fillp);
+                       jp.add(radiusp);
+                       panel.add(jp);
+               }
+
+               public JPanel getPanel() {
+                       return panel;
+               }
+
+               public double getAngle() {
+                       return _toSlider.getValue();
+               }
+
+               public VARNAPanel get_vp() {
+                       return _vp;
+               }
+               
+               HighlightRegionAnnotation _backup = null;
+               
+               public boolean show() {
+                       boolean accept = false;
+                       int from = _an.getMinIndex();
+                       int to = _an.getMaxIndex();
+                       _fromSlider.setValue(from);
+                       _toSlider.setValue(to );
+                       if (JOptionPane.showConfirmDialog(_vp, getPanel(),
+                                       "Edit region annotation", JOptionPane.OK_CANCEL_OPTION,
+                                       JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION) 
+                       {
+                               accept = true;
+                       } 
+                       _vp.repaint();
+                       return accept;
+               }
+
+               public void stateChanged(ChangeEvent e) {
+                       if ((e.getSource()==_toSlider)||(e.getSource()==_fromSlider))
+                       {
+                       int from  = _fromSlider.getValue(); 
+                       int to  = _toSlider.getValue(); 
+                       if (from>to)
+                       {
+                               if (e.getSource().equals(_fromSlider))
+                               {
+                                       _toSlider.setValue(from);
+                               }
+                               else if (e.getSource().equals(_toSlider))
+                               {
+                                       _fromSlider.setValue(to);
+                               }
+                       }
+                       from  = _fromSlider.getValue(); 
+                       to  = _toSlider.getValue(); 
+                       _an.setBases(_vp.getRNA().getBasesBetween(from, to));
+                       _vp.repaint();
+                       }
+                       else if (e.getSource().equals(rad))
+                       {
+                               Object val = rad.getValue();
+                               if (val instanceof Double)
+                               {                                       
+                                       _an.setRadius(((Double)val).doubleValue());
+                               }
+                       }
+                       
+               }
+
+               public void actionPerformed(ActionEvent e) {
+                       if (e.getActionCommand().equals("outline"))
+                       {
+                         Color c = JColorChooser.showDialog(getPanel(), "Choose new outline color", _an.getOutlineColor());
+                         if (c!= null)
+                         {   _an.setOutlineColor(c);  }
+                       }
+                       else if (e.getActionCommand().equals("fill"))
+                       {
+                                 Color c = JColorChooser.showDialog(getPanel(), "Choose new fill color", _an.getFillColor());
+                                 if (c!= null)
+                                 {  _an.setFillColor(c); }
+                       }
+                       outlineShow.setBackground(_an.getOutlineColor());
+                       fillShow.setBackground(_an.getFillColor());
+                       _vp.repaint();
+                       
+               }
+       }
+
diff --git a/srcjar/fr/orsay/lri/varna/views/VueJPEG.java b/srcjar/fr/orsay/lri/varna/views/VueJPEG.java
new file mode 100644 (file)
index 0000000..e6741a9
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+public class VueJPEG {
+       private JSlider qualitySlider;
+       private JSlider scaleSlider;
+       private JPanel panel;
+
+       // Turn on labels at major tick marks.
+//     public VueJPEG() {
+//     }
+
+       public VueJPEG(boolean showQuality, boolean showScale) {
+               qualitySlider = new JSlider(JSlider.HORIZONTAL, 10, 100, 75);
+               qualitySlider.setMajorTickSpacing(5);
+               qualitySlider.setPaintTicks(true);
+               qualitySlider.setPaintLabels(true);
+               qualitySlider.setPreferredSize(new Dimension(400, 50));
+               scaleSlider = new JSlider(JSlider.HORIZONTAL, 0, 600, 100);
+               scaleSlider.setPreferredSize(new Dimension(400, 50));
+               scaleSlider.setMajorTickSpacing(100);
+               scaleSlider.setPaintTicks(true);
+               scaleSlider.setPaintLabels(true);
+               panel = new JPanel();
+               JPanel pup = new JPanel();
+               JPanel pdown = new JPanel();
+               int nbPanels = 0;
+               if (showQuality)
+                       nbPanels++;
+               if (showScale)
+                       nbPanels++;
+               panel.setLayout(new GridLayout(nbPanels, 1));
+               pup.setLayout(new FlowLayout(FlowLayout.LEFT));
+               pdown.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel lseq = new JLabel("Resolution:");
+               JLabel lstr = new JLabel("Quality:");
+               pup.add(lseq);
+               pup.add(scaleSlider);
+               pdown.add(lstr);
+               pdown.add(qualitySlider);
+               if (showQuality) {
+                       panel.add(pup);
+               }
+               if (showScale) {
+                       panel.add(pdown);
+               }
+       }
+
+       public JSlider getQualitySlider() {
+               return qualitySlider;
+       }
+
+       public JSlider getScaleSlider() {
+               return scaleSlider;
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueListeAnnotations.java b/srcjar/fr/orsay/lri/varna/views/VueListeAnnotations.java
new file mode 100644 (file)
index 0000000..6fecd1d
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * VARNA is a tool for the automated drawing, visualization and annotation
+ * of the secondary structure of RNA, designed as a companion software for
+ * web servers and databases. Copyright (C) 2008 Kevin Darty, Alain Denise
+ * and Yann Ponty. electronic mail : Yann.Ponty@lri.fr paper mail : LRI, bat
+ * 490 Université Paris-Sud 91405 Orsay Cedex France
+ * 
+ * This file is part of VARNA version 3.1. VARNA version 3.1 is free
+ * software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ * 
+ * VARNA version 3.1 is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with VARNA version 3.1. If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.filechooser.FileFilter;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.AnnotationTableModel;
+import fr.orsay.lri.varna.controlers.ControleurTableAnnotations;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+
+/**
+ * a view for all annoted texts on the VARNAPanel
+ * 
+ * @author Darty@lri.fr
+ * 
+ */
+public class VueListeAnnotations extends JPanel {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * if this view is for removing annoted texts
+        */
+       public static final int REMOVE = 0;
+       /**
+        * if this view is for editing annoted texts
+        */
+       public static final int EDIT = 1;
+
+       private VARNAPanel _vp;
+       private ArrayList<Object> data;
+       private JTable table;
+       private int type;
+       private AnnotationTableModel specialTableModel;
+       // BH SwingJS - this is never used in JavaScript
+       private static JFileChooser fc = new JFileChooser(){
+           public void approveSelection(){
+               File f = getSelectedFile();
+               if(f.exists() && getDialogType() == SAVE_DIALOG){
+                   int result = JOptionPane.showConfirmDialog(this,"The file exists, overwrite?","Existing file",JOptionPane.YES_NO_OPTION);
+                   switch(result){
+                       case JOptionPane.YES_OPTION:
+                           super.approveSelection();
+                           return;
+                       case JOptionPane.NO_OPTION:
+                           return;
+                       case JOptionPane.CLOSED_OPTION:
+                           return;
+                       case JOptionPane.CANCEL_OPTION:
+                           cancelSelection();
+                           return;
+                   }
+               }
+               super.approveSelection();
+           }        
+       };
+
+
+       /**
+        * creates the view
+        * 
+        * @param vp
+        * @param type
+        *            (REMOVE or EDIT)
+        */
+       public VueListeAnnotations(VARNAPanel vp, int type) {
+               super(new BorderLayout());
+               this.type = type;
+               _vp = vp;
+               data = new ArrayList<Object>();
+               data.addAll(_vp.getListeAnnotations());
+               data.addAll(_vp.getRNA().getHighlightRegion());
+               data.addAll(_vp.getRNA().getChemProbAnnotations());
+               createView();
+       }
+
+       private void createView() {
+               specialTableModel = new AnnotationTableModel(data);
+               table = new JTable(specialTableModel);
+               ControleurTableAnnotations ctrl = new ControleurTableAnnotations(table,
+                               _vp, type);
+               table.addMouseListener(ctrl);
+               table.addMouseMotionListener(ctrl);
+               // table.setPreferredScrollableViewportSize(new Dimension(500, 100));
+               // TODO: Find equivalent in JRE 1.5
+               // table.setFillsViewportHeight(true);
+               // Create the scroll pane and add the table to it.
+               JScrollPane scrollPane = new JScrollPane(table);
+
+               add(scrollPane, BorderLayout.CENTER);
+               
+               FileFilter CPAFiles = new FileFilter(){
+                       public boolean accept(File f) {
+                               return f.getName().toLowerCase().endsWith(".cpa") || f.isDirectory();
+                       }
+
+                       public String getDescription() {
+                               return "Chemical Probing Annotations (*.cpa) Files";
+                       }
+                       
+               };
+               fc.addChoosableFileFilter(CPAFiles);
+               fc.setFileFilter(CPAFiles);
+
+               
+               JButton loadStyleButton = new JButton("Load");
+               loadStyleButton.addActionListener(new ActionListener(){
+                       public void actionPerformed(ActionEvent e) {
+                               if (fc.showOpenDialog(VueListeAnnotations.this)==JFileChooser.APPROVE_OPTION)
+                               {
+                                       File file = fc.getSelectedFile();
+                                       try {
+                                               BufferedReader br = new BufferedReader(new FileReader(file));
+                                               String s = br.readLine();
+                                               while(s != null)
+                                               {
+                                                       if (s.startsWith(TextAnnotation.HEADER_TEXT))
+                                                       s = br.readLine();
+                                               }
+                                               // TODO
+                                       } catch (FileNotFoundException e1) {
+                                               e1.printStackTrace();
+                                       } catch (IOException e1) {
+                                               e1.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               JButton saveStyleButton = new JButton("Save");
+               saveStyleButton.addActionListener(new ActionListener(){
+                       public void actionPerformed(ActionEvent e) {
+                               if (fc.showSaveDialog(VueListeAnnotations.this)==JFileChooser.APPROVE_OPTION)
+                               {
+                                       try {
+                                               PrintWriter out = new PrintWriter(fc.getSelectedFile());
+                                               // TODO out.println(_gp.getColorMap().getParamEncoding());
+                                               out.close();
+                                       } catch (FileNotFoundException e1) {
+                                               e1.printStackTrace();
+                                       } catch (IOException e1) {
+                                               e1.printStackTrace();
+                                       }
+                               }
+                       }
+                       
+               });
+               saveStyleButton.setAlignmentX(CENTER_ALIGNMENT);
+               loadStyleButton.setAlignmentX(CENTER_ALIGNMENT);
+
+               JPanel jp2 = new JPanel();
+               BoxLayout bl = new BoxLayout(jp2, BoxLayout.X_AXIS);
+               jp2.setLayout(bl);
+               jp2.setAlignmentX(CENTER_ALIGNMENT);
+               jp2.add(loadStyleButton);
+               jp2.add(Box.createRigidArea(new Dimension(5,0)));
+               jp2.add(saveStyleButton);
+               this.add(jp2,BorderLayout.SOUTH);
+
+               
+
+               UIvueListeAnnotations();
+       }
+
+       /**
+        * Create the GUI and show it. For thread safety, this method should be
+        * invoked from the event-dispatching thread.
+        */
+       public void UIvueListeAnnotations() {
+               JComponent newContentPane = this;
+               newContentPane.setOpaque(true); 
+               JOptionPane.showMessageDialog(_vp, newContentPane,
+                               "Annotation edition", JOptionPane.PLAIN_MESSAGE);
+       }
+
+       public ArrayList<Object> getData() {
+               return data;
+       }
+
+       public void setData(ArrayList<Object> data) {
+               this.data = data;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public JTable getTable() {
+               return table;
+       }
+
+       public void setTable(JTable table) {
+               this.table = table;
+       }
+
+       public AnnotationTableModel getSpecialTableModel() {
+               return specialTableModel;
+       }
+
+       public void setSpecialTableModel(AnnotationTableModel specialTableModel) {
+               this.specialTableModel = specialTableModel;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueLoadColorMapValues.java b/srcjar/fr/orsay/lri/varna/views/VueLoadColorMapValues.java
new file mode 100644 (file)
index 0000000..3bfb27a
--- /dev/null
@@ -0,0 +1,116 @@
+package fr.orsay.lri.varna.views;
+
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+public class VueLoadColorMapValues extends JPanel implements ActionListener {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = -1648400107478203724L;
+       VARNAPanel _vp;
+       
+  public VueLoadColorMapValues(VARNAPanel vp)
+  {
+         _vp = vp;
+         init();
+  }
+  JRadioButton urlCB = new JRadioButton("URL"); 
+  JRadioButton fileCB = new JRadioButton("File");
+  JPanel urlAux = new JPanel();
+  JPanel fileAux = new JPanel();
+  CardLayout l = new CardLayout();
+  JPanel input = new JPanel();
+  JTextField urlTxt = new JTextField(); 
+  JTextField fileTxt = new JTextField(); 
+  JButton load = new JButton("Choose file");
+  
+  private void init()
+  {
+         setLayout(new GridLayout(2,1));
+         JPanel choice = new JPanel();
+         urlCB.addActionListener(this);
+         fileCB.addActionListener(this);
+         ButtonGroup group = new ButtonGroup();
+         group.add(urlCB);
+         group.add(fileCB);
+         choice.add(new JLabel("Choose input source:"));
+         choice.add(urlCB);
+         choice.add(fileCB);
+         input.setLayout(l);
+         urlTxt.setPreferredSize(new Dimension(300,30));
+         fileTxt.setPreferredSize(new Dimension(300,30));
+         urlAux.add(urlTxt);
+         fileAux.add(fileTxt);
+         fileAux.add(load);
+         input.add(fileAux,"file");
+         input.add(urlAux,"url");
+         group.setSelected(fileCB.getModel(), true);
+         load.addActionListener(this);
+         this.add(choice);
+         this.add(input);
+  }
+
+public void actionPerformed(ActionEvent e) {
+       if (e.getSource() instanceof JRadioButton)
+       {
+               if (urlCB.isSelected())
+               {
+                       l.show(input, "url");
+               }
+               else
+               {
+                       l.show(input, "file");
+               }
+       }
+       else if (e.getSource() instanceof JButton)
+       {
+               JFileChooser fc = new JFileChooser();
+               if (fc.showOpenDialog(_vp) == JFileChooser.APPROVE_OPTION)
+               {
+                       this.fileTxt.setText(fc.getSelectedFile().getAbsolutePath());
+               }
+       }
+}
+
+public Reader getReader() throws IOException
+{
+       if (urlCB.isSelected())
+       {
+               URL url = new URL(urlTxt.getText());
+               URLConnection connexion = url.openConnection();
+               connexion.setUseCaches(false);
+               InputStream r = connexion.getInputStream();
+               return new InputStreamReader(r);
+       }
+       else
+       {
+         return new FileReader(fileTxt.getText());
+       }
+       
+}
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueManualInput.java b/srcjar/fr/orsay/lri/varna/views/VueManualInput.java
new file mode 100644 (file)
index 0000000..07aef4a
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import fr.orsay.lri.varna.VARNAPanel;
+
+
+public class VueManualInput {
+
+       private VARNAPanel _vp;
+       private JPanel panel;
+       private JTextField tseq, tstr;
+
+       public VueManualInput(VARNAPanel vp) {
+               _vp = vp;
+               buildView();
+       }
+
+       private void buildView() {
+               panel = new JPanel();
+               JPanel pup = new JPanel();
+               JPanel pdown = new JPanel();
+               panel.setLayout(new GridLayout(2, 1));
+               pup.setLayout(new FlowLayout(FlowLayout.LEFT));
+               pdown.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               Font _textFieldsFont = Font.decode("MonoSpaced-PLAIN-12");
+
+               JLabel lseq = new JLabel("Sequence:");
+               tseq = new JTextField(_vp.getRNA().getListeBasesToString());
+               JLabel lstr = new JLabel("Structure:");
+               tstr = new JTextField(_vp.getRNA().getStructDBN());
+               tstr
+                               .setPreferredSize(new Dimension(400,
+                                               tstr.getPreferredSize().height));
+               tseq
+                               .setPreferredSize(new Dimension(400,
+                                               tseq.getPreferredSize().height));
+               tstr.setFont(_textFieldsFont);
+               tseq.setFont(_textFieldsFont);
+               pup.add(lseq);
+               pup.add(tseq);
+               pdown.add(lstr);
+               pdown.add(tstr);
+               panel.add(pup);
+               panel.add(pdown);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public void setPanel(JPanel panel) {
+               this.panel = panel;
+       }
+
+       public JTextField getTseq() {
+               return tseq;
+       }
+
+       public JTextField getTstr() {
+               return tstr;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueMenu.java b/srcjar/fr/orsay/lri/varna/views/VueMenu.java
new file mode 100644 (file)
index 0000000..f7ee43f
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.KeyStroke;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurMenu;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VueMenu extends JPopupMenu {
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 1L;
+
+       private VARNAPanel _vp;
+
+       private ControleurMenu _controlerMenu;
+
+       private JCheckBoxMenuItem _itemOptionSpecialBaseColored = new JCheckBoxMenuItem(
+                       "Custom colored", false);
+       private JCheckBoxMenuItem _itemShowWarnings = new JCheckBoxMenuItem(
+                       "Show warnings", false);
+       private JCheckBoxMenuItem _itemDrawBackbone = new JCheckBoxMenuItem(
+                       "Draw backbone", true);
+       private JCheckBoxMenuItem _itemOptionGapsBaseColored = new JCheckBoxMenuItem(
+                       "Custom colored", false);
+       private JCheckBoxMenuItem _itemOptionBondsColored = new JCheckBoxMenuItem(
+                       "Use base color for base-pairs", false);
+       private JCheckBoxMenuItem _itemShowNCBP = new JCheckBoxMenuItem(
+                       "Show non-canonical BPs", true);
+       private JCheckBoxMenuItem _itemShowOnlyPlanar = new JCheckBoxMenuItem(
+                       "Hide tertiary BPs", false);
+       private JCheckBoxMenuItem _itemFlatExteriorLoop = new JCheckBoxMenuItem(
+                       "Flat exterior loop", false);
+       
+       private JCheckBoxMenuItem _itemShowColorMap = new JCheckBoxMenuItem(
+                       "Show color map", false);
+       private JMenuItem _dashBasesColor;
+
+       private ArrayList<JComponent> _disabled = new ArrayList<JComponent>();
+
+       private JMenuItem _rotation;
+       private JMenuItem _bpHeightIncrement;
+
+       private Point _spawnOrigin = new Point(-1,-1);
+       
+       public VueMenu(VARNAPanel vp) {
+               _vp = vp;
+               _controlerMenu = new ControleurMenu(_vp, this);
+       }
+
+       private void addTitle(String title, boolean keep) {
+               // TOD BH SwingJS -- this should not be necessary
+               JSeparator sep = new JPopupMenu.Separator(); // BH SWingJS needs JPopupMenu.Separator
+               //JSeparator sep = new JSeparator();
+               JMenuItem titleItem = new JMenuItem(" " + title);//BH SwingJS was JLabel -- need to be able to do this.
+               // titleItem.setAlignmentX(0.5f);
+               Font previousFont = titleItem.getFont();
+               Font futureFont = previousFont.deriveFont(Font.BOLD).deriveFont(
+                               (float) previousFont.getSize() + 1.0f);
+
+               titleItem.setFont(futureFont);
+               Color current = titleItem.getForeground();
+               Color future = current.brighter().brighter();
+               // titleItem.setBackground(future);
+               titleItem.setForeground(future);
+               add(titleItem);
+               add(sep);
+               if (!keep) {
+                       _disabled.add(sep);
+                       _disabled.add(titleItem);
+               }
+       }
+       
+       private void configMenuItem(JMenuItem mi, String command, String keyStroke, Container par)
+       { configMenuItem(mi,command,keyStroke,par,false); }
+
+       private void configMenuItem(JMenuItem mi, String command, String keyStroke, Container par, boolean disabled)
+       { 
+               mi.setActionCommand(command);
+               mi.addActionListener(_controlerMenu);
+               if (keyStroke!=null)
+                       if (!keyStroke.equals(""))
+                               mi.setAccelerator(KeyStroke.getKeyStroke(keyStroke));
+               if (disabled)
+               { _disabled.add(mi);}
+               par.add(mi);
+       }
+       
+       private JMenuItem createMenuItem(String caption, String command, String keyStroke, Container par, boolean disabled)
+       {
+               JMenuItem mi = new JMenuItem(caption);
+               configMenuItem(mi, command,keyStroke, par, disabled);
+               return mi;
+       }
+
+       private JMenuItem createMenuItem(String caption, String command, String keyStroke, Container par)
+       { return createMenuItem(caption, command, keyStroke, par,false); }
+
+
+       public void updateDialog() {
+               for (int i = 0; i < _disabled.size(); i++) {
+                       JComponent j = _disabled.get(i);
+                       j.setVisible(_vp.isModifiable());
+               }
+               _itemOptionSpecialBaseColored.setState(_vp.getColorSpecialBases());
+               _itemShowWarnings.setState(_vp.getShowWarnings());
+               _itemOptionGapsBaseColored.setState(_vp.getColorGapsBases());
+               _itemOptionGapsBaseColored.setEnabled(_vp.isComparisonMode());
+               _dashBasesColor.setEnabled(_vp.isComparisonMode());
+               
+               _rotation.setEnabled(_vp.getDrawMode() != RNA.DRAW_MODE_LINEAR);
+               _bpHeightIncrement.setEnabled(_vp.getDrawMode() == RNA.DRAW_MODE_LINEAR);
+
+               _itemOptionBondsColored.setState(_vp.getUseBaseColorsForBPs());
+               _itemShowNCBP.setState(_vp.getShowNonCanonicalBP());
+               _itemShowOnlyPlanar.setState(!_vp.getShowNonPlanarBP());
+               _itemShowColorMap.setState(_vp.getColorMapVisible());
+               _itemFlatExteriorLoop.setState(_vp.getFlatExteriorLoop());
+               _itemFlatExteriorLoop.setEnabled(_vp.getDrawMode() == RNA.DRAW_MODE_RADIATE);
+       }
+
+       /**
+        * Builds the popup menu
+        */
+       public void buildPopupMenu() {
+               addTitle("File", true);
+               fileMenu();
+               exportMenu();
+               createMenuItem("Print...", "print", "control P", this);
+               addSeparator();
+
+               addTitle("Display", true);
+               viewMenu();
+               displayMenu();
+               //JSeparator sep = new JSeparator();
+               // TODO BH SwingJS - this should not be necessary
+               JSeparator sep = new JPopupMenu.Separator(); // BH SWingJS needs JPopupMenu.Separator
+               add(sep);
+               _disabled.add(sep);
+
+               addTitle("Edit", false);
+               editRNAMenu();
+               redrawMenu();
+               colorClassesMenu();
+               annotationMenu();
+               _disabled.add(_itemShowNCBP);
+               _disabled.add(_itemShowOnlyPlanar);
+               aboutMenu();
+       }
+
+       private void annotationMenu() {
+               JMenu submenuAnnotations = new JMenu("Annotations");
+               JMenu addAnnotations = new JMenu("New");
+               createMenuItem("Here", "annotationsaddPosition", "", addAnnotations);
+               createMenuItem("Base", "annotationsaddBase", "", addAnnotations);
+               createMenuItem("Loop", "annotationsaddLoop", "", addAnnotations);
+               createMenuItem("Helix", "annotationsaddHelix", "", addAnnotations);
+               //JSeparator sep = new JSeparator();
+               JSeparator sep = new JPopupMenu.Separator(); // BH SWingJS needs JPopupMenu.Separator
+
+               addAnnotations.add(sep);
+               createMenuItem("Region", "annotationsaddRegion", "", addAnnotations);
+               createMenuItem("Chem. prob.", "annotationsaddChemProb", "", addAnnotations);
+               submenuAnnotations.add(addAnnotations);
+               createMenuItem("Edit from list...", "annotationsedit", "", submenuAnnotations);
+               createMenuItem("Remove from list...", "annotationsremove", "", submenuAnnotations);
+               submenuAnnotations.addSeparator();
+               createMenuItem("Auto 5'/3'", "annotationsautoextremites", "control alt Q", submenuAnnotations);
+               createMenuItem("Auto helices", "annotationsautohelices", "control Q", submenuAnnotations);
+               createMenuItem("Auto interior loops", "annotationsautointerior", "alt shift Q", submenuAnnotations);
+               createMenuItem("Auto terminal loops", "annotationsautoterminal", "control shift Q", submenuAnnotations);
+               add(submenuAnnotations);
+       }
+
+       private void fileMenu() {
+               createMenuItem("New...", "userInput", "control N", this,true);
+               createMenuItem("Open...", "file", "control O", this,true);
+               createMenuItem("Save...", "saveas", "control S", this,true);
+               JMenu submenuSave = new JMenu("Save as");
+               createMenuItem("DBN (Vienna)", "dbn", "", submenuSave);
+               createMenuItem("BPSEQ", "bpseq", "", submenuSave);
+               createMenuItem("CT", "ct", "", submenuSave);
+               add(submenuSave);
+       }
+
+       private void exportMenu() {
+               // Export menu
+               JMenu submenuExport = new JMenu("Export");
+               createMenuItem("SVG", "svg", "", submenuExport);
+               createMenuItem("PGF/TIKZ", "tikz", "", submenuExport);
+               createMenuItem("XFIG", "xfig", "", submenuExport);
+               submenuExport.addSeparator();
+               createMenuItem("EPS", "eps", "", submenuExport);
+               submenuExport.addSeparator();
+               createMenuItem("PNG", "png", "", submenuExport);
+               createMenuItem("JPEG", "jpeg", "", submenuExport);
+               add(submenuExport);
+       }
+
+
+       private void displayMenu() {
+               
+               // SubMenu Base-pairs
+               JMenu subMenuBasePairs = new JMenu("Base Pairs");
+               createMenuItem("BP style...", "bpstyle", "control shift P", subMenuBasePairs);
+               configMenuItem(_itemShowNCBP, "shownc", "control W", subMenuBasePairs);
+               configMenuItem(_itemShowOnlyPlanar, "shownp", "control E", subMenuBasePairs);
+               // SubMenu Non standard Bases
+               JMenu subMenuNSBases = new JMenu("Non-standard bases");
+               configMenuItem(_itemOptionSpecialBaseColored, "specialbasecolored", "control J", subMenuNSBases);
+               createMenuItem("Color", "specialBasesColor", "control shift J", subMenuNSBases);
+               // SubMenu Gaps Bases
+               JMenu subMenuGapsBases = new JMenu("'Gaps' bases");
+               configMenuItem(_itemOptionGapsBaseColored, "dashbasecolored", "control D", subMenuGapsBases);           
+               _dashBasesColor = createMenuItem("Color", "dashBasesColor", "control shift D", subMenuGapsBases);
+               // Removable separator 
+               //JSeparator sep = new JSeparator();
+               JSeparator sep = new JPopupMenu.Separator(); // BH SWingJS needs JPopupMenu.Separator
+               _disabled.add(sep);
+               
+               // Style menu
+               JMenu submenuStyle = new JMenu("RNA style");
+               createMenuItem("Toggle draw bases", "gaspin", "alt G", submenuStyle,true);
+               submenuStyle.add(subMenuBasePairs);
+               submenuStyle.addSeparator();
+               submenuStyle.add(subMenuNSBases);
+               submenuStyle.add(subMenuGapsBases);
+               submenuStyle.add(sep);
+               createMenuItem("Backbone color", "backbone", "control K", submenuStyle,true);
+               configMenuItem(_itemDrawBackbone, "showbackbone", "alt B", submenuStyle);
+               
+               // Submenu Title
+               JMenu submenuTitle = new JMenu("Title");                
+               createMenuItem("Set Title", "setTitle", "control T", submenuTitle, true);
+               createMenuItem("Font", "titleDisplay", "control shift T", submenuTitle, true);
+               createMenuItem("Color", "titleColor", "control alt T", submenuTitle, true);
+               _disabled.add(submenuTitle);
+
+               // Color map menu
+               JMenu submenuColorMap = new JMenu("Color map");
+               configMenuItem(_itemShowColorMap, "toggleshowcolormap", "control shift L", submenuColorMap, false);
+               createMenuItem("Caption", "colormapcaption", "control shift C", submenuColorMap,true);
+               createMenuItem("Style...", "colormapstyle", "control L", submenuColorMap,false);
+               submenuColorMap.addSeparator();
+               createMenuItem("Edit values...", "colormapvalues", "shift L", submenuColorMap,true);
+               createMenuItem("Load values...", "colormaploadvalues", "control shift K", submenuColorMap,true);
+               _disabled.add(submenuColorMap);
+               
+               // Menu Misc
+               JMenu submenuMisc = new JMenu("Misc");
+               createMenuItem("Num. period.", "numPeriod", "control M", submenuMisc);
+               createMenuItem("Background color", "background", "control G", submenuMisc);
+               submenuMisc.add(submenuTitle);
+               
+               // Main menu
+               add(submenuStyle);              
+               add(submenuColorMap);
+               add(submenuMisc);
+
+       }
+       
+       private void editRNAMenu()
+       {
+               createMenuItem("Bases...","editallbases","",this,true);
+               createMenuItem("BasePairs...","editallbps","",this,true);
+       }
+
+       private void redrawMenu() {
+               JMenu submenuRedraw = new JMenu("Redraw");
+               _disabled.add(submenuRedraw);
+
+               JMenu submenuAlgorithms = new JMenu("Algorithm");
+               _disabled.add(submenuAlgorithms);
+
+               createMenuItem("Linear","line","control 1",submenuAlgorithms,true);
+               createMenuItem("Circular","circular","control 2",submenuAlgorithms,true);
+               createMenuItem("Radiate","radiate","control 3",submenuAlgorithms,true);
+               createMenuItem("NAView","naview","control 4",submenuAlgorithms,true);
+               //createMenuItem("VARNAView","varnaview","control 5",submenuAlgorithms,true);
+               //createMenuItem("MOTIFView","motifview","control 6",submenuAlgorithms,true);
+               submenuRedraw.add(submenuAlgorithms);
+               
+               // Sets the height increment in LINEAR_MODE type of drawing
+               _bpHeightIncrement = createMenuItem("BP height increment","bpheightincrement","control H",submenuRedraw);
+               configMenuItem(_itemFlatExteriorLoop, "flat", "control F", submenuRedraw, true);
+
+               // Item pour le r�glage de l'espace entre chaques bases
+               createMenuItem("Space between bases","spaceBetweenBases","control shift S",submenuRedraw,true);         
+               createMenuItem("Reset","reset","control shift R",submenuRedraw,true);           
+
+               add(submenuRedraw);
+       }
+
+       @SuppressWarnings("unused")
+       private void warningMenu() {
+               // Menu showWarning
+               configMenuItem(_itemShowWarnings, "showwarnings", "", this, true);
+       }
+
+       private void viewMenu() {
+               // View menu
+               JMenu submenuView = new JMenu("View");
+
+               // Zoom submenu
+               JMenu zoomDisplay = new JMenu("Zoom");
+               createMenuItem("25%","zoom25","",zoomDisplay);
+               createMenuItem("50%","zoom50","",zoomDisplay);
+               createMenuItem("100%","zoom100","",zoomDisplay);
+               createMenuItem("150%","zoom150","",zoomDisplay);
+               createMenuItem("200%","zoom200","",zoomDisplay);
+               createMenuItem("Custom","zoom","control Z",zoomDisplay);
+               submenuView.add(zoomDisplay);           
+               _rotation = createMenuItem("Rotation...","rotation","control R",submenuView);
+               createMenuItem("Rescale...","rescale","",submenuView);
+               submenuView.addSeparator();             
+               createMenuItem("Border size","borderSize","control B",submenuView);
+               
+               add(submenuView);
+
+       }
+
+       JMenu _subMenuBases;
+
+       private Component _selectionMenuIndex = null;
+
+       public void addSelectionMenu(JMenuItem s) {
+               _selectionMenuIndex = s;
+               _disabled.add(s);
+               insert(s, getComponentCount() - 2);
+       }
+
+       public void removeSelectionMenu() {
+               if (_selectionMenuIndex != null) {
+                       this.remove(_selectionMenuIndex);
+                       _selectionMenuIndex = null;
+               }
+       }
+
+       private void colorClassesMenu() {
+               // Menu Bases
+               _subMenuBases = new JMenu("Colors");
+               _disabled.add(_subMenuBases);
+               createMenuItem("By Base","eachKind","control U",_subMenuBases,true);
+               createMenuItem("By BP","eachCouple","shift U",_subMenuBases,true);
+               createMenuItem("By Position","eachBase","alt U",_subMenuBases,true);
+               add(_subMenuBases);
+       }
+
+       /**
+        * add default color options to a menu
+        */
+       public void addColorOptions(JMenu submenu) {
+               createMenuItem("Fill Color",submenu.getActionCommand() + ",InnerColor","",submenu,true);
+               createMenuItem("Stroke Color",submenu.getActionCommand() + ",OutlineColor","",submenu,true);
+               createMenuItem("Label Color",submenu.getActionCommand() + ",NameColor","",submenu,true);                
+               submenu.addSeparator();
+               createMenuItem("BP Color",submenu.getActionCommand() + ",BPColor","",submenu,true);             
+               createMenuItem("BP Thickness",submenu.getActionCommand() + ",BPThickness","",submenu,true);             
+               submenu.addSeparator();
+               createMenuItem("Number Color",submenu.getActionCommand() + ",NumberColor","",submenu,true);             
+       }
+
+       private void aboutMenu() {
+               addSeparator();
+               createMenuItem("About VARNA", "about", "control A", this);
+       }
+
+       public void addAnnotationMenu(JMenu menu) {
+               addAnnotationMenu(menu, false);
+       }       
+       
+       public void addAnnotationMenu(JMenu menu, boolean existingAnnot) {
+               String title = "Annotation";
+               if (existingAnnot)
+               {
+                       String debut = "";
+                       String texte = _vp.get_selectedAnnotation().getTexte();
+                       if (texte.length() < 5)
+                               debut = texte;
+                       else
+                               debut = texte.substring(0, 5) + "...";
+                       title = "Annotation: " + debut;                 
+               }
+               JMenu menuAnnotation = new JMenu(title);
+               if (!existingAnnot)
+                       createMenuItem("Add",menu.getActionCommand() + "annotationadd","",menuAnnotation,true);         
+               createMenuItem("Edit",menu.getActionCommand() + "annotationedit","",menuAnnotation,true);               
+               createMenuItem("Remove",menu.getActionCommand() + "annotationremove","",menuAnnotation,true);           
+               menu.add(menuAnnotation);
+       }       
+       
+       
+       public static long getSerialVersionUID() {
+               return serialVersionUID;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public ControleurMenu get_controleurMenu() {
+               return _controlerMenu;
+       }
+
+       public JCheckBoxMenuItem get_itemOptionSpecialBaseColored() {
+               return _itemOptionSpecialBaseColored;
+       }
+
+       public JCheckBoxMenuItem get_itemShowWarnings() {
+               return _itemShowWarnings;
+       }
+
+       public JCheckBoxMenuItem get_itemOptionDashBaseColored() {
+               return _itemOptionGapsBaseColored;
+       }
+
+       public void set_controleurMenu(ControleurMenu menu) {
+               _controlerMenu = menu;
+       }
+
+       public void set_itemOptionSpecialBaseColored(
+                       JCheckBoxMenuItem optionSpecialBaseColored) {
+               _itemOptionSpecialBaseColored = optionSpecialBaseColored;
+       }
+
+       public void set_itemShowWarnings(JCheckBoxMenuItem showWarnings) {
+               _itemShowWarnings = showWarnings;
+       }
+
+       public void set_itemOptionDashBaseColored(
+                       JCheckBoxMenuItem optionDashBaseColored) {
+               _itemOptionGapsBaseColored = optionDashBaseColored;
+       }
+
+       public JMenuItem get_rotation() {
+               return _rotation;
+       }
+
+       public void set_rotation(JMenuItem _rotation) {
+               this._rotation = _rotation;
+       }
+
+       public JCheckBoxMenuItem get_itemOptionBondsColored() {
+               return _itemOptionBondsColored;
+       }
+
+       public void set_itemOptionBondsColored(JCheckBoxMenuItem optionBondsColored) {
+               _itemOptionBondsColored = optionBondsColored;
+       }
+
+
+       public void show(Component invoker,int x,int y) {
+                _spawnOrigin = new Point(x,y);
+                super.show(invoker,x,y); 
+        }
+       
+       public Point getSpawnPoint()
+       {
+               return _spawnOrigin ;
+       }
+
+}
\ No newline at end of file
diff --git a/srcjar/fr/orsay/lri/varna/views/VueNumPeriod.java b/srcjar/fr/orsay/lri/varna/views/VueNumPeriod.java
new file mode 100644 (file)
index 0000000..e092304
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurNumPeriod;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+
+public class VueNumPeriod {
+       private VARNAPanel _vp;
+       private JPanel panel;
+       private JSlider numPeriodSlider;
+
+       public VueNumPeriod(VARNAPanel vp) {
+               _vp = vp;
+               panel = new JPanel();
+
+               int maxPeriod = _vp.getRNA().get_listeBases().size();
+               numPeriodSlider = new JSlider(JSlider.HORIZONTAL, 1, maxPeriod, Math
+                               .min(_vp.getNumPeriod(), maxPeriod));
+               // Turn on labels at major tick marks.
+               numPeriodSlider.setMajorTickSpacing(10);
+               numPeriodSlider.setMinorTickSpacing(5);
+               numPeriodSlider.setPaintTicks(true);
+               numPeriodSlider.setPaintLabels(true);
+
+               JLabel numLabel = new JLabel(String.valueOf(_vp.getNumPeriod()));
+               numLabel.setPreferredSize(new Dimension(50,
+                               numLabel.getPreferredSize().height));
+               numPeriodSlider.addChangeListener(new ControleurSliderLabel(numLabel,
+                               false));
+               numPeriodSlider.addChangeListener(new ControleurNumPeriod(this));
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelS = new JLabel("NumPeriod:");
+
+               panel.add(labelS);
+               panel.add(numPeriodSlider);
+               panel.add(numLabel);
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public int getNumPeriod() {
+               return numPeriodSlider.getValue();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueRNAList.java b/srcjar/fr/orsay/lri/varna/views/VueRNAList.java
new file mode 100644 (file)
index 0000000..1fc7e28
--- /dev/null
@@ -0,0 +1,195 @@
+package fr.orsay.lri.varna.views;
+
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.components.ColorRenderer;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleColorMap;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+public class VueRNAList extends JPanel implements TableModelListener, ActionListener {
+
+       private JTable table;
+       private ValueTableModel _tm;
+       private ArrayList<RNA> data;
+       private ArrayList<Object> columns;
+       private ArrayList<Boolean> included;
+       
+       
+       public VueRNAList( ArrayList<RNA> rnas)
+       {
+               super(new BorderLayout());
+               data = rnas;
+               init();
+       }
+       
+       public ArrayList<RNA> getSelectedRNAs()
+       {
+               ArrayList<RNA> result = new ArrayList<RNA>();
+               for (int i = 0; i < data.size(); i++)
+               {
+                       if (included.get(i))
+                       {
+                               result.add(data.get(i));
+                       }
+               }               
+               return result;
+       }
+       
+       private void init()
+       {
+               Object[] col = {"Num","Selected","Name","ID","Length"};
+               columns = new ArrayList<Object>();
+               for (int i = 0; i < col.length; i++)
+               {
+                       columns.add(col[i]);
+               }
+               included = new ArrayList<Boolean>();
+               for (int i = 0; i < data.size(); i++)
+               {
+                       included.add(new Boolean(true));
+               }
+               
+               
+               _tm = new ValueTableModel();
+               table = new JTable(_tm);
+               table.setDefaultRenderer(Color.class, new ColorRenderer(true)); 
+               table.setPreferredScrollableViewportSize(new Dimension(600, 300));
+               table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+               TableColumn c0 = table.getColumnModel().getColumn(0);
+               c0.setPreferredWidth(30);
+               TableColumn c1 = table.getColumnModel().getColumn(1);
+               c1.setPreferredWidth(30);
+               TableColumn c2 = table.getColumnModel().getColumn(2);
+               c2.setPreferredWidth(200);
+               TableColumn c3 = table.getColumnModel().getColumn(3);
+               c3.setPreferredWidth(200);
+               TableColumn c4 = table.getColumnModel().getColumn(4);
+               c4.setPreferredWidth(30);
+               table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+               table.getModel().addTableModelListener(this);
+               
+               JScrollPane scrollPane = new JScrollPane(table);
+               this.add(scrollPane,BorderLayout.CENTER);
+               JPanel jp = new JPanel();
+               JPanel jpl = new JPanel();
+               JPanel jpr = new JPanel();
+               jp.setLayout(new BorderLayout());
+               jp.add(jpl,BorderLayout.WEST);
+               jp.add(jpr,BorderLayout.EAST);
+               jp.add(new JLabel("Please select which model(s) should be imported." ),BorderLayout.SOUTH);
+               JButton selectAll = new JButton("Select All");
+               selectAll.addActionListener(this);
+               selectAll.setActionCommand("all");
+               JButton deselectAll = new JButton("Deselect All");
+               deselectAll.addActionListener(this);
+               deselectAll.setActionCommand("none");
+               jpl.add(selectAll);
+               jpr.add(deselectAll);
+               
+               add(scrollPane,BorderLayout.CENTER);
+               add(jp,BorderLayout.SOUTH);
+               }
+       
+
+
+       private class ValueTableModel extends AbstractTableModel {
+           public String getColumnName(int col) {
+               return columns.get(col).toString();
+           }
+           public int getRowCount() { return data.size(); }
+           public int getColumnCount() { return columns.size(); }
+           public Object getValueAt(int row, int col) {
+               RNA r = data.get(row);
+               if (col==0)
+               {
+                       return new Integer(row+1);
+               }
+               else if (col==1)
+               {
+                       return new Boolean(included.get(row));
+               }
+               else if (col==2)
+               {
+                       return new String(r.getName());
+               } 
+               else if (col==3)
+               {
+                       return new String(r.getID());
+               } 
+               else if (col==4)
+               {
+                       return new Integer(r.getSize());
+               } 
+               return "N/A";
+           }
+           public boolean isCellEditable(int row, int col)
+               { 
+                       if (col==1) 
+                               return true;
+                       return false;
+               }
+           public void setValueAt(Object value, int row, int col) {
+               if (col==1)
+               {
+                 included.set(row, (Boolean)value);
+                 fireTableCellUpdated(row, col);
+               }
+           }
+           public Class getColumnClass(int c) {
+               return getValueAt(0, c).getClass();
+           }
+       }
+
+       public void tableChanged(TableModelEvent e) {
+               if (e.getType() == TableModelEvent.UPDATE)
+               {
+                       table.repaint();
+               }
+               
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               if (e.getActionCommand().equals("none"))
+               {
+                       for(int i=0;i<this.included.size();i++)
+                       {
+                               included.set(i, false);
+                       }
+                       _tm.fireTableRowsUpdated(0, included.size()-1);
+               }
+               else if (e.getActionCommand().equals("all"))
+               {
+                       for(int i=0;i<this.included.size();i++)
+                       {
+                               included.set(i, true);
+                       }
+                       _tm.fireTableRowsUpdated(0, included.size()-1);
+               }
+       }
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueSpaceBetweenBases.java b/srcjar/fr/orsay/lri/varna/views/VueSpaceBetweenBases.java
new file mode 100644 (file)
index 0000000..0492b55
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+import fr.orsay.lri.varna.controlers.ControleurSpaceBetweenBases;
+
+public class VueSpaceBetweenBases {
+       private VARNAPanel _vp;
+       private JPanel panel;
+       private JSlider spaceSlider;
+
+       public VueSpaceBetweenBases(VARNAPanel vp) {
+               _vp = vp;
+               panel = new JPanel();
+
+               spaceSlider = new JSlider(JSlider.HORIZONTAL, 10, 200, Integer
+                               .valueOf(String.valueOf(Math.round(_vp.getSpaceBetweenBases() * 100))));
+               // Turn on labels at major tick marks.
+               spaceSlider.setMajorTickSpacing(30);
+               spaceSlider.setPaintTicks(true);
+               spaceSlider.setPaintLabels(true);
+
+               JLabel spaceLabel = new JLabel(String.valueOf(100.0 * _vp.getSpaceBetweenBases()));
+               spaceLabel.setPreferredSize(new Dimension(50, spaceLabel
+                               .getPreferredSize().height));
+               spaceSlider.addChangeListener(new ControleurSliderLabel(spaceLabel,
+                               false));
+               spaceSlider.addChangeListener(new ControleurSpaceBetweenBases(this));
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelS = new JLabel("Space:");
+
+               panel.add(labelS);
+               panel.add(spaceSlider);
+               panel.add(spaceLabel);
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public Double getSpace() {
+               return spaceSlider.getValue() / 100.0;
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueStyleBP.java b/srcjar/fr/orsay/lri/varna/views/VueStyleBP.java
new file mode 100644 (file)
index 0000000..a0876d6
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.models.VARNAConfig;
+
+
+public class VueStyleBP implements ActionListener {
+
+       private VARNAPanel _vp;
+       private JComboBox _cmb;
+       private JPanel panel;
+
+       public VueStyleBP(VARNAPanel vp) {
+               _vp = vp;
+               VARNAConfig.BP_STYLE[] styles = VARNAConfig.BP_STYLE.values();
+               VARNAConfig.BP_STYLE bck = vp.getConfig()._mainBPStyle;
+               _cmb = new JComboBox(styles);
+               for (int i = 0; i < styles.length; i++) {
+                       if (styles[i] == bck)
+                               _cmb.setSelectedIndex(i);
+               }
+               _cmb.addActionListener(this);
+
+               panel = new JPanel();
+               panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               JLabel labelZ = new JLabel("Base pair style: ");
+
+               panel.add(labelZ);
+               panel.add(_cmb);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public VARNAConfig.BP_STYLE getStyle() {
+               return (VARNAConfig.BP_STYLE) _cmb.getSelectedItem();
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               VARNAConfig.BP_STYLE newSel = (VARNAConfig.BP_STYLE) _cmb
+                               .getSelectedItem();
+               _vp.setBPStyle(newSel);
+               _vp.repaint();
+       }
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueUI.java b/srcjar/fr/orsay/lri/varna/views/VueUI.java
new file mode 100644 (file)
index 0000000..e8d7fd1
--- /dev/null
@@ -0,0 +1,1932 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.FileImageOutputStream;
+import javax.swing.JColorChooser;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.plaf.UIResource;
+import javax.swing.undo.UndoManager;
+import javax.swing.undo.UndoableEditSupport;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.applications.FileNameExtensionFilter;
+import fr.orsay.lri.varna.applications.VARNAPrinter;
+import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionJPEGEncoding;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm;
+import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
+import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
+import fr.orsay.lri.varna.factories.RNAFactory;
+import fr.orsay.lri.varna.models.FullBackup;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.VARNAEdits;
+import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation;
+import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
+import fr.orsay.lri.varna.models.annotations.TextAnnotation;
+import fr.orsay.lri.varna.models.rna.ModeleBP;
+import fr.orsay.lri.varna.models.rna.ModeleBase;
+import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide;
+import fr.orsay.lri.varna.models.rna.ModeleBasesComparison;
+import fr.orsay.lri.varna.models.rna.RNA;
+
+
+public class VueUI {
+       
+       /**
+        *
+        * BH SwingJS
+        * 
+        *      JavaScript cannot wait on a thread and so cannot wait for the result
+        *      of a customized modal JOptionPane.
+        * 
+        *      Instead, we make any JPanel that is placed in the JOptionPane
+        *      implement Runnable. In Java, we simply run that runnable here when an OK
+        *      response is delivered; in JavaScript, we run the runnable within the
+        *      JavaScript version of JOptionPane as an asynchronous callback.
+        *      
+        *      Same for a CANCEL, CLOSED, ERROR, or FINAL response.
+        *      
+        *      Note that for simple error or warning messages, this is not required; they will use simple HTML5 messages.
+        * 
+        */
+   public Runnable okBtnCallback, cancelBtnCallback, closeBtnCallback, errorCallback, finalCallback, noBtnCallback, objectCallback;
+
+   public Object dialogReturnValue;
+       
+   public Object getDialogReturnValue() {
+               return dialogReturnValue;
+       }
+
+       public Throwable dialogError;
+       
+       /**
+        * BH SwingJS
+        * 
+        * 
+        * @return
+        */
+       public Throwable getDialogError() {
+               return dialogError;
+       }
+
+
+       /**
+        * BH SwingJS
+        * 
+        * Initiate a message dialog with callback for OK and close.
+        * 
+        * @param messagePanel
+        * @param title
+        * @param ok
+        * @param close
+        */
+       private void showMessageDialog(Object messagePanel, String title, int messageType, Runnable ok, Runnable close) {
+               okBtnCallback = ok;
+               closeBtnCallback = close;
+               JOptionPane.showMessageDialog(_vp, messagePanel, title, messageType);
+       }
+
+
+
+
+       /**
+        * BH SwingJS
+        * 
+        * Initiate a confirm dialog with callbacks.
+        * 
+        * @param optionPanel
+        * @param title
+        * @param ok
+        * @param cancel
+        * @param close_final_error optional close,finally,error
+        */
+       public void showConfirmDialog(JPanel optionPanel, String title, Runnable ok, Runnable cancel, Runnable... close_final_error) {
+               okBtnCallback = ok;
+               cancelBtnCallback = cancel;
+               closeBtnCallback = (close_final_error.length > 0 ? close_final_error[0] : null);
+               finalCallback = (close_final_error.length > 1 ? close_final_error[1] : null);
+               errorCallback = (close_final_error.length > 2 ? close_final_error[2] : null);           
+               onDialogReturn(JOptionPane.showConfirmDialog(_vp, optionPanel, title, JOptionPane.OK_CANCEL_OPTION));
+       }
+
+       /**
+        * BH SwingJS
+        * 
+        * Initiate an input dialog with callbacks.
+        * 
+        * @param message
+        * @param initialValue
+        * @param input
+        * @param close_final_error optional [close,finally,error]
+        */
+       public void showInputDialog(String message, Object initialValue, Runnable input, Runnable... close_final_error) {
+               objectCallback = input;
+               closeBtnCallback = (close_final_error.length > 0 ? close_final_error[0] : null);
+               finalCallback = (close_final_error.length > 1 ? close_final_error[1] : null);
+               errorCallback = (close_final_error.length > 2 ? close_final_error[2] : null);           
+               onDialogReturn(JOptionPane.showInputDialog(_vp, message, initialValue));
+       }
+
+       /**
+        * BH SwingJS
+        * 
+        * Initiate an color chooser dialog with callbacks.
+        * 
+        * @param message
+        * @param initialValue
+        * @param input
+        * @param close_final_error optional [close,finally,error]
+        */
+       public void showColorDialog(String message, Object initialValue, Runnable ret) {
+               objectCallback = ret;
+               onDialogReturn(JColorChooser.showDialog(_vp, message, (Color) initialValue));
+       }
+
+       
+       /**
+        * BH SwingJS
+        * 
+        * A general method to handle all the showInputDialog, JFileChooser, and JColorChooser callbacks from all the VueXXX classes.
+        * 
+        * The initial return to be ignored is an object that is an instanceof UIResource.
+        * 
+        */
+       public void onDialogReturn(Object value) {
+               dialogReturnValue = value;
+               if (objectCallback != null && !(value instanceof UIResource))
+                       objectCallback.run();
+       }
+       
+       /**
+        * BH SwingJS
+        * 
+        * A general method to handle all the showConfirmDialog callbacks from all
+        * the VueXXX classes.
+        * 
+        * The initial return to be ignored is NaN, testable as 
+        * value != Math.floor(value).
+        * 
+        */
+       public void onDialogReturn(int value) {
+               try {
+                       switch (value) {
+                       case JOptionPane.OK_OPTION | JOptionPane.YES_OPTION:
+                               if (okBtnCallback != null)
+                                       okBtnCallback.run();
+                               break;
+                       case JOptionPane.NO_OPTION:
+                               if (noBtnCallback != null)
+                                       noBtnCallback.run();
+                               break;
+                       case JOptionPane.CANCEL_OPTION:
+                               if (cancelBtnCallback != null)
+                                       cancelBtnCallback.run();
+                               break;
+                       case JOptionPane.CLOSED_OPTION:
+                               if (closeBtnCallback != null)
+                                       closeBtnCallback.run();
+                               break;
+                       }
+               } catch (Throwable e) {
+                       dialogError = e;
+                       if (errorCallback != null)
+                               errorCallback.run();
+               } finally {
+                       if (value != Math.floor(value)) {
+                               // asynchronous deferred
+                               return;
+                       }
+                       if (finalCallback != null)
+                               finalCallback.run();
+                       okBtnCallback = noBtnCallback = cancelBtnCallback = closeBtnCallback = errorCallback = objectCallback = null;
+                       dialogError = null;
+               }
+       }
+       
+       
+    protected VARNAPanel _vp;
+       private File _fileChooserDirectory = null;
+       private UndoableEditSupport _undoableEditSupport;
+
+       public VueUI(VARNAPanel vp) {
+               _vp = vp;
+               _undoableEditSupport = new UndoableEditSupport(_vp);
+       }
+
+       public void addUndoableEditListener(UndoManager manager) {
+               _undoableEditSupport.addUndoableEditListener(manager);
+       }
+
+       public void UIToggleColorMap() {
+               if (_vp.isModifiable()) {
+                       _vp.setColorMapVisible(!_vp.getColorMapVisible());
+                       _vp.repaint();
+               }
+       }
+
+       public void UIToggleDrawBackbone() {
+               if (_vp.isModifiable()) {
+                       _vp.setDrawBackbone(!_vp.getDrawBackbone());
+                       _vp.repaint();
+               }
+       }
+
+       public Hashtable<Integer, Point2D.Double> backupAllCoords() {
+               Hashtable<Integer, Point2D.Double> tmp = new Hashtable<Integer, Point2D.Double>();
+               for (int i = 0; i < _vp.getRNA().getSize(); i++) {
+                       tmp.put(i, _vp.getRNA().getCoords(i));
+               }
+               return tmp;
+       }
+
+       public void UIToggleFlatExteriorLoop() {
+               if (_vp.isModifiable()
+                               && _vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_RADIATE, _vp, !_vp.getFlatExteriorLoop()));
+                       _vp.setFlatExteriorLoop(!_vp.getFlatExteriorLoop());
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_RADIATE);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UIRadiate() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_RADIATE, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_RADIATE);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UIMOTIFView() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_MOTIFVIEW, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_MOTIFVIEW);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UILine() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_LINEAR, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_LINEAR);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UICircular() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_CIRCULAR, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_CIRCULAR);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UINAView() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_NAVIEW, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_NAVIEW);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UIVARNAView() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(
+                                       RNA.DRAW_MODE_VARNA_VIEW, _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), RNA.DRAW_MODE_VARNA_VIEW);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       public void UIReset() {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> bck = backupAllCoords();
+                       _undoableEditSupport.postEdit(new VARNAEdits.RedrawEdit(_vp
+                                       .getRNA().get_drawMode(), _vp));
+                       _vp.reset();
+                       _vp.drawRNA(_vp.getRNA(), _vp.getRNA().get_drawMode());
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(bck);
+               }
+       }
+
+       private void savePath(JFileChooser jfc) {
+               _fileChooserDirectory = jfc.getCurrentDirectory();
+       }
+
+       private void loadPath(JFileChooser jfc) {
+               if (_fileChooserDirectory != null) {
+                       jfc.setCurrentDirectory(_fileChooserDirectory);
+               }
+       }
+
+       public void UIChooseRNAs(ArrayList<RNA> rnas) {
+               if (rnas.size() > 5) {
+                       final VueRNAList vrna = new VueRNAList(rnas);
+                       Runnable ok = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       for (RNA r : vrna.getSelectedRNAs()) {
+                                               try {
+                                                       r.drawRNA(_vp.getConfig());
+                                               } catch (ExceptionNAViewAlgorithm e) {
+                                                       e.printStackTrace();
+                                               }
+                                               _vp.showRNA(r);
+                                       }
+                                       _vp.repaint();
+                               }
+
+                       };
+                       showConfirmDialog(vrna, "Select imported sequence/structures", ok, null);
+               } else {
+                       for (RNA r : rnas) {
+                               try {
+                                       r.drawRNA(_vp.getConfig());
+                               } catch (ExceptionNAViewAlgorithm e) {
+                                       e.printStackTrace();
+                               }
+                               _vp.showRNA(r);
+                       }
+                       _vp.repaint();
+               }
+       }
+
+       public void UIFile() throws ExceptionNonEqualLength {
+               if (_vp.isModifiable()) {
+                       JFileChooser fc = new JFileChooser();
+                       fc.setFileSelectionMode(JFileChooser.OPEN_DIALOG);
+                       fc.setDialogTitle("Open...");
+                       loadPath(fc);
+                       if (fc.showOpenDialog(_vp) == JFileChooser.APPROVE_OPTION) {
+                               try {
+                                       savePath(fc);
+                                       String path = fc.getSelectedFile().getAbsolutePath();
+                                       if (!path.toLowerCase().endsWith(".varna")) {
+                                               ArrayList<RNA> rnas = RNAFactory.loadSecStr(path);
+                                               if (rnas.isEmpty()) {
+                                                       throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from that source.");
+                                               } else {
+                                                       UIChooseRNAs(rnas);
+                                               }
+                                       } else {
+                                               FullBackup bck = _vp.loadSession(fc.getSelectedFile()); // was path
+                                       }
+                               } catch (ExceptionExportFailed e1) {
+                                       _vp.errorDialog(e1);
+                               } catch (ExceptionPermissionDenied e1) {
+                                       _vp.errorDialog(e1);
+                               } catch (ExceptionLoadingFailed e1) {
+                                       _vp.errorDialog(e1);
+                               } catch (ExceptionFileFormatOrSyntax e1) {
+                                       _vp.errorDialog(e1);
+                               } catch (ExceptionUnmatchedClosingParentheses e1) {
+                                       _vp.errorDialog(e1);
+                               } catch (FileNotFoundException e) {
+                                       _vp.errorDialog(e);
+                               }
+                       }
+               }
+       }
+
+       public void UISetColorMapStyle() {
+               final VueColorMapStyle vcms = new VueColorMapStyle(_vp);
+               Runnable ok = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               _vp.setColorMap(vcms.getColorMap());
+                       }
+                       
+               };
+               Runnable cancel = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               vcms.cancelChanges();
+                       }
+                                               
+               };
+               showConfirmDialog(vcms, "Choose color map style", ok, cancel, cancel, null, null);
+       }
+
+       public void UILoadColorMapValues() {
+               final VueLoadColorMapValues vcmv = new VueLoadColorMapValues(_vp);
+               Runnable ok = new Runnable(){
+                       
+                       @Override
+                       public void run() {
+                               _vp.setColorMapVisible(true);
+                               try {
+                                       _vp.readValues(vcmv.getReader());
+                               } catch (IOException e) {
+                                       _vp.errorDialog((Exception) getDialogError());
+                               }
+                       }
+                       
+               };
+               showConfirmDialog(vcmv, "Load base values", ok, null);
+       }
+
+       public void UISetColorMapValues() {
+               final VueBaseValues vbv = new VueBaseValues(_vp);
+               Runnable cancel = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               vbv.cancelChanges();
+                       }
+                                               
+               };
+               showConfirmDialog(vbv, "Choose base values", null, cancel);
+       }
+
+       public void UIManualInput() throws ParseException, ExceptionNonEqualLength {
+               if (_vp.isModifiable()) {
+                       final VueManualInput manualInput = new VueManualInput(_vp);
+                       Runnable ok = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       if (_vp.getRNA().getSize() == 0) {
+
+                                       }
+                                       try {
+                                               RNA r = new RNA();
+                                               VARNAConfig cfg = new VARNAConfig();
+                                               r.setRNA(manualInput.getTseq().getText(), manualInput.getTstr().getText());
+                                               r.drawRNA(_vp.getRNA().get_drawMode(), cfg);
+                                               _vp.drawRNAInterpolated(r);
+                                               _vp.repaint();
+                                       } catch (ExceptionFileFormatOrSyntax e) {
+                                               // TODO Auto-generated catch block
+                                               e.printStackTrace();
+                                       } catch (ExceptionNAViewAlgorithm e) {
+                                               // TODO Auto-generated catch block
+                                               e.printStackTrace();
+                                       } catch (ExceptionUnmatchedClosingParentheses e) {
+                                               // TODO Auto-generated catch block
+                                               e.printStackTrace();
+                                       }
+                               }
+                               
+                       };
+                       showConfirmDialog(manualInput.getPanel(), "Input sequence/structure", ok, null);
+               }
+       }
+
+       public void UISetTitle() {
+               if (_vp.isModifiable()) {
+                       Runnable input = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       String res = (String) getDialogReturnValue();
+                                       if (res != null) {
+                               _vp.setTitle(res);
+                                               _vp.repaint();
+                                       }
+                               }
+                               
+                       };
+                       
+                       showInputDialog("Input title", _vp.getTitle(), input);
+               }
+       }
+
+       public void UISetColorMapCaption() {
+               if (_vp.isModifiable()) {
+                       Runnable input = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       String res = (String) getDialogReturnValue();                                           
+                                       if (res != null) {
+                                               _vp.setColorMapCaption(res);
+                                               _vp.repaint();
+                                       }
+                               }
+                               
+                       };
+                       showInputDialog("Input new color map caption", _vp.getColorMapCaption(), input);
+               }
+       }
+
+       public void UISetBaseCharacter() {
+               if (_vp.isModifiable()) {
+                       final int i = _vp.getNearestBase();
+
+                       if (_vp.isComparisonMode()) {
+
+                               Runnable input = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               String res = (String) getDialogReturnValue();
+                                               if (res != null) {
+                                                       ModeleBasesComparison mb = (ModeleBasesComparison) _vp.getRNA().get_listeBases().get(i);
+                                                       String bck = mb.getBase1() + "|" + mb.getBase2();
+                                                       mb.setBase1(((res.length() > 0) ? res.charAt(0) : ' '));
+                                                       mb.setBase2(((res.length() > 1) ? res.charAt(1) : ' '));
+                                                       _vp.repaint();
+                                                       _vp.fireSequenceChanged(i, bck, res);
+                                               }
+                                       }
+
+                               };
+
+                               
+                               
+                               showInputDialog("Input base", ((ModeleBasesComparison) _vp.getRNA().get_listeBases().get(i)).getBases(),  input);
+
+                       } else {
+
+                               Runnable input = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               String res = (String) getDialogReturnValue();
+                                               if (res != null) {
+                                                       ModeleBaseNucleotide mb = (ModeleBaseNucleotide) _vp.getRNA().get_listeBases().get(i);
+                                                       String bck = mb.getBase();
+                                                       mb.setBase(res);
+                                                       _vp.repaint();
+                                                       _vp.fireSequenceChanged(i, bck, res);
+                                               }
+                                       }
+
+                               };
+                               showInputDialog("Input base", ((ModeleBaseNucleotide) _vp.getRNA().get_listeBases().get(i)).getBase(), input);
+                       }
+               }
+       }
+
+       FileNameExtensionFilter _varnaFilter = new FileNameExtensionFilter(
+                       "VARNA Session File", "varna", "VARNA");
+       FileNameExtensionFilter _bpseqFilter = new FileNameExtensionFilter(
+                       "BPSeq (CRW) File", "bpseq", "BPSEQ");
+       FileNameExtensionFilter _ctFilter = new FileNameExtensionFilter(
+                       "Connect (MFold) File", "ct", "CT");
+       FileNameExtensionFilter _dbnFilter = new FileNameExtensionFilter(
+                       "Dot-bracket notation (Vienna) File", "dbn", "DBN", "faa", "FAA");
+
+       FileNameExtensionFilter _jpgFilter = new FileNameExtensionFilter(
+                       "JPEG Picture", "jpeg", "jpg", "JPG", "JPEG");
+       FileNameExtensionFilter _pngFilter = new FileNameExtensionFilter(
+                       "PNG Picture", "png", "PNG");
+       FileNameExtensionFilter _epsFilter = new FileNameExtensionFilter(
+                       "EPS File", "eps", "EPS");
+       FileNameExtensionFilter _svgFilter = new FileNameExtensionFilter(
+                       "SVG Picture", "svg", "SVG");
+       FileNameExtensionFilter _xfigFilter = new FileNameExtensionFilter(
+                       "XFig Diagram", "fig", "xfig", "FIG", "XFIG");
+       FileNameExtensionFilter _tikzFilter = new FileNameExtensionFilter(
+                       "PGF/Tikz diagram", "tex", "pgf");
+
+       public void UIExport() throws ExceptionExportFailed,
+                       ExceptionPermissionDenied, ExceptionWritingForbidden,
+                       ExceptionJPEGEncoding {
+               ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
+               v.add(_epsFilter);
+               v.add(_svgFilter);
+               v.add(_tikzFilter);
+               v.add(_xfigFilter);
+               v.add(_jpgFilter);
+               v.add(_pngFilter);
+               String dest = UIChooseOutputFile(v);
+               if (dest != null) {
+                       String extLower = dest.substring(dest.lastIndexOf('.'))
+                                       .toLowerCase();
+                       // System.out.println(extLower);
+                       if (extLower.equals(".eps")) {
+                               _vp.getRNA().saveRNAEPS(dest, _vp.getConfig());
+                       } else if (extLower.equals(".svg")) {
+                               _vp.getRNA().saveRNASVG(dest, _vp.getConfig());
+                       } else if (extLower.equals(".fig") || extLower.equals(".xfig")) {
+                               _vp.getRNA().saveRNAXFIG(dest, _vp.getConfig());
+                       } else if (extLower.equals(".pgf") || extLower.equals(".tex")) {
+                               _vp.getRNA().saveRNATIKZ(dest, _vp.getConfig());
+                       } else if (extLower.equals(".png")) {
+                               saveToPNG(dest);
+                       } else if (extLower.equals(".jpg") || extLower.equals(".jpeg")) {
+                               saveToJPEG(dest);
+                       }
+               }
+       }
+
+       public void UIExportJPEG() throws ExceptionJPEGEncoding,
+                       ExceptionExportFailed {
+               String dest = UIChooseOutputFile(_jpgFilter);
+               if (dest != null) {
+                       saveToJPEG(dest);
+               }
+       }
+
+       public void UIPrint() {
+               VARNAPrinter.printComponent(_vp);
+       }
+
+       public void UIExportPNG() throws ExceptionExportFailed {
+               String dest = UIChooseOutputFile(_pngFilter);
+               if (dest != null) {
+                       saveToPNG(dest);
+               }
+       }
+
+       public void UIExportXFIG() throws ExceptionExportFailed,
+                       ExceptionWritingForbidden {
+               String dest = UIChooseOutputFile(_xfigFilter);
+               if (dest != null) {
+                       _vp.getRNA().saveRNAXFIG(dest, _vp.getConfig());
+               }
+       }
+
+       public void UIExportTIKZ() throws ExceptionExportFailed,
+                       ExceptionWritingForbidden {
+               String dest = UIChooseOutputFile(_tikzFilter);
+               if (dest != null) {
+                       _vp.getRNA().saveRNATIKZ(dest, _vp.getConfig());
+               }
+       }
+
+       public void UIExportEPS() throws ExceptionExportFailed,
+                       ExceptionWritingForbidden {
+               String dest = UIChooseOutputFile(_epsFilter);
+               if (dest != null) {
+                       _vp.getRNA().saveRNAEPS(dest, _vp.getConfig());
+               }
+       }
+
+       public void UIExportSVG() throws ExceptionExportFailed,
+                       ExceptionWritingForbidden {
+               String dest = UIChooseOutputFile(_svgFilter);
+               if (dest != null) {
+                       _vp.getRNA().saveRNASVG(dest, _vp.getConfig());
+               }
+       }
+
+       public void UISaveAsDBN() throws ExceptionExportFailed,
+                       ExceptionPermissionDenied {
+               String name = _vp.getVARNAUI().UIChooseOutputFile(_dbnFilter);
+               if (name != null)
+                       _vp.getRNA().saveAsDBN(name, _vp.getTitle());
+       }
+
+       public void UISaveAsCT() throws ExceptionExportFailed,
+                       ExceptionPermissionDenied {
+               String name = _vp.getVARNAUI().UIChooseOutputFile(_ctFilter);
+               if (name != null)
+                       _vp.getRNA().saveAsCT(name, _vp.getTitle());
+       }
+
+       public void UISaveAsBPSEQ() throws ExceptionExportFailed,
+                       ExceptionPermissionDenied {
+               String name = _vp.getVARNAUI().UIChooseOutputFile(_bpseqFilter);
+               if (name != null)
+                       _vp.getRNA().saveAsBPSEQ(name, _vp.getTitle());
+       }
+
+       public void UISaveAs() throws ExceptionExportFailed,
+                       ExceptionPermissionDenied {
+               ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
+               v.add(_bpseqFilter);
+               v.add(_dbnFilter);
+               v.add(_ctFilter);
+               v.add(_varnaFilter);
+               String dest = UIChooseOutputFile(v);
+               if (dest != null) {
+                       String extLower = dest.substring(dest.lastIndexOf('.'))
+                                       .toLowerCase();
+                       if (extLower.endsWith("bpseq")) {
+                               _vp.getRNA().saveAsBPSEQ(dest, _vp.getTitle());
+                       } else if (extLower.endsWith("ct")) {
+                               _vp.getRNA().saveAsCT(dest, _vp.getTitle());
+                       } else if (extLower.endsWith("dbn") || extLower.endsWith("faa")) {
+                               _vp.getRNA().saveAsDBN(dest, _vp.getTitle());
+                       } else if (extLower.endsWith("varna")) {
+                               _vp.saveSession(dest);
+                       }
+               }
+       }
+
+       public String UIChooseOutputFile(FileNameExtensionFilter filtre) {
+               ArrayList<FileNameExtensionFilter> v = new ArrayList<FileNameExtensionFilter>();
+               v.add(filtre);
+               return UIChooseOutputFile(v);
+       }
+
+       /**
+        * Opens a save dialog with right extensions and return the absolute path
+        * 
+        * @param filtre
+        *            Allowed extensions
+        * @return <code>null</code> if the user doesn't approve the save dialog,<br>
+        *         <code>absolutePath</code> if the user approve the save dialog
+        */
+       public String UIChooseOutputFile(ArrayList<FileNameExtensionFilter> filtre) {
+               JFileChooser fc = new JFileChooser();
+               loadPath(fc);
+               String absolutePath = null;
+               // applique le filtre
+               for (int i = 0; i < filtre.size(); i++) {
+                       fc.addChoosableFileFilter(filtre.get(i));
+               }
+               // en mode open dialog pour voir les autres fichiers avec la meme
+               // extension
+               fc.setFileSelectionMode(JFileChooser.OPEN_DIALOG);
+               fc.setDialogTitle("Save...");
+               // Si l'utilisateur a valider
+               if (fc.showSaveDialog(_vp) == JFileChooser.APPROVE_OPTION) {
+                       savePath(fc);
+                       absolutePath = fc.getSelectedFile().getAbsolutePath();
+                       String extension = _vp.getPopupMenu().get_controleurMenu()
+                                       .getExtension(fc.getSelectedFile());
+                       FileFilter f = fc.getFileFilter();
+                       if (f instanceof FileNameExtensionFilter) {
+                               ArrayList<String> listeExtension = new ArrayList<String>();
+                               listeExtension.addAll(Arrays
+                                               .asList(((FileNameExtensionFilter) f).getExtensions()));
+                               // si l'extension du fichier ne fait pas partie de la liste
+                               // d'extensions acceptées
+                               if (!listeExtension.contains(extension)) {
+                                       absolutePath += "." + listeExtension.get(0);
+                               }
+                       }
+               }
+               return absolutePath;
+       }
+
+       public void UISetBorder() {
+               VueBorder border = new VueBorder(_vp);
+               final Dimension oldBorder = _vp.getBorderSize();
+               _vp.drawBBox(true);
+               _vp.drawBorder(true);
+               _vp.repaint();
+               Runnable cancel = new Runnable () {
+
+                       @Override
+                       public void run() {
+                               _vp.setBorderSize(oldBorder);
+                       }
+                       
+               };
+               Runnable final_ = new Runnable () {
+
+                       @Override
+                       public void run() {
+                               _vp.drawBorder(false);
+                               _vp.drawBBox(false);
+                               _vp.repaint();
+                       }
+                       
+               };
+               
+               showConfirmDialog(border.getPanel(), "Set new border size", null, cancel, cancel, final_);
+       }
+
+       public void UISetBackground() {
+               showColorDialog("Choose new background color", _vp.getBackground(), new Runnable() {
+
+                       @Override
+                       public void run() {
+                               if (dialogReturnValue != null) {
+                                       _vp.setBackground((Color) dialogReturnValue);
+                                       _vp.repaint();
+                               }
+                       }
+                       
+               });
+       }
+
+       public void UIZoomIn() {
+               double _actualZoom = _vp.getZoom();
+               double _actualAmount = _vp.getZoomIncrement();
+               Point _actualTranslation = _vp.getTranslation();
+               double newZoom = Math.min(VARNAConfig.MAX_ZOOM, _actualZoom
+                               * _actualAmount);
+               double ratio = newZoom / _actualZoom;
+               Point newTrans = new Point((int) (_actualTranslation.x * ratio),
+                               (int) (_actualTranslation.y * ratio));
+               _vp.setZoom(newZoom);
+               _vp.setTranslation(newTrans);
+               // verification que la translation ne pose pas de problemes
+               _vp.checkTranslation();
+               // System.out.println("Zoom in");
+               _vp.repaint();
+       }
+
+       public void UIZoomOut() {
+               double _actualZoom = _vp.getZoom();
+               double _actualAmount = _vp.getZoomIncrement();
+               Point _actualTranslation = _vp.getTranslation();
+               double newZoom = Math.max(_actualZoom / _actualAmount,
+                               VARNAConfig.MIN_ZOOM);
+               double ratio = newZoom / _actualZoom;
+               Point newTrans = new Point((int) (_actualTranslation.x * ratio),
+                               (int) (_actualTranslation.y * ratio));
+               _vp.setZoom(newZoom);
+               _vp.setTranslation(newTrans);
+               // verification que la translation ne pose pas de problemes
+               _vp.checkTranslation();
+               _vp.repaint();
+       }
+
+       public void UICustomZoom() {
+               VueZoom zoom = new VueZoom(_vp);
+               final double oldZoom = _vp.getZoom();
+               final double oldZoomAmount = _vp.getZoomIncrement();
+               _vp.drawBBox(true);
+               _vp.repaint();
+               Runnable cancel = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               _vp.setZoom(oldZoom);
+                               _vp.setZoomIncrement(oldZoomAmount);
+                       }
+                       
+               };
+               Runnable final_ = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               _vp.drawBBox(false);
+                               _vp.repaint();
+                       }
+                       
+               };
+               showConfirmDialog(zoom.getPanel(), "Set zoom", null, cancel, cancel, final_);
+       }
+
+       public void UIGlobalRescale() {
+               if (_vp.isModifiable()) {
+                       if (_vp.getRNA().get_listeBases().size() > 0) {
+                               final VueGlobalRescale rescale = new VueGlobalRescale(_vp);
+                               Runnable cancel = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               UIGlobalRescale(1. / rescale.getScale());
+                                       }
+                                       
+                               };
+                               Runnable final_ = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               _vp.drawBBox(false);
+                                               _vp.repaint();
+                                       }
+                                       
+                               };
+                               showConfirmDialog(rescale.getPanel(), "Rescales the whole RNA (No redraw)", null, cancel, cancel, final_);
+                       }
+               }
+       }
+
+       public void UIGlobalRescale(double d) {
+               if (_vp.isModifiable()) {
+                       if (_vp.getRNA().get_listeBases().size() > 0) {
+                               _vp.globalRescale(d);
+                               _undoableEditSupport.postEdit(new VARNAEdits.RescaleRNAEdit(d,
+                                               _vp));
+                       }
+               }
+       }
+
+       public void UIGlobalRotation() {
+               if (_vp.isModifiable()) {
+                       if (_vp.getRNA().get_listeBases().size() > 0) {
+                               _vp.drawBBox(true);
+                               _vp.repaint();
+                               final VueGlobalRotation rotation = new VueGlobalRotation(_vp);
+                               Runnable cancel = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               UIGlobalRotation(-rotation.getAngle());
+                                       }
+                                       
+                               };
+                               Runnable final_ = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               _vp.drawBBox(false);
+                                               _vp.repaint();
+                                       }
+                                       
+                               };
+                               showConfirmDialog(rotation.getPanel(), "Rotates the whole RNA", null, cancel, cancel, final_, null);
+                       }
+               }
+       }
+
+       public void UIGlobalRotation(double d) {
+               if (_vp.isModifiable()) {
+                       if (_vp.getRNA().get_listeBases().size() > 0) {
+                               _vp.globalRotation(d);
+                               _undoableEditSupport.postEdit(new VARNAEdits.RotateRNAEdit(d,
+                                               _vp));
+                       }
+               }
+       }
+
+       public void UISetBPStyle() {
+               if (_vp.getRNA().get_listeBases().size() > 0) {
+                       VueStyleBP bpstyle = new VueStyleBP(_vp);
+                       final VARNAConfig.BP_STYLE bck = _vp.getBPStyle();
+                       Runnable cancel = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       _vp.setBPStyle(bck);
+                                       _vp.repaint();
+                               }
+                               
+                       };
+                       showConfirmDialog(bpstyle.getPanel(), "Set main base pair style", null, cancel, cancel);
+               }
+       }
+
+       public void UISetTitleColor() {
+               if (_vp.isModifiable()) {
+                       showColorDialog("Choose new title color", _vp.getTitleColor(), new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       if (dialogReturnValue != null) {
+                                               _vp.setTitleColor((Color) dialogReturnValue);
+                                               _vp.repaint();
+                                       }
+                               }
+                               
+                       });
+               }
+       }
+
+       public void UISetBackboneColor() {
+               if (_vp.isModifiable()) {
+                       showColorDialog("Choose new backbone color", _vp.getBackboneColor(), new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       if (dialogReturnValue != null) {
+                                               _vp.setBackboneColor((Color) dialogReturnValue);
+                                               _vp.repaint();
+                                       }
+                               }
+                               
+                       });
+               }
+       }
+
+       public void UISetTitleFont() {
+               if (_vp.isModifiable()) {
+                       final VueFont font = new VueFont(_vp);
+                       Runnable ok = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       _vp.setTitleFont(font.getFont());
+                                       _vp.repaint();
+                               }
+
+                       };
+                       showConfirmDialog(font.getPanel(), "New Title font", ok, null);
+               }
+       }
+
+       public void UISetSpaceBetweenBases() {
+               if (_vp.isModifiable()) {
+
+                       final VueSpaceBetweenBases vsbb = new VueSpaceBetweenBases(_vp);
+                       final Double oldSpace = _vp.getSpaceBetweenBases();
+                       Runnable cancel = new Runnable () {
+
+                               @Override
+                               public void run() {
+                                       _vp.setSpaceBetweenBases(oldSpace);
+                                       _vp.drawRNA(_vp.getRNA());
+                                       _vp.repaint();
+                               }
+                               
+                       };
+                       showConfirmDialog(vsbb.getPanel(), "Set the space between each base", null, cancel, cancel);                    
+               }
+       }
+
+       public void UISetBPHeightIncrement() {
+               if (_vp.isModifiable()) {
+
+                       VueBPHeightIncrement v = new VueBPHeightIncrement(_vp);
+                       final Double oldSpace = _vp.getBPHeightIncrement();
+                       Runnable cancel = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       _vp.setBPHeightIncrement(oldSpace);
+                                       _vp.drawRNA(_vp.getRNA());
+                                       _vp.repaint();
+                               }
+                               
+                       };
+                       showConfirmDialog(v.getPanel(), "Set the vertical increment in linear mode", null, cancel, cancel);
+               }
+       }
+
+       public void UISetNumPeriod() {
+               if (_vp.getRNA().get_listeBases().size() != 0) {
+                       final int oldNumPeriod = _vp.getNumPeriod();
+                       VueNumPeriod vnp = new VueNumPeriod(_vp);
+                       Runnable cancel = new Runnable() {
+
+                               @Override
+                               public void run() {
+                                       _vp.setNumPeriod(oldNumPeriod);
+                                       _vp.repaint();
+                               }
+                               
+                       };
+                       showConfirmDialog(vnp.getPanel(), "Set new numbering period", null, cancel, cancel);
+               }
+       }
+
+       public void UIEditBasePair() {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase());
+                       if (mb.getElementStructure() != -1) {
+                               final ModeleBP msbp = mb.getStyleBP();
+                               final ModeleBP.Edge bck5 = msbp.getEdgePartner5();
+                               final ModeleBP.Edge bck3 = msbp.getEdgePartner3();
+                               final ModeleBP.Stericity bcks = msbp.getStericity();
+
+                               VueBPType vbpt = new VueBPType(_vp, msbp);
+                               Runnable cancel = new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               msbp.setEdge5(bck5);
+                                               msbp.setEdge3(bck3);
+                                               msbp.setStericity(bcks);
+                                               _vp.repaint();
+                                       }
+                                       
+                               };
+                               showConfirmDialog(vbpt.getPanel(), "Set base pair L/W type", null, cancel, cancel);
+                       }
+               }
+       }
+
+       public void UIColorBasePair() {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase());
+                       if (mb.getElementStructure() != -1) {
+                               final ModeleBP msbp = mb.getStyleBP();
+                               showColorDialog("Choose custom base pair color",
+                                               msbp.getStyle().getColor(_vp.getConfig()._bondColor), new Runnable() {
+
+                                       @Override
+                                       public void run() {
+                                               if (dialogReturnValue != null) {
+                                                       msbp.getStyle().setCustomColor((Color) dialogReturnValue);
+                                                       _vp.repaint();
+                                               }
+                                       }
+                                       
+                               });
+                       }
+               }
+       }
+
+       public void UIThicknessBasePair() {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases()
+                                       .get(_vp.getNearestBase());
+                       if (mb.getElementStructure() != -1) {
+                               ModeleBP msbp = mb.getStyleBP();
+                               ArrayList<ModeleBP> bases = new ArrayList<ModeleBP>();
+                               bases.add(msbp);
+                               UIThicknessBasePairs(bases);
+                       }
+               }
+       }
+
+       public void saveToPNG(final String filename) {
+               final VueJPEG jpeg = new VueJPEG(true, false);
+               Runnable ok = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               Double scale = jpeg.getScaleSlider().getValue() / 100.0;
+                               BufferedImage myImage = new BufferedImage((int) Math.round(_vp.getWidth() * scale),
+                                               (int) Math.round(_vp.getHeight() * scale), BufferedImage.TYPE_INT_ARGB);
+                               // BH j2s SwingJS: was BufferedImage.TRANSLUCENT, which is TYPE_INT_ARGB_PRE ?? no transparent background?
+                               Graphics2D g2 = myImage.createGraphics();
+                               AffineTransform AF = new AffineTransform();
+                               AF.setToScale(scale, scale);
+                               g2.setTransform(AF);
+                               _vp.paintComponent(g2, !_vp.getConfig()._drawBackground);
+                               g2.dispose();
+                               try {
+                                       ImageIO.write(myImage, "PNG", new File(filename));
+                               } catch (IOException e) {
+                                       // cannot throw an exception from Runnable.run()
+                                       _vp.errorDialog(new ExceptionExportFailed(e.getMessage(), filename));
+                               }
+                       }
+
+               };
+               showConfirmDialog(jpeg.getPanel(), "Set resolution", ok, null);
+       }
+
+       public void saveToJPEG(final String filename) {
+               final VueJPEG jpeg = new VueJPEG(true, true);
+               Runnable ok = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               Double scale;
+                               if (jpeg.getScaleSlider().getValue() == 0)
+                                       scale = 1. / 100.;
+                               else
+                                       scale = jpeg.getScaleSlider().getValue() / 100.;
+                       BufferedImage myImage = new BufferedImage((int) Math.round(_vp
+                                       .getWidth() * scale), (int) Math.round(_vp.getHeight()
+                                       * scale), BufferedImage.TYPE_INT_RGB);
+                               Graphics2D g2 = myImage.createGraphics();
+                               AffineTransform AF = new AffineTransform();
+                               AF.setToScale(scale, scale);
+                               g2.setTransform(AF);
+                               _vp.paintComponent(g2);
+                               try {
+                               FileImageOutputStream out = new FileImageOutputStream(new File(
+                                               filename));
+                               ImageWriter writer = ImageIO
+                                               .getImageWritersByFormatName("jpeg").next();
+                                       ImageWriteParam params = writer.getDefaultWriteParam();
+                                       params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+                                       params.setCompressionQuality(jpeg.getQualitySlider().getValue() / 100.0f);
+                                       writer.setOutput(out);
+                                       IIOImage myIIOImage = new IIOImage(myImage, null, null);
+                                       writer.write(null, myIIOImage, params);
+                                       out.close();
+                               } catch (IOException e) {
+                                       // cannot throw an exception from Runnable.run()
+                                       _vp.errorDialog(new ExceptionExportFailed(e.getMessage(), filename));
+                               }
+                       }
+
+               };
+               showConfirmDialog(jpeg.getPanel(), "Set resolution/quality", ok, null);
+       }
+
+       public void UIToggleShowNCBP() {
+               if (_vp.isModifiable()) {
+                       _vp.setShowNonCanonicalBP(!_vp.getShowNonCanonicalBP());
+                       _vp.repaint();
+               }
+       }
+
+       public void UIToggleColorSpecialBases() {
+               _vp.setColorNonStandardBases(!_vp.getColorSpecialBases());
+               _vp.repaint();
+       }
+
+       public void UIToggleColorGapsBases() {
+               _vp.setColorGapsBases(!_vp.getColorGapsBases());
+               _vp.repaint();
+       }
+
+       public void UIToggleShowNonPlanar() {
+               if (_vp.isModifiable()) {
+                       _vp.setShowNonPlanarBP(!_vp.getShowNonPlanarBP());
+                       _vp.repaint();
+               }
+       }
+
+       public void UIToggleShowWarnings() {
+               _vp.setShowWarnings(!_vp.getShowWarnings());
+               _vp.repaint();
+       }
+
+       public void UIPickSpecialBasesColor() {
+               showColorDialog("Choose new special bases color", _vp.getNonStandardBasesColor(), new Runnable() {
+
+                       @Override
+                       public void run() {
+                               if (dialogReturnValue != null) {
+                                       _vp.setNonStandardBasesColor((Color) dialogReturnValue);
+                                       _vp.setColorNonStandardBases(true);
+                                       _vp.repaint();
+                               }
+                       }
+                       
+               });
+       }
+
+       public void UIPickGapsBasesColor() {
+               showColorDialog("Choose new gaps bases color", _vp.getGapsBasesColor(), new Runnable() {
+
+                       @Override
+                       public void run() {
+                               if (dialogReturnValue != null) {
+                                       _vp.setGapsBasesColor((Color) dialogReturnValue);
+                                       _vp.setColorGapsBases(true);
+                                       _vp.repaint();
+                               }
+                       }
+                       
+               });
+       }
+
+       public void UIBaseTypeColor() {
+               if (_vp.isModifiable()) {
+                       new VueBases(_vp, VueBases.KIND_MODE);
+               }
+       }
+
+       public void UIToggleModifiable() {
+               _vp.setModifiable(!_vp.isModifiable());
+       }
+
+       public void UIBasePairTypeColor() {
+               if (_vp.isModifiable()) {
+                       new VueBases(_vp, VueBases.COUPLE_MODE);
+               }
+       }
+
+       public void UIBaseAllColor() {
+               if (_vp.isModifiable()) {
+                       new VueBases(_vp, VueBases.ALL_MODE);
+               }
+       }
+
+       public void UIAbout() {
+               final VueAboutPanel about = new VueAboutPanel();
+               Runnable ok = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               about.gracefulStop();
+                       }
+                       
+               };
+               showMessageDialog(about, "About VARNA " + VARNAConfig.MAJOR_VERSION + "." + VARNAConfig.MINOR_VERSION,
+                               JOptionPane.PLAIN_MESSAGE, ok, ok);
+       }
+
+       public void UIAutoAnnotateHelices() {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().autoAnnotateHelices();
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAutoAnnotateStrandEnds() {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().autoAnnotateStrandEnds();
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAutoAnnotateInteriorLoops() {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().autoAnnotateInteriorLoops();
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAutoAnnotateTerminalLoops() {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().autoAnnotateTerminalLoops();
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAnnotationRemoveFromAnnotation(TextAnnotation textAnnotation) {
+               if (_vp.isModifiable()) {
+                       _vp.set_selectedAnnotation(null);
+                       _vp.getListeAnnotations().remove(textAnnotation);
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAnnotationEditFromAnnotation(TextAnnotation textAnnotation) {
+               VueAnnotation vue;
+               if (textAnnotation.getType() == TextAnnotation.AnchorType.POSITION)
+                       vue = new VueAnnotation(_vp, textAnnotation, false);
+               else
+                       vue = new VueAnnotation(_vp, textAnnotation, true, false);
+               vue.show();
+       }
+
+       public void UIAnnotationAddFromStructure(TextAnnotation.AnchorType type, ArrayList<Integer> listeIndex)
+                       throws Exception {
+               TextAnnotation textAnnot;
+               ArrayList<ModeleBase> listeBase;
+               VueAnnotation vue;
+               switch (type) {
+               case BASE:
+                       textAnnot = new TextAnnotation("", _vp.getRNA().get_listeBases()
+                                       .get(listeIndex.get(0)));
+                       vue = new VueAnnotation(_vp, textAnnot, true);
+                       vue.show();
+                       break;
+               case LOOP:
+                       listeBase = new ArrayList<ModeleBase>();
+                       for (Integer i : listeIndex) {
+                               listeBase.add(_vp.getRNA().get_listeBases().get(i));
+                       }
+                       textAnnot = new TextAnnotation("", listeBase, type);
+                       vue = new VueAnnotation(_vp, textAnnot, true);
+                       vue.show();
+                       break;
+               case HELIX:
+                       listeBase = new ArrayList<ModeleBase>();
+                       for (Integer i : listeIndex) {
+                               listeBase.add(_vp.getRNA().get_listeBases().get(i));
+                       }
+                       textAnnot = new TextAnnotation("", listeBase, type);
+                       vue = new VueAnnotation(_vp, textAnnot, true);
+                       vue.show();
+                       break;
+               default:
+                       _vp.errorDialog(new Exception("Unknown structure type"));
+                       break;
+               }
+       }
+
+       public void UIAnnotationEditFromStructure(TextAnnotation.AnchorType type,
+                       ArrayList<Integer> listeIndex) {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases()
+                                       .get(listeIndex.get(0));
+                       TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
+                       if (ta != null)
+                               UIAnnotationEditFromAnnotation(ta);
+               }
+       }
+
+       public void UIAnnotationRemoveFromStructure(TextAnnotation.AnchorType type,
+                       ArrayList<Integer> listeIndex) {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().get_listeBases()
+                                       .get(listeIndex.get(0));
+                       TextAnnotation ta = _vp.getRNA().getAnnotation(type, mb);
+                       if (ta != null)
+                               UIAnnotationRemoveFromAnnotation(ta);
+               }
+       }
+
+       public void UIAnnotationsAddPosition(int x, int y) {
+               if (_vp.isModifiable()) {
+                       Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
+                       VueAnnotation annotationAdd = new VueAnnotation(_vp, (int) p.x,
+                                       (int) p.y);
+                       annotationAdd.show();
+               }
+       }
+
+       public void UIAnnotationsAddBase(int x, int y) {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
+                       if (mb != null) {
+                               _vp.highlightSelectedBase(mb);
+                               TextAnnotation textAnnot = new TextAnnotation("", mb);
+                               VueAnnotation annotationAdd = new VueAnnotation(_vp, textAnnot,
+                                               true);
+                               annotationAdd.show();
+                       }
+               }
+       }
+
+       public void UIAnnotationsAddLoop(int x, int y) {
+               if (_vp.isModifiable()) {
+                       try {
+                               ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
+                               if (mb != null) {
+                                       Vector<Integer> v = _vp.getRNA()
+                                                       .getLoopBases(mb.getIndex());
+                                       ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
+                                       TextAnnotation textAnnot;
+                                       textAnnot = new TextAnnotation("", mbs,
+                                                       TextAnnotation.AnchorType.LOOP);
+                                       _vp.setSelection(mbs);
+                                       VueAnnotation annotationAdd = new VueAnnotation(_vp,
+                                                       textAnnot, true);
+                                       annotationAdd.show();
+                               }
+                       } catch (Exception e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       private ArrayList<ModeleBase> extractMaxContiguousPortion(
+                       ArrayList<ModeleBase> m) {
+               ModeleBase[] tab = new ModeleBase[_vp.getRNA().getSize()];
+               for (int i = 0; i < tab.length; i++) {
+                       tab[i] = null;
+               }
+               for (ModeleBase mb : m) {
+                       tab[mb.getIndex()] = mb;
+               }
+               ArrayList<ModeleBase> best = new ArrayList<ModeleBase>();
+               ArrayList<ModeleBase> current = new ArrayList<ModeleBase>();
+               for (int i = 0; i < tab.length; i++) {
+                       if (tab[i] != null) {
+                               current.add(tab[i]);
+                       } else {
+                               if (current.size() > best.size())
+                                       best = current;
+                               current = new ArrayList<ModeleBase>();
+                       }
+               }
+               if (current.size() > best.size()) {
+                       best = current;
+               }
+               return best;
+       }
+
+       public void UIAnnotationsAddRegion(int x, int y) {
+               if (_vp.isModifiable()) {
+                       ArrayList<ModeleBase> mb = _vp.getSelection().getBases();
+                       if (mb.size() == 0) {
+                               ModeleBase m = _vp.getBaseAt(new Point2D.Double(x, y));
+                               mb.add(m);
+                       }
+                       mb = extractMaxContiguousPortion(extractMaxContiguousPortion(mb));
+                       _vp.setSelection(mb);
+                       HighlightRegionAnnotation regionAnnot = new HighlightRegionAnnotation(
+                                       mb);
+                       _vp.addHighlightRegion(regionAnnot);
+                       VueHighlightRegionEdit annotationAdd = new VueHighlightRegionEdit(
+                                       _vp, regionAnnot);
+                       if (!annotationAdd.show()) {
+                               _vp.removeHighlightRegion(regionAnnot);
+                       }
+                       _vp.clearSelection();
+               }
+       }
+
+       public void UIAnnotationsAddChemProb(int x, int y) {
+               if (_vp.isModifiable() && _vp.getRNA().getSize() > 1) {
+                       Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(x, y));
+                       ModeleBase m1 = _vp.getBaseAt(new Point2D.Double(x, y));
+                       ModeleBase best = null;
+                       if (m1.getIndex() - 1 >= 0) {
+                               best = _vp.getRNA().getBaseAt(m1.getIndex() - 1);
+                       }
+                       if (m1.getIndex() + 1 < _vp.getRNA().getSize()) {
+                               ModeleBase m2 = _vp.getRNA().getBaseAt(m1.getIndex() + 1);
+                               if (best == null) {
+                                       best = m2;
+                               } else {
+                                       if (best.getCoords().distance(p) > m2.getCoords().distance(
+                                                       p)) {
+                                               best = m2;
+                                       }
+                               }
+                       }
+                       ArrayList<ModeleBase> tab = new ArrayList<ModeleBase>();
+                       tab.add(m1);
+                       tab.add(best);
+                       _vp.setSelection(tab);
+                       ChemProbAnnotation regionAnnot = new ChemProbAnnotation(m1, best);
+                       _vp.getRNA().addChemProbAnnotation(regionAnnot);
+                       VueChemProbAnnotation annotationAdd = new VueChemProbAnnotation(
+                                       _vp, regionAnnot);
+                       if (!annotationAdd.show()) {
+                               _vp.getRNA().removeChemProbAnnotation(regionAnnot);
+                       }
+                       _vp.clearSelection();
+               }
+       }
+
+       public void UIAnnotationsAddHelix(int x, int y) {
+               if (_vp.isModifiable()) {
+                       try {
+                               ModeleBase mb = _vp.getBaseAt(new Point2D.Double(x, y));
+                               if (mb != null) {
+                                       ArrayList<Integer> v = _vp.getRNA().findHelix(mb.getIndex());
+                                       ArrayList<ModeleBase> mbs = _vp.getRNA().getBasesAt(v);
+                                       TextAnnotation textAnnot;
+                                       textAnnot = new TextAnnotation("", mbs,
+                                                       TextAnnotation.AnchorType.HELIX);
+                                       _vp.setSelection(mbs);
+                                       VueAnnotation annotationAdd = new VueAnnotation(_vp,
+                                                       textAnnot, true);
+                                       annotationAdd.show();
+                               }
+                       } catch (Exception e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       public void UIToggleGaspinMode() {
+               if (_vp.isModifiable()) {
+                       _vp.toggleDrawOutlineBases();
+                       _vp.toggleFillBases();
+                       _vp.repaint();
+               }
+       }
+
+       public void UIAnnotationsAdd() {
+               if (_vp.isModifiable()) {
+                       VueAnnotation annotationAdd = new VueAnnotation(_vp);
+                       annotationAdd.show();
+               }
+       }
+
+       public void UIEditAllBasePairs() {
+               if (_vp.isModifiable()) {
+                       new VueBPList(_vp);
+               }
+       }
+
+       public void UIEditAllBases() {
+               if (_vp.isModifiable()) {
+                       new VueBases(_vp, VueBases.ALL_MODE);
+               }
+       }
+
+       public void UIAnnotationsRemove() {
+               if (_vp.isModifiable()) {
+                       new VueListeAnnotations(_vp, VueListeAnnotations.REMOVE);
+               }
+       }
+
+       public void UIAnnotationsEdit() {
+               if (_vp.isModifiable()) {
+                       new VueListeAnnotations(_vp, VueListeAnnotations.EDIT);
+               }
+       }
+
+       public void UIAddBP(int i, int j, ModeleBP ms) {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().addBP(i, j, ms);
+                       _undoableEditSupport.postEdit(new VARNAEdits.AddBPEdit(i, j, ms,
+                                       _vp));
+                       _vp.repaint();
+
+                       HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
+                       tmp.add(ms);
+                       _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
+                                       .getAllBPs()), tmp, new HashSet<ModeleBP>());
+               }
+       }
+
+       public void UIRemoveBP(ModeleBP ms) {
+               if (_vp.isModifiable()) {
+                       _undoableEditSupport.postEdit(new VARNAEdits.RemoveBPEdit(ms
+                                       .getIndex5(), ms.getIndex3(), ms, _vp));
+                       _vp.getRNA().removeBP(ms);
+                       _vp.repaint();
+
+                       HashSet<ModeleBP> tmp = new HashSet<ModeleBP>();
+                       tmp.add(ms);
+                       _vp.fireStructureChanged(new HashSet<ModeleBP>(_vp.getRNA()
+                                       .getAllBPs()), new HashSet<ModeleBP>(), tmp);
+               }
+       }
+
+       public void UIShiftBaseCoord(ArrayList<Integer> indices, double dx, double dy) {
+               if (_vp.isModifiable()) {
+                       Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
+
+                       for (int index : indices) {
+                               ModeleBase mb = _vp.getRNA().getBaseAt(index);
+                               Point2D.Double d = mb.getCoords();
+                               backupPos.put(index, d);
+                               _vp.getRNA().setCoord(index, d.x + dx, d.y + dy);
+                               _vp.getRNA().setCenter(index, mb.getCenter().x + dx, mb.getCenter().y + dy);
+                       }
+                       _undoableEditSupport.postEdit(new VARNAEdits.BasesShiftEdit(
+                                       indices, dx, dy, _vp));
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(backupPos);
+               }
+       }
+
+       public void UIShiftBaseCoord(ArrayList<Integer> indices, Point2D.Double dv) {
+               UIShiftBaseCoord(indices, dv.x, dv.y);
+       }
+
+       public void UIMoveSingleBase(int index, double nx, double ny) {
+               if (_vp.isModifiable()) {
+                       ModeleBase mb = _vp.getRNA().getBaseAt(index);
+                       Point2D.Double d = mb.getCoords();
+                       Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
+                       backupPos.put(index, d);
+                       _undoableEditSupport.postEdit(new VARNAEdits.SingleBaseMoveEdit(
+                                       index, nx, ny, _vp));
+                       _vp.getRNA().setCoord(index, nx, ny);
+                       _vp.repaint();
+                       _vp.fireLayoutChanged(backupPos);
+               }
+       }
+
+       public void UIMoveSingleBase(int index, Point2D.Double dv) {
+               UIMoveSingleBase(index, dv.x, dv.y);
+       }
+
+       public void UISetBaseCenter(int index, double x, double y) {
+               UISetBaseCenter(index, new Point2D.Double(x, y));
+       }
+
+       public void UISetBaseCenter(int index, Point2D.Double p) {
+               if (_vp.isModifiable()) {
+                       _vp.getRNA().setCenter(index, p);
+               }
+       }
+
+       public void UIUndo() {
+               _vp.undo();
+       }
+
+       public void UIRedo() {
+               _vp.redo();
+       }
+
+       /**
+        * Move a helix of the rna
+        * 
+        * @param index
+        *            :the index of the selected base
+        * @param newPos
+        *            :the new xy coordinate, within the logical system of
+        *            coordinates
+        */
+       public void UIMoveHelixAtom(int index, Point2D.Double newPos) {
+               if (_vp.isModifiable() && (index >= 0)
+                               && (index < _vp.getRNA().get_listeBases().size())) {
+                       int indexTo = _vp.getRNA().get_listeBases().get(index)
+                                       .getElementStructure();
+                       Point h = _vp.getRNA().getHelixInterval(index);
+                       Point ml = _vp.getRNA().getMultiLoop(h.x);
+                       int i = ml.x;
+                       if (indexTo != -1) {
+                               if (i == 0) {
+                                       if (shouldFlip(index, newPos)) {
+                                               UIFlipHelix(h);
+                                               _undoableEditSupport
+                                                               .postEdit(new VARNAEdits.HelixFlipEdit(h, _vp));
+                                       }
+                               } else {
+                                       UIRotateHelixAtom(index, newPos);
+                               }
+
+                       }
+                       _vp.fireLayoutChanged();
+               }
+       }
+
+       /**
+        * Flip an helix around its supporting base
+        */
+       public void UIFlipHelix(Point h) {
+               int hBeg = h.x;
+               int hEnd = h.y;
+               Point2D.Double A = _vp.getRNA().getCoords(hBeg);
+               Point2D.Double B = _vp.getRNA().getCoords(hEnd);
+               Point2D.Double AB = new Point2D.Double(B.x - A.x, B.y - A.y);
+               double normAB = Math.sqrt(AB.x * AB.x + AB.y * AB.y);
+               // Creating a coordinate system centered on A and having
+               // unit x-vector Ox.
+               Point2D.Double O = A;
+               Point2D.Double Ox = new Point2D.Double(AB.x / normAB, AB.y / normAB);
+               Hashtable<Integer, Point2D.Double> old = new Hashtable<Integer, Point2D.Double>();
+               for (int i = hBeg + 1; i < hEnd; i++) {
+                       Point2D.Double P = _vp.getRNA().getCoords(i);
+                       Point2D.Double nP = RNA.project(O, Ox, P);
+                       old.put(i, nP);
+               }
+               _vp.getRNA().flipHelix(h);
+               _vp.fireLayoutChanged(old);
+       }
+
+       /**
+        * Tests if an helix needs to be flipped.
+        */
+       boolean shouldFlip(int index, Point2D.Double P) {
+               Point h = _vp.getRNA().getHelixInterval(index);
+
+               Point2D.Double A = _vp.getRNA().getCoords(h.x);
+               Point2D.Double B = _vp.getRNA().getCoords(h.y);
+               Point2D.Double C = _vp.getRNA().getCoords(h.x + 1);
+               // Creating a vector that is orthogonal to AB
+               Point2D.Double hAB = new Point2D.Double(B.y - A.y, -(B.x - A.x));
+               Point2D.Double AC = new Point2D.Double(C.x - A.x, C.y - A.y);
+               Point2D.Double AP = new Point2D.Double(P.x - A.x, P.y - A.y);
+               double signC = (hAB.x * AC.x + hAB.y * AC.y);
+               double signP = (hAB.x * AP.x + hAB.y * AP.y);
+               // Now, the product signC*signP is negative iff the mouse and the first
+               // base inside
+               // the helix are on different sides of the end of the helix => Flip the
+               // helix!
+               return (signC * signP < 0.0);
+       }
+
+       public void UIRotateHelixAtom(int index, Point2D.Double newPos) {
+               Point h = _vp.getRNA().getHelixInterval(index);
+               Point ml = _vp.getRNA().getMultiLoop(h.x);
+               int i = ml.x;
+               int prevIndex = h.x;
+               int nextIndex = h.y;
+               while (i <= ml.y) {
+                       int j = _vp.getRNA().get_listeBases().get(i).getElementStructure();
+                       if ((j != -1) && (i < h.x)) {
+                               prevIndex = i;
+                       }
+                       if ((j != -1) && (i > h.y) && (nextIndex == h.y)) {
+                               nextIndex = i;
+                       }
+                       if ((j > i) && (j < ml.y)) {
+                               i = _vp.getRNA().get_listeBases().get(i).getElementStructure();
+                       } else {
+                               i++;
+                       }
+               }
+               Point2D.Double oldPos = _vp.getRNA().getCoords(index);
+               Point2D.Double limitLoopLeft, limitLoopRight, limitLeft, limitRight, helixStart, helixStop;
+               boolean isDirect = _vp.getRNA().testDirectionality(ml.x, ml.y, h.x);
+               if (isDirect) {
+                       limitLoopLeft = _vp.getRNA().getCoords(ml.y);
+                       limitLoopRight = _vp.getRNA().getCoords(ml.x);
+                       limitLeft = _vp.getRNA().getCoords(prevIndex);
+                       limitRight = _vp.getRNA().getCoords(nextIndex);
+                       helixStart = _vp.getRNA().getCoords(h.x);
+                       helixStop = _vp.getRNA().getCoords(h.y);
+               } else {
+                       limitLoopLeft = _vp.getRNA().getCoords(ml.x);
+                       limitLoopRight = _vp.getRNA().getCoords(ml.y);
+                       limitLeft = _vp.getRNA().getCoords(nextIndex);
+                       limitRight = _vp.getRNA().getCoords(prevIndex);
+                       helixStart = _vp.getRNA().getCoords(h.y);
+                       helixStop = _vp.getRNA().getCoords(h.x);
+               }
+
+               Point2D.Double center = _vp.getRNA().get_listeBases().get(h.x)
+                               .getCenter();
+               double base = (RNA.computeAngle(center, limitLoopRight) + RNA
+                               .computeAngle(center, limitLoopLeft)) / 2.0;
+               double pLimR = RNA.computeAngle(center, limitLeft) - base;
+               double pHelR = RNA.computeAngle(center, helixStart) - base;
+               double pNew = RNA.computeAngle(center, newPos) - base;
+               double pOld = RNA.computeAngle(center, oldPos) - base;
+               double pHelL = RNA.computeAngle(center, helixStop) - base;
+               double pLimL = RNA.computeAngle(center, limitRight) - base;
+
+               while (pLimR < 0.0)
+                       pLimR += 2.0 * Math.PI;
+               while (pHelR < pLimR)
+                       pHelR += 2.0 * Math.PI;
+               while ((pNew < pHelR))
+                       pNew += 2.0 * Math.PI;
+               while ((pOld < pHelR))
+                       pOld += 2.0 * Math.PI;
+               while ((pHelL < pOld))
+                       pHelL += 2.0 * Math.PI;
+               while ((pLimL < pHelL))
+                       pLimL += 2.0 * Math.PI;
+
+               double minDelta = normalizeAngle((pLimR - pHelR) + 0.25);
+               double maxDelta = normalizeAngle((pLimL - pHelL) - 0.25);
+               while (maxDelta < minDelta)
+                       maxDelta += 2.0 * Math.PI;
+               double delta = normalizeAngle(pNew - pOld);
+               while (delta < minDelta)
+                       delta += 2.0 * Math.PI;
+
+               if (delta > maxDelta) {
+                       double distanceMax = delta - maxDelta;
+                       double distanceMin = minDelta - (delta - 2.0 * Math.PI);
+                       if (distanceMin < distanceMax) {
+                               delta = minDelta;
+                       } else {
+                               delta = maxDelta;
+                       }
+               }
+               double corrected = RNA
+                               .correctHysteresis((delta + base + (pHelR + pHelL) / 2.));
+               delta = corrected - (base + (pHelR + pHelL) / 2.);
+               _undoableEditSupport.postEdit(new VARNAEdits.HelixRotateEdit(delta,
+                               base, pLimL, pLimR, h, ml, _vp));
+               UIRotateEverything(delta, base, pLimL, pLimR, h, ml);
+       }
+
+       public void UIRotateEverything(double delta, double base, double pLimL,
+                       double pLimR, Point h, Point ml) {
+               Hashtable<Integer, Point2D.Double> backupPos = new Hashtable<Integer, Point2D.Double>();
+               _vp.getRNA().rotateEverything(delta, base, pLimL, pLimR, h, ml,
+                               backupPos);
+               _vp.fireLayoutChanged(backupPos);
+       }
+
+       private double normalizeAngle(double angle) {
+               return normalizeAngle(angle, 0.0);
+       }
+
+       private double normalizeAngle(double angle, double base) {
+               while (angle < base) {
+                       angle += 2.0 * Math.PI;
+               }
+               while (angle >= (2.0 * Math.PI) - base) {
+                       angle -= 2.0 * Math.PI;
+               }
+               return angle;
+       }
+
+       public void UIThicknessBasePairs(ArrayList<ModeleBP> bases) {
+               final VueBPThickness vbpt = new VueBPThickness(_vp, bases);
+               Runnable cancel = new Runnable() {
+
+                       @Override
+                       public void run() {
+                               vbpt.restoreThicknesses();
+                               _vp.repaint();
+                       }
+                       
+               };
+               showConfirmDialog(vbpt.getPanel(), "Set base pair(s) thickness", null, cancel, cancel);
+       }
+       
+       
+
+}
diff --git a/srcjar/fr/orsay/lri/varna/views/VueZoom.java b/srcjar/fr/orsay/lri/varna/views/VueZoom.java
new file mode 100644 (file)
index 0000000..08b75f9
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
+ Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
+ electronic mail : Yann.Ponty@lri.fr
+ paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France
+
+ This file is part of VARNA version 3.1.
+ VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with VARNA version 3.1.
+ If not, see http://www.gnu.org/licenses.
+ */
+package fr.orsay.lri.varna.views;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import fr.orsay.lri.varna.VARNAPanel;
+import fr.orsay.lri.varna.controlers.ControleurSliderLabel;
+import fr.orsay.lri.varna.controlers.ControleurZoom;
+import fr.orsay.lri.varna.models.VARNAConfig;
+
+public class VueZoom implements ChangeListener {
+
+       private VARNAPanel _vp;
+       private JSlider zoomSlider, zoomAmountSlider;
+       private JPanel panel;
+
+       public VueZoom(VARNAPanel vp) {
+               _vp = vp;
+
+               JPanel pup = new JPanel();
+               JPanel pdown = new JPanel();
+               panel = new JPanel();
+               panel.setLayout(new GridLayout(2, 1));
+               pup.setLayout(new FlowLayout(FlowLayout.LEFT));
+               pdown.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+               zoomSlider = new JSlider(JSlider.HORIZONTAL,
+                               (int) (VARNAConfig.MIN_ZOOM * 100),
+                               (int) (VARNAConfig.MAX_ZOOM * 100), (int) (_vp.getZoom() * 100));
+               // Turn on labels at major tick marks.
+               zoomSlider.setMajorTickSpacing(2000);
+               zoomSlider.setMinorTickSpacing(500);
+               zoomSlider.setPaintTicks(true);
+               zoomSlider.setPaintLabels(true);
+               zoomSlider.setPreferredSize(new Dimension(250, zoomSlider
+                               .getPreferredSize().height));
+               zoomSlider.addChangeListener(new ControleurZoom(this));
+
+               JLabel zoomValueLabel = new JLabel(String.valueOf(_vp.getZoom()));
+               zoomValueLabel.setPreferredSize(new Dimension(50, zoomValueLabel
+                               .getPreferredSize().height));
+               zoomSlider.addChangeListener(new ControleurSliderLabel(zoomValueLabel,
+                               true));
+
+               zoomAmountSlider = new JSlider(JSlider.HORIZONTAL,
+                               (int) (VARNAConfig.MIN_AMOUNT * 100),
+                               (int) (VARNAConfig.MAX_AMOUNT * 100), (int) (_vp
+                                               .getZoomIncrement() * 100));
+               // Turn on labels at major tick marks.
+               zoomAmountSlider.setMajorTickSpacing(50);
+               zoomAmountSlider.setMinorTickSpacing(10);
+               zoomAmountSlider.setPaintTicks(true);
+               zoomAmountSlider.setPaintLabels(true);
+               zoomAmountSlider.setPreferredSize(new Dimension(200, zoomAmountSlider
+                               .getPreferredSize().height));
+
+               JLabel zoomAmountValueLabel = new JLabel(String.valueOf(_vp
+                               .getZoomIncrement()));
+               zoomAmountValueLabel.setPreferredSize(new Dimension(50,
+                               zoomAmountValueLabel.getPreferredSize().height));
+               zoomAmountSlider.addChangeListener(new ControleurSliderLabel(
+                               zoomAmountValueLabel, true));
+               zoomAmountSlider.addChangeListener(this);
+
+               JLabel labelZ = new JLabel("Zoom:");
+               JLabel labelA = new JLabel("Increment:");
+
+               pup.add(labelZ);
+               pup.add(zoomSlider);
+               pup.add(zoomValueLabel);
+               pdown.add(labelA);
+               pdown.add(zoomAmountSlider);
+               pdown.add(zoomAmountValueLabel);
+               panel.add(pup);
+               panel.add(pdown);
+       }
+
+       public JPanel getPanel() {
+               return panel;
+       }
+
+       public double getZoom() {
+               return zoomSlider.getValue() / 100.0;
+       }
+
+       public double getZoomAmount() {
+               return zoomAmountSlider.getValue() / 100.0;
+       }
+
+       public VARNAPanel get_vp() {
+               return _vp;
+       }
+
+       public void stateChanged(ChangeEvent e) {
+               _vp.setZoomIncrement(getZoomAmount());
+       }
+}
diff --git a/srcjar/org/apache/log4j/Appender.java b/srcjar/org/apache/log4j/Appender.java
new file mode 100644 (file)
index 0000000..42ca4b8
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   Implement this interface for your own strategies for outputting log
+   statements.
+
+   @author Ceki G&uuml;lc&uuml; 
+*/
+public interface Appender {
+
+  /**
+     Add a filter to the end of the filter list.
+
+     @since 0.9.0
+   */
+  void addFilter(Filter newFilter);
+
+  /**
+     Returns the head Filter. The Filters are organized in a linked list
+     and so all Filters on this Appender are available through the result.
+     
+     @return the head Filter or null, if no Filters are present
+     @since 1.1
+  */
+  public
+  Filter getFilter();
+
+  /**
+     Clear the list of filters by removing all the filters in it.
+     
+     @since 0.9.0
+   */
+  public
+  void clearFilters();
+
+  /**
+     Release any resources allocated within the appender such as file
+     handles, network connections, etc.
+
+     <p>It is a programming error to append to a closed appender.
+
+     @since 0.8.4
+  */
+  public
+  void close();
+  
+  /**
+     Log in <code>Appender</code> specific way. When appropriate,
+     Loggers will call the <code>doAppend</code> method of appender
+     implementations in order to log. */
+  public
+  void doAppend(LoggingEvent event);
+
+
+  /**
+     Get the name of this appender.
+     @return name, may be null.*/
+  public
+  String getName();
+
+
+  /**
+     Set the {@link ErrorHandler} for this appender.
+
+     @since 0.9.0
+   */
+  public
+  void setErrorHandler(ErrorHandler errorHandler);
+
+  /**
+     Returns the {@link ErrorHandler} for this appender.
+
+     @since 1.1
+   */
+  public
+  ErrorHandler getErrorHandler();
+
+  /**
+     Set the {@link Layout} for this appender.
+
+     @since 0.8.1
+  */
+  public
+  void setLayout(Layout layout);
+
+  /**
+     Returns this appenders layout.
+     
+     @since 1.1
+  */
+  public
+  Layout getLayout();
+  
+
+  /**
+     Set the name of this appender. The name is used by other
+     components to identify this appender.
+
+     @since 0.8.1
+  */
+  public
+  void setName(String name);
+
+  /**
+     Configurators call this method to determine if the appender
+    requires a layout. If this method returns <code>true</code>,
+    meaning that layout is required, then the configurator will
+    configure an layout using the configuration information at its
+    disposal.  If this method returns <code>false</code>, meaning that
+    a layout is not required, then layout configuration will be
+    skipped even if there is available layout configuration
+    information at the disposal of the configurator..
+
+     <p>In the rather exceptional case, where the appender
+     implementation admits a layout but can also work without it, then
+     the appender should return <code>true</code>.
+     
+     @since 0.8.4 */
+  public
+  boolean requiresLayout();
+}
diff --git a/srcjar/org/apache/log4j/AppenderSkeleton.java b/srcjar/org/apache/log4j/AppenderSkeleton.java
new file mode 100644 (file)
index 0000000..5a98c84
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.OnlyOnceErrorHandler;
+import org.apache.log4j.helpers.LogLog;
+
+
+/** 
+ * Abstract superclass of the other appenders in the package.
+ *  
+ *  This class provides the code for common functionality, such as
+ *  support for threshold filtering and support for general filters.
+ *
+ * @since 0.8.1
+ * @author Ceki G&uuml;lc&uuml; 
+ * */
+public abstract class AppenderSkeleton implements Appender, OptionHandler {
+
+  /** The layout variable does not need to be set if the appender
+      implementation has its own layout. */
+  protected Layout layout;
+
+  /** Appenders are named. */
+  protected String name;
+
+  /**
+     There is no level threshold filtering by default.  */
+  protected Priority threshold;
+
+  /** 
+      It is assumed and enforced that errorHandler is never null.
+  */
+  protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();
+
+  /** The first filter in the filter chain. Set to <code>null</code>
+      initially. */
+  protected Filter headFilter;
+  /** The last filter in the filter chain. */
+  protected Filter tailFilter;
+
+  /**
+     Is this appender closed? 
+   */
+  protected boolean closed = false;
+
+    /**
+     * Create new instance.
+     */
+    public AppenderSkeleton() {
+        super();
+    }
+
+    /**
+     * Create new instance.
+     * Provided for compatibility with log4j 1.3.
+     *
+     * @param isActive true if appender is ready for use upon construction.
+     *                 Not used in log4j 1.2.x.
+     * @since 1.2.15
+     */
+    protected AppenderSkeleton(final boolean isActive) {
+        super();
+    }
+
+
+
+  /**
+     Derived appenders should override this method if option structure
+     requires it.  */
+  public
+  void activateOptions() {
+  }
+
+
+  /**
+     Add a filter to end of the filter list.
+
+     @since 0.9.0
+   */
+  public
+  void addFilter(Filter newFilter) {
+    if(headFilter == null) {
+      headFilter = tailFilter = newFilter;
+    } else {
+      tailFilter.setNext(newFilter);
+      tailFilter = newFilter;    
+    }
+  }
+
+  /**
+     Subclasses of <code>AppenderSkeleton</code> should implement this
+     method to perform actual logging. See also {@link #doAppend
+     AppenderSkeleton.doAppend} method.
+
+     @since 0.9.0
+  */
+  abstract
+  protected
+  void append(LoggingEvent event);
+
+
+  /**
+     Clear the filters chain.
+     
+     @since 0.9.0 */
+  public
+  void clearFilters() {
+    headFilter = tailFilter = null;
+  }
+
+  /**
+     Finalize this appender by calling the derived class'
+     <code>close</code> method.
+
+     @since 0.8.4 */
+  public
+  void finalize() {
+    // An appender might be closed then garbage collected. There is no
+    // point in closing twice.
+    if(this.closed) {
+        return;
+    }
+
+    LogLog.debug("Finalizing appender named ["+name+"].");
+    close();
+  }
+
+
+  /** 
+      Return the currently set {@link ErrorHandler} for this
+      Appender.  
+
+      @since 0.9.0 */
+  public
+  ErrorHandler getErrorHandler() {
+    return this.errorHandler;
+  }
+
+
+  /**
+     Returns the head Filter.
+     
+     @since 1.1
+  */
+  public
+  Filter getFilter() {
+    return headFilter;
+  }
+
+  /** 
+      Return the first filter in the filter chain for this
+      Appender. The return value may be <code>null</code> if no is
+      filter is set.
+      
+  */
+  public
+  final
+  Filter getFirstFilter() {
+    return headFilter;
+  }
+
+  /**
+     Returns the layout of this appender. The value may be null.
+  */
+  public
+  Layout getLayout() {
+    return layout;
+  }
+
+
+  /**
+     Returns the name of this appender.
+     @return name, may be null.
+   */
+  public
+  final
+  String getName() {
+    return this.name;
+  }
+
+  /**
+     Returns this appenders threshold level. See the {@link
+     #setThreshold} method for the meaning of this option.
+     
+     @since 1.1 */
+  public
+  Priority getThreshold() {
+    return threshold;
+  }
+
+
+  /**
+     Check whether the message level is below the appender's
+     threshold. If there is no threshold set, then the return value is
+     always <code>true</code>.
+
+  */
+  public
+  boolean isAsSevereAsThreshold(Priority priority) {
+    return ((threshold == null) || priority.isGreaterOrEqual(threshold));
+  }
+
+
+  /**
+    * This method performs threshold checks and invokes filters before
+    * delegating actual logging to the subclasses specific {@link
+    * AppenderSkeleton#append} method.
+    * */
+  public
+  synchronized 
+  void doAppend(LoggingEvent event) {
+    if(closed) {
+      LogLog.error("Attempted to append to closed appender named ["+name+"].");
+      return;
+    }
+    
+    if(!isAsSevereAsThreshold(event.getLevel())) {
+      return;
+    }
+
+    Filter f = this.headFilter;
+    
+    FILTER_LOOP:
+    while(f != null) {
+      switch(f.decide(event)) {
+      case Filter.DENY: return;
+      case Filter.ACCEPT: break FILTER_LOOP;
+      case Filter.NEUTRAL: f = f.getNext();
+      }
+    }
+    
+    this.append(event);    
+  }
+
+  /** 
+      Set the {@link ErrorHandler} for this Appender.
+      @since 0.9.0
+  */
+  public
+  synchronized
+  void setErrorHandler(ErrorHandler eh) {
+    if(eh == null) {
+      // We do not throw exception here since the cause is probably a
+      // bad config file.
+      LogLog.warn("You have tried to set a null error-handler.");
+    } else {
+      this.errorHandler = eh;
+    }
+  }
+
+  /**
+     Set the layout for this appender. Note that some appenders have
+     their own (fixed) layouts or do not use one. For example, the
+     {@link org.apache.log4j.net.SocketAppender} ignores the layout set
+     here. 
+  */
+  public
+  void setLayout(Layout layout) {
+    this.layout = layout;
+  }
+
+  
+  /**
+     Set the name of this Appender.
+   */
+  public
+  void setName(String name) {
+    this.name = name;
+  }
+
+
+  /**
+     Set the threshold level. All log events with lower level
+     than the threshold level are ignored by the appender.
+     
+     <p>In configuration files this option is specified by setting the
+     value of the <b>Threshold</b> option to a level
+     string, such as "DEBUG", "INFO" and so on.
+     
+     @since 0.8.3 */
+  public
+  void setThreshold(Priority threshold) {
+    this.threshold = threshold;
+  }  
+}
diff --git a/srcjar/org/apache/log4j/AsyncAppender.java b/srcjar/org/apache/log4j/AsyncAppender.java
new file mode 100644 (file)
index 0000000..214ffa7
--- /dev/null
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contibutors:  Aaron Greenhouse <aarong@cs.cmu.edu>
+//               Thomas Tuft Muller <ttm@online.no>
+package org.apache.log4j;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * The AsyncAppender lets users log events asynchronously.
+ * <p/>
+ * <p/>
+ * The AsyncAppender will collect the events sent to it and then dispatch them
+ * to all the appenders that are attached to it. You can attach multiple
+ * appenders to an AsyncAppender.
+ * </p>
+ * <p/>
+ * <p/>
+ * The AsyncAppender uses a separate thread to serve the events in its buffer.
+ * </p>
+ * <p/>
+ * <b>Important note:</b> The <code>AsyncAppender</code> can only be script
+ * configured using the {@link org.apache.log4j.xml.DOMConfigurator}.
+ * </p>
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Curt Arnold
+ * @since 0.9.1
+ */
+public class AsyncAppender extends AppenderSkeleton
+  implements AppenderAttachable {
+  /**
+   * The default buffer size is set to 128 events.
+   */
+  public static final int DEFAULT_BUFFER_SIZE = 128;
+
+  /**
+   * Event buffer, also used as monitor to protect itself and
+   * discardMap from simulatenous modifications.
+   */
+  private final List buffer = new ArrayList();
+
+  /**
+   * Map of DiscardSummary objects keyed by logger name.
+   */
+  private final Map discardMap = new HashMap();
+
+  /**
+   * Buffer size.
+   */
+  private int bufferSize = DEFAULT_BUFFER_SIZE;
+
+  /** Nested appenders. */
+  AppenderAttachableImpl aai;
+
+  /**
+   * Nested appenders.
+   */
+  private final AppenderAttachableImpl appenders;
+
+  /**
+   * Dispatcher.
+   */
+  private final Thread dispatcher;
+
+  /**
+   * Should location info be included in dispatched messages.
+   */
+  private boolean locationInfo = false;
+
+  /**
+   * Does appender block when buffer is full.
+   */
+  private boolean blocking = true;
+
+  /**
+   * Create new instance.
+   */
+  public AsyncAppender() {
+    appenders = new AppenderAttachableImpl();
+
+    //
+    //   only set for compatibility
+    aai = appenders;
+
+    dispatcher =
+      new Thread(new Dispatcher(this, buffer, discardMap, appenders));
+
+    // It is the user's responsibility to close appenders before
+    // exiting.
+    dispatcher.setDaemon(true);
+
+    // set the dispatcher priority to lowest possible value
+    //        dispatcher.setPriority(Thread.MIN_PRIORITY);
+    dispatcher.setName("AsyncAppender-Dispatcher-" + dispatcher.getName());
+    dispatcher.start();
+  }
+
+  /**
+   * Add appender.
+   *
+   * @param newAppender appender to add, may not be null.
+   */
+  public void addAppender(final Appender newAppender) {
+    synchronized (appenders) {
+      appenders.addAppender(newAppender);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void append(final LoggingEvent event) {
+    //
+    //   if dispatcher thread has died then
+    //      append subsequent events synchronously
+    //   See bug 23021
+    if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) {
+      synchronized (appenders) {
+        appenders.appendLoopOnAppenders(event);
+      }
+
+      return;
+    }
+
+    // Set the NDC and thread name for the calling thread as these
+    // LoggingEvent fields were not set at event creation time.
+    event.getNDC();
+    event.getThreadName();
+    // Get a copy of this thread's MDC.
+    event.getMDCCopy();
+    if (locationInfo) {
+      event.getLocationInformation();
+    }
+    event.getRenderedMessage();
+    event.getThrowableStrRep();
+
+    synchronized (buffer) {
+      while (true) {
+        int previousSize = buffer.size();
+
+        if (previousSize < bufferSize) {
+          buffer.add(event);
+
+          //
+          //   if buffer had been empty
+          //       signal all threads waiting on buffer
+          //       to check their conditions.
+          //
+          if (previousSize == 0) {
+            buffer.notifyAll();
+          }
+
+          break;
+        }
+
+        //
+        //   Following code is only reachable if buffer is full
+        //
+        //
+        //   if blocking and thread is not already interrupted
+        //      and not the dispatcher then
+        //      wait for a buffer notification
+        boolean discard = true;
+        if (blocking
+                && !Thread.interrupted()
+                && Thread.currentThread() != dispatcher) {
+          try {
+            buffer.wait();
+            discard = false;
+          } catch (InterruptedException e) {
+            //
+            //  reset interrupt status so
+            //    calling code can see interrupt on
+            //    their next wait or sleep.
+            Thread.currentThread().interrupt();
+          }
+        }
+
+        //
+        //   if blocking is false or thread has been interrupted
+        //   add event to discard map.
+        //
+        if (discard) {
+          String loggerName = event.getLoggerName();
+          DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName);
+
+          if (summary == null) {
+            summary = new DiscardSummary(event);
+            discardMap.put(loggerName, summary);
+          } else {
+            summary.add(event);
+          }
+
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Close this <code>AsyncAppender</code> by interrupting the dispatcher
+   * thread which will process all pending events before exiting.
+   */
+  public void close() {
+    /**
+     * Set closed flag and notify all threads to check their conditions.
+     * Should result in dispatcher terminating.
+     */
+    synchronized (buffer) {
+      closed = true;
+      buffer.notifyAll();
+    }
+
+    try {
+      dispatcher.join();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      org.apache.log4j.helpers.LogLog.error(
+        "Got an InterruptedException while waiting for the "
+        + "dispatcher to finish.", e);
+    }
+
+    //
+    //    close all attached appenders.
+    //
+    synchronized (appenders) {
+      Enumeration iter = appenders.getAllAppenders();
+
+      if (iter != null) {
+        while (iter.hasMoreElements()) {
+          Object next = iter.nextElement();
+
+          if (next instanceof Appender) {
+            ((Appender) next).close();
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get iterator over attached appenders.
+   * @return iterator or null if no attached appenders.
+   */
+  public Enumeration getAllAppenders() {
+    synchronized (appenders) {
+      return appenders.getAllAppenders();
+    }
+  }
+
+  /**
+   * Get appender by name.
+   *
+   * @param name name, may not be null.
+   * @return matching appender or null.
+   */
+  public Appender getAppender(final String name) {
+    synchronized (appenders) {
+      return appenders.getAppender(name);
+    }
+  }
+
+  /**
+   * Gets whether the location of the logging request call
+   * should be captured.
+   *
+   * @return the current value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+   * Determines if specified appender is attached.
+   * @param appender appender.
+   * @return true if attached.
+   */
+  public boolean isAttached(final Appender appender) {
+    synchronized (appenders) {
+      return appenders.isAttached(appender);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  /**
+   * Removes and closes all attached appenders.
+   */
+  public void removeAllAppenders() {
+    synchronized (appenders) {
+      appenders.removeAllAppenders();
+    }
+  }
+
+  /**
+   * Removes an appender.
+   * @param appender appender to remove.
+   */
+  public void removeAppender(final Appender appender) {
+    synchronized (appenders) {
+      appenders.removeAppender(appender);
+    }
+  }
+
+  /**
+   * Remove appender by name.
+   * @param name name.
+   */
+  public void removeAppender(final String name) {
+    synchronized (appenders) {
+      appenders.removeAppender(name);
+    }
+  }
+
+  /**
+   * The <b>LocationInfo</b> option takes a boolean value. By default, it is
+   * set to false which means there will be no effort to extract the location
+   * information related to the event. As a result, the event that will be
+   * ultimately logged will likely to contain the wrong location information
+   * (if present in the log format).
+   * <p/>
+   * <p/>
+   * Location information extraction is comparatively very slow and should be
+   * avoided unless performance is not a concern.
+   * </p>
+   * @param flag true if location information should be extracted.
+   */
+  public void setLocationInfo(final boolean flag) {
+    locationInfo = flag;
+  }
+
+  /**
+   * Sets the number of messages allowed in the event buffer
+   * before the calling thread is blocked (if blocking is true)
+   * or until messages are summarized and discarded.  Changing
+   * the size will not affect messages already in the buffer.
+   *
+   * @param size buffer size, must be positive.
+   */
+  public void setBufferSize(final int size) {
+    //
+    //   log4j 1.2 would throw exception if size was negative
+    //      and deadlock if size was zero.
+    //
+    if (size < 0) {
+      throw new java.lang.NegativeArraySizeException("size");
+    }
+
+    synchronized (buffer) {
+      //
+      //   don't let size be zero.
+      //
+      bufferSize = (size < 1) ? 1 : size;
+      buffer.notifyAll();
+    }
+  }
+
+  /**
+   * Gets the current buffer size.
+   * @return the current value of the <b>BufferSize</b> option.
+   */
+  public int getBufferSize() {
+    return bufferSize;
+  }
+
+  /**
+   * Sets whether appender should wait if there is no
+   * space available in the event buffer or immediately return.
+   *
+   * @since 1.2.14
+   * @param value true if appender should wait until available space in buffer.
+   */
+  public void setBlocking(final boolean value) {
+    synchronized (buffer) {
+      blocking = value;
+      buffer.notifyAll();
+    }
+  }
+
+  /**
+   * Gets whether appender should block calling thread when buffer is full.
+   * If false, messages will be counted by logger and a summary
+   * message appended after the contents of the buffer have been appended.
+   *
+   * @since 1.2.14
+   * @return true if calling thread will be blocked when buffer is full.
+   */
+  public boolean getBlocking() {
+    return blocking;
+  }
+
+  /**
+   * Summary of discarded logging events for a logger.
+   */
+  private static final class DiscardSummary {
+    /**
+     * First event of the highest severity.
+     */
+    private LoggingEvent maxEvent;
+
+    /**
+     * Total count of messages discarded.
+     */
+    private int count;
+
+    /**
+     * Create new instance.
+     *
+     * @param event event, may not be null.
+     */
+    public DiscardSummary(final LoggingEvent event) {
+      maxEvent = event;
+      count = 1;
+    }
+
+    /**
+     * Add discarded event to summary.
+     *
+     * @param event event, may not be null.
+     */
+    public void add(final LoggingEvent event) {
+      if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) {
+        maxEvent = event;
+      }
+
+      count++;
+    }
+
+    /**
+     * Create event with summary information.
+     *
+     * @return new event.
+     */
+    public LoggingEvent createEvent() {
+      String msg =
+        MessageFormat.format(
+          "Discarded {0} messages due to full event buffer including: {1}",
+          new Object[] { new Integer(count), maxEvent.getMessage() });
+
+      return new LoggingEvent(
+              "org.apache.log4j.AsyncAppender.DONT_REPORT_LOCATION",
+              Logger.getLogger(maxEvent.getLoggerName()),
+              maxEvent.getLevel(),
+              msg,
+              null);
+    }
+  }
+
+  /**
+   * Event dispatcher.
+   */
+  private static class Dispatcher implements Runnable {
+    /**
+     * Parent AsyncAppender.
+     */
+    private final AsyncAppender parent;
+
+    /**
+     * Event buffer.
+     */
+    private final List buffer;
+
+    /**
+     * Map of DiscardSummary keyed by logger name.
+     */
+    private final Map discardMap;
+
+    /**
+     * Wrapped appenders.
+     */
+    private final AppenderAttachableImpl appenders;
+
+    /**
+     * Create new instance of dispatcher.
+     *
+     * @param parent     parent AsyncAppender, may not be null.
+     * @param buffer     event buffer, may not be null.
+     * @param discardMap discard map, may not be null.
+     * @param appenders  appenders, may not be null.
+     */
+    public Dispatcher(
+      final AsyncAppender parent, final List buffer, final Map discardMap,
+      final AppenderAttachableImpl appenders) {
+
+      this.parent = parent;
+      this.buffer = buffer;
+      this.appenders = appenders;
+      this.discardMap = discardMap;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run() {
+      boolean isActive = true;
+
+      //
+      //   if interrupted (unlikely), end thread
+      //
+      try {
+        //
+        //   loop until the AsyncAppender is closed.
+        //
+        while (isActive) {
+          LoggingEvent[] events = null;
+
+          //
+          //   extract pending events while synchronized
+          //       on buffer
+          //
+          synchronized (buffer) {
+            int bufferSize = buffer.size();
+            isActive = !parent.closed;
+
+            while ((bufferSize == 0) && isActive) {
+              buffer.wait();
+              bufferSize = buffer.size();
+              isActive = !parent.closed;
+            }
+
+            if (bufferSize > 0) {
+              events = new LoggingEvent[bufferSize + discardMap.size()];
+              buffer.toArray(events);
+
+              //
+              //   add events due to buffer overflow
+              //
+              int index = bufferSize;
+
+              for (
+                Iterator iter = discardMap.values().iterator();
+                  iter.hasNext();) {
+                events[index++] = ((DiscardSummary) iter.next()).createEvent();
+              }
+
+              //
+              //    clear buffer and discard map
+              //
+              buffer.clear();
+              discardMap.clear();
+
+              //
+              //    allow blocked appends to continue
+              buffer.notifyAll();
+            }
+          }
+
+          //
+          //   process events after lock on buffer is released.
+          //
+          if (events != null) {
+            for (int i = 0; i < events.length; i++) {
+              synchronized (appenders) {
+                appenders.appendLoopOnAppenders(events[i]);
+              }
+            }
+          }
+        }
+      } catch (InterruptedException ex) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/BasicConfigurator.java b/srcjar/org/apache/log4j/BasicConfigurator.java
new file mode 100644 (file)
index 0000000..2d859cf
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contibutors: "Luke Blanshard" <Luke@quiq.com>
+//              "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
+//              "Muly Oved" <mulyoved@hotmail.com>
+
+package org.apache.log4j;
+
+
+/**
+   Use this class to quickly configure the package.
+
+   <p>For file based configuration see {@link
+   PropertyConfigurator}. For XML based configuration see {@link
+   org.apache.log4j.xml.DOMConfigurator DOMConfigurator}.
+
+   @since 0.8.1
+   @author Ceki G&uuml;lc&uuml; */
+public class BasicConfigurator {
+
+  protected BasicConfigurator() {
+  }
+
+  /**
+     Add a {@link ConsoleAppender} that uses {@link PatternLayout}
+     using the {@link PatternLayout#TTCC_CONVERSION_PATTERN} and
+     prints to <code>System.out</code> to the root category.  */
+  static
+  public
+  void configure() {
+    Logger root = Logger.getRootLogger();
+    root.addAppender(new ConsoleAppender(
+           new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
+  }
+
+  /**
+     Add <code>appender</code> to the root category.
+     @param appender The appender to add to the root category.
+  */
+  static
+  public
+  void configure(Appender appender) {
+    Logger root = Logger.getRootLogger();
+    root.addAppender(appender);
+  }
+
+  /**
+     Reset the default hierarchy to its defaut. It is equivalent to
+     calling
+     <code>Category.getDefaultHierarchy().resetConfiguration()</code>.
+
+     See {@link Hierarchy#resetConfiguration()} for more details.  */
+  public
+  static
+  void resetConfiguration() {
+    LogManager.resetConfiguration();
+  }
+}
diff --git a/srcjar/org/apache/log4j/Category.java b/srcjar/org/apache/log4j/Category.java
new file mode 100644 (file)
index 0000000..9b304bc
--- /dev/null
@@ -0,0 +1,1096 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contibutors: Alex Blewitt <Alex.Blewitt@ioshq.com>
+//              Markus Oestreicher <oes@zurich.ibm.com>
+//              Frank Hoering <fhr@zurich.ibm.com>
+//              Nelson Minar <nelson@media.mit.edu>
+//              Jim Cakalic <jim_cakalic@na.biomerieux.com>
+//              Avy Sharell <asharell@club-internet.fr>
+//              Ciaran Treanor <ciaran@xelector.com>
+//              Jeff Turner <jeff@socialchange.net.au>
+//              Michael Horwitz <MHorwitz@siemens.co.za>
+//              Calvin Chan <calvin.chan@hic.gov.au>
+//              Aaron Greenhouse <aarong@cs.cmu.edu>
+//              Beat Meier <bmeier@infovia.com.ar>
+//              Colin Sampaleanu <colinml1@exis.com>
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.helpers.NullEnumeration;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+
+import java.util.Enumeration;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Vector;
+
+
+/**
+  * <font color="#AA2222"><b>This class has been deprecated and
+  * replaced by the {@link Logger} <em>subclass</em></b></font>. It
+  * will be kept around to preserve backward compatibility until mid
+  * 2003.
+  * 
+  * <p><code>Logger</code> is a subclass of Category, i.e. it extends
+  * Category. In other words, a logger <em>is</em> a category. Thus,
+  * all operations that can be performed on a category can be
+  * performed on a logger. Internally, whenever log4j is asked to
+  * produce a Category object, it will instead produce a Logger
+  * object. Log4j 1.2 will <em>never</em> produce Category objects but
+  * only <code>Logger</code> instances. In order to preserve backward
+  * compatibility, methods that previously accepted category objects
+  * still continue to accept category objects.
+  * 
+  * <p>For example, the following are all legal and will work as
+  * expected.
+  * 
+   <pre>
+    &nbsp;&nbsp;&nbsp;// Deprecated form:
+    &nbsp;&nbsp;&nbsp;Category cat = Category.getInstance("foo.bar")
+   
+    &nbsp;&nbsp;&nbsp;// Preferred form for retrieving loggers:
+    &nbsp;&nbsp;&nbsp;Logger logger = Logger.getLogger("foo.bar")
+   </pre>
+   
+  *  <p>The first form is deprecated and should be avoided.
+  * 
+  *  <p><b>There is absolutely no need for new client code to use or
+  *  refer to the <code>Category</code> class.</b> Whenever possible,
+  *  please avoid referring to it or using it.
+  * 
+  * <p>See the <a href="../../../../manual.html">short manual</a> for an
+  * introduction on this class.
+  * <p>
+  * See the document entitled <a href="http://www.qos.ch/logging/preparingFor13.html">preparing
+  *  for log4j 1.3</a> for a more detailed discussion.
+  *
+  * @author Ceki G&uuml;lc&uuml;
+  * @author Anders Kristensen 
+  */
+public class Category implements AppenderAttachable {
+
+  /**
+     The hierarchy where categories are attached to by default.
+  */
+  //static
+  //public
+  //final Hierarchy defaultHierarchy = new Hierarchy(new
+  //                                      RootCategory(Level.DEBUG));
+
+  /**
+     The name of this category.
+  */
+  protected String   name;
+
+  /**
+     The assigned level of this category.  The
+     <code>level</code> variable need not be assigned a value in
+     which case it is inherited form the hierarchy.  */
+  volatile protected Level level;
+
+  /**
+     The parent of this category. All categories have at least one
+     ancestor which is the root category. */
+  volatile protected Category parent;
+
+  /**
+     The fully qualified name of the Category class. See also the
+     getFQCN method. */
+  private static final String FQCN = Category.class.getName();
+
+  protected ResourceBundle resourceBundle;
+
+  // Categories need to know what Hierarchy they are in
+  protected LoggerRepository repository;
+
+
+  AppenderAttachableImpl aai;
+
+  /** Additivity is set to true by default, that is children inherit
+      the appenders of their ancestors by default. If this variable is
+      set to <code>false</code> then the appenders found in the
+      ancestors of this category are not used. However, the children
+      of this category will inherit its appenders, unless the children
+      have their additivity flag set to <code>false</code> too. See
+      the user manual for more details. */
+  protected boolean additive = true;
+
+  /**
+     This constructor created a new <code>Category</code> instance and
+     sets its name.
+
+     <p>It is intended to be used by sub-classes only. You should not
+     create categories directly.
+
+     @param name The name of the category.
+  */
+  protected
+  Category(String name) {
+    this.name = name;
+  }
+
+  /**
+     Add <code>newAppender</code> to the list of appenders of this
+     Category instance.
+
+     <p>If <code>newAppender</code> is already in the list of
+     appenders, then it won't be added again.
+  */
+  synchronized
+  public
+  void addAppender(Appender newAppender) {
+    if(aai == null) {
+      aai = new AppenderAttachableImpl();
+    }
+    aai.addAppender(newAppender);
+    repository.fireAddAppenderEvent(this, newAppender);
+  }
+
+  /**
+     If <code>assertion</code> parameter is <code>false</code>, then
+     logs <code>msg</code> as an {@link #error(Object) error} statement.
+
+     <p>The <code>assert</code> method has been renamed to
+     <code>assertLog</code> because <code>assert</code> is a language
+     reserved word in JDK 1.4.
+
+     @param assertion
+     @param msg The message to print if <code>assertion</code> is
+     false.
+
+     @since 1.2 */
+  public
+  void assertLog(boolean assertion, String msg) {
+    if(!assertion) {
+        this.error(msg);
+    }
+  }
+
+
+  /**
+     Call the appenders in the hierrachy starting at
+     <code>this</code>.  If no appenders could be found, emit a
+     warning.
+
+     <p>This method calls all the appenders inherited from the
+     hierarchy circumventing any evaluation of whether to log or not
+     to log the particular log request.
+
+     @param event the event to log.  */
+  public
+  void callAppenders(LoggingEvent event) {
+    int writes = 0;
+
+    for(Category c = this; c != null; c=c.parent) {
+      // Protected against simultaneous call to addAppender, removeAppender,...
+      synchronized(c) {
+       if(c.aai != null) {
+         writes += c.aai.appendLoopOnAppenders(event);
+       }
+       if(!c.additive) {
+         break;
+       }
+      }
+    }
+
+    if(writes == 0) {
+      repository.emitNoAppenderWarning(this);
+    }
+  }
+
+  /**
+     Close all attached appenders implementing the AppenderAttachable
+     interface.
+     @since 1.0
+  */
+  synchronized
+  void closeNestedAppenders() {
+    Enumeration enumeration = this.getAllAppenders();
+    if(enumeration != null) {
+      while(enumeration.hasMoreElements()) {
+       Appender a = (Appender) enumeration.nextElement();
+       if(a instanceof AppenderAttachable) {
+         a.close();
+       }
+      }
+    }
+  }
+
+  /**
+    Log a message object with the {@link Level#DEBUG DEBUG} level.
+
+    <p>This method first checks if this category is <code>DEBUG</code>
+    enabled by comparing the level of this category with the {@link
+    Level#DEBUG DEBUG} level. If this category is
+    <code>DEBUG</code> enabled, then it converts the message object
+    (passed as parameter) to a string by invoking the appropriate
+    {@link org.apache.log4j.or.ObjectRenderer}. It then proceeds to call all the
+    registered appenders in this category and also higher in the
+    hierarchy depending on the value of the additivity flag.
+
+    <p><b>WARNING</b> Note that passing a {@link Throwable} to this
+    method will print the name of the <code>Throwable</code> but no
+    stack trace. To print a stack trace use the {@link #debug(Object,
+    Throwable)} form instead.
+
+    @param message the message object to log. */
+  public
+  void debug(Object message) {
+    if(repository.isDisabled(Level.DEBUG_INT)) {
+        return;
+    }
+    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
+      forcedLog(FQCN, Level.DEBUG, message, null);
+    }
+  }
+
+
+  /**
+   Log a message object with the <code>DEBUG</code> level including
+   the stack trace of the {@link Throwable} <code>t</code> passed as
+   parameter.
+
+   <p>See {@link #debug(Object)} form for more detailed information.
+
+   @param message the message object to log.
+   @param t the exception to log, including its stack trace.  */
+  public
+  void debug(Object message, Throwable t) {
+    if(repository.isDisabled(Level.DEBUG_INT)) {
+        return;
+    }
+    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.DEBUG, message, t);
+    }
+  }
+
+  /**
+    Log a message object with the {@link Level#ERROR ERROR} Level.
+
+    <p>This method first checks if this category is <code>ERROR</code>
+    enabled by comparing the level of this category with {@link
+    Level#ERROR ERROR} Level. If this category is <code>ERROR</code>
+    enabled, then it converts the message object passed as parameter
+    to a string by invoking the appropriate {@link
+    org.apache.log4j.or.ObjectRenderer}. It proceeds to call all the
+    registered appenders in this category and also higher in the
+    hierarchy depending on the value of the additivity flag.
+
+    <p><b>WARNING</b> Note that passing a {@link Throwable} to this
+    method will print the name of the <code>Throwable</code> but no
+    stack trace. To print a stack trace use the {@link #error(Object,
+    Throwable)} form instead.
+
+    @param message the message object to log */
+  public
+  void error(Object message) {
+    if(repository.isDisabled(Level.ERROR_INT)) {
+        return;
+    }
+    if(Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.ERROR, message, null);
+    }
+  }
+
+  /**
+   Log a message object with the <code>ERROR</code> level including
+   the stack trace of the {@link Throwable} <code>t</code> passed as
+   parameter.
+
+   <p>See {@link #error(Object)} form for more detailed information.
+
+   @param message the message object to log.
+   @param t the exception to log, including its stack trace.  */
+  public
+  void error(Object message, Throwable t) {
+    if(repository.isDisabled(Level.ERROR_INT)) {
+        return;
+    }
+    if(Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.ERROR, message, t);
+    }
+
+  }
+
+
+  /**
+     If the named category exists (in the default hierarchy) then it
+     returns a reference to the category, otherwise it returns
+     <code>null</code>.
+
+     @deprecated Please use {@link LogManager#exists} instead.
+
+     @since 0.8.5 */
+  public
+  static
+  Logger exists(String name) {
+    return LogManager.exists(name);
+  }
+
+  /**
+    Log a message object with the {@link Level#FATAL FATAL} Level.
+
+    <p>This method first checks if this category is <code>FATAL</code>
+    enabled by comparing the level of this category with {@link
+    Level#FATAL FATAL} Level. If the category is <code>FATAL</code>
+    enabled, then it converts the message object passed as parameter
+    to a string by invoking the appropriate
+    {@link org.apache.log4j.or.ObjectRenderer}. It
+    proceeds to call all the registered appenders in this category and
+    also higher in the hierarchy depending on the value of the
+    additivity flag.
+
+    <p><b>WARNING</b> Note that passing a {@link Throwable} to this
+    method will print the name of the Throwable but no stack trace. To
+    print a stack trace use the {@link #fatal(Object, Throwable)} form
+    instead.
+
+    @param message the message object to log */
+  public
+  void fatal(Object message) {
+    if(repository.isDisabled(Level.FATAL_INT)) {
+        return;
+    }
+    if(Level.FATAL.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.FATAL, message, null);
+    }
+  }
+
+  /**
+   Log a message object with the <code>FATAL</code> level including
+   the stack trace of the {@link Throwable} <code>t</code> passed as
+   parameter.
+
+   <p>See {@link #fatal(Object)} for more detailed information.
+
+   @param message the message object to log.
+   @param t the exception to log, including its stack trace.  */
+  public
+  void fatal(Object message, Throwable t) {
+    if(repository.isDisabled(Level.FATAL_INT)) {
+        return;
+    }
+    if(Level.FATAL.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.FATAL, message, t);
+    }
+  }
+
+
+  /**
+     This method creates a new logging event and logs the event
+     without further checks.  */
+  protected
+  void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
+    callAppenders(new LoggingEvent(fqcn, this, level, message, t));
+  }
+
+
+  /**
+     Get the additivity flag for this Category instance.
+  */
+  public
+  boolean getAdditivity() {
+    return additive;
+  }
+
+  /**
+     Get the appenders contained in this category as an {@link
+     Enumeration}. If no appenders can be found, then a {@link NullEnumeration}
+     is returned.
+
+     @return Enumeration An enumeration of the appenders in this category.  */
+  synchronized
+  public
+  Enumeration getAllAppenders() {
+    if(aai == null) {
+        return NullEnumeration.getInstance();
+    } else {
+        return aai.getAllAppenders();
+    }
+  }
+
+  /**
+     Look for the appender named as <code>name</code>.
+
+     <p>Return the appender with that name if in the list. Return
+     <code>null</code> otherwise.  */
+  synchronized
+  public
+  Appender getAppender(String name) {
+     if(aai == null || name == null) {
+        return null;
+    }
+
+     return aai.getAppender(name);
+  }
+
+  /**
+     Starting from this category, search the category hierarchy for a
+     non-null level and return it. Otherwise, return the level of the
+     root category.
+
+     <p>The Category class is designed so that this method executes as
+     quickly as possible.
+   */
+  public
+  Level getEffectiveLevel() {
+    for(Category c = this; c != null; c=c.parent) {
+      if(c.level != null) {
+        return c.level;
+    }
+    }
+    return null; // If reached will cause an NullPointerException.
+  }
+
+  /**
+    *
+    * @deprecated Please use the the {@link #getEffectiveLevel} method
+    * instead.  
+    * */
+  public
+  Priority getChainedPriority() {
+    for(Category c = this; c != null; c=c.parent) {
+      if(c.level != null) {
+        return c.level;
+    }
+    }
+    return null; // If reached will cause an NullPointerException.
+  }
+
+
+  /**
+     Returns all the currently defined categories in the default
+     hierarchy as an {@link java.util.Enumeration Enumeration}.
+
+     <p>The root category is <em>not</em> included in the returned
+     {@link Enumeration}.
+
+     @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
+  */
+  public
+  static
+  Enumeration getCurrentCategories() {
+    return LogManager.getCurrentLoggers();
+  }
+
+
+  /**
+     Return the default Hierarchy instance.
+
+     @deprecated Please use {@link LogManager#getLoggerRepository()} instead.
+
+     @since 1.0
+   */
+  public
+  static
+  LoggerRepository getDefaultHierarchy() {
+    return LogManager.getLoggerRepository();
+  }
+
+  /**
+     Return the the {@link Hierarchy} where this <code>Category</code>
+     instance is attached.
+
+     @deprecated Please use {@link #getLoggerRepository} instead.
+
+     @since 1.1 */
+  public
+  LoggerRepository  getHierarchy() {
+    return repository;
+  }
+
+  /**
+     Return the the {@link LoggerRepository} where this
+     <code>Category</code> is attached.
+
+     @since 1.2 */
+  public
+  LoggerRepository  getLoggerRepository() {
+    return repository;
+  }
+
+
+ /**
+  * @deprecated Make sure to use {@link Logger#getLogger(String)} instead.
+  */
+  public
+  static
+  Category getInstance(String name) {
+    return LogManager.getLogger(name);
+  }
+
+ /**
+  * @deprecated Please make sure to use {@link Logger#getLogger(Class)} instead.
+  */ 
+  public
+  static
+  Category getInstance(Class clazz) {
+    return LogManager.getLogger(clazz);
+  }
+
+
+  /**
+     Return the category name.  */
+  public
+  final
+  String getName() {
+    return name;
+  }
+
+
+  /**
+     Returns the parent of this category. Note that the parent of a
+     given category may change during the lifetime of the category.
+
+     <p>The root category will return <code>null</code>.
+
+     @since 1.2
+  */
+  final
+  public
+  Category getParent() {
+    return this.parent;
+  }
+
+
+  /**
+     Returns the assigned {@link Level}, if any, for this Category.
+
+     @return Level - the assigned Level, can be <code>null</code>.
+  */
+  final
+  public
+  Level getLevel() {
+    return this.level;
+  }
+
+  /**
+     @deprecated Please use {@link #getLevel} instead.
+  */
+  final
+  public
+  Level getPriority() {
+    return this.level;
+  }
+
+
+  /**
+   *  @deprecated Please use {@link Logger#getRootLogger()} instead.
+   */
+  final
+  public
+  static
+  Category getRoot() {
+    return LogManager.getRootLogger();
+  }
+
+  /**
+     Return the <em>inherited</em> {@link ResourceBundle} for this
+     category.
+
+     <p>This method walks the hierarchy to find the appropriate
+     resource bundle. It will return the resource bundle attached to
+     the closest ancestor of this category, much like the way
+     priorities are searched. In case there is no bundle in the
+     hierarchy then <code>null</code> is returned.
+
+     @since 0.9.0 */
+  public
+  ResourceBundle getResourceBundle() {
+    for(Category c = this; c != null; c=c.parent) {
+      if(c.resourceBundle != null) {
+        return c.resourceBundle;
+    }
+    }
+    // It might be the case that there is no resource bundle
+    return null;
+  }
+
+  /**
+     Returns the string resource coresponding to <code>key</code> in
+     this category's inherited resource bundle. See also {@link
+     #getResourceBundle}.
+
+     <p>If the resource cannot be found, then an {@link #error error}
+     message will be logged complaining about the missing resource.
+  */
+  protected
+  String getResourceBundleString(String key) {
+    ResourceBundle rb = getResourceBundle();
+    // This is one of the rare cases where we can use logging in order
+    // to report errors from within log4j.
+    if(rb == null) {
+      //if(!hierarchy.emittedNoResourceBundleWarning) {
+      //error("No resource bundle has been set for category "+name);
+      //hierarchy.emittedNoResourceBundleWarning = true;
+      //}
+      return null;
+    }
+    else {
+      try {
+       return rb.getString(key);
+      }
+      catch(MissingResourceException mre) {
+       error("No resource is associated with key \""+key+"\".");
+       return null;
+      }
+    }
+  }
+
+  /**
+    Log a message object with the {@link Level#INFO INFO} Level.
+
+    <p>This method first checks if this category is <code>INFO</code>
+    enabled by comparing the level of this category with {@link
+    Level#INFO INFO} Level. If the category is <code>INFO</code>
+    enabled, then it converts the message object passed as parameter
+    to a string by invoking the appropriate
+    {@link org.apache.log4j.or.ObjectRenderer}. It
+    proceeds to call all the registered appenders in this category and
+    also higher in the hierarchy depending on the value of the
+    additivity flag.
+
+    <p><b>WARNING</b> Note that passing a {@link Throwable} to this
+    method will print the name of the Throwable but no stack trace. To
+    print a stack trace use the {@link #info(Object, Throwable)} form
+    instead.
+
+    @param message the message object to log */
+  public
+  void info(Object message) {
+    if(repository.isDisabled(Level.INFO_INT)) {
+        return;
+    }
+    if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.INFO, message, null);
+    }
+  }
+
+  /**
+   Log a message object with the <code>INFO</code> level including
+   the stack trace of the {@link Throwable} <code>t</code> passed as
+   parameter.
+
+   <p>See {@link #info(Object)} for more detailed information.
+
+   @param message the message object to log.
+   @param t the exception to log, including its stack trace.  */
+  public
+  void info(Object message, Throwable t) {
+    if(repository.isDisabled(Level.INFO_INT)) {
+        return;
+    }
+    if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.INFO, message, t);
+    }
+  }
+
+  /**
+     Is the appender passed as parameter attached to this category?
+   */
+  public
+  boolean isAttached(Appender appender) {
+    if(appender == null || aai == null) {
+        return false;
+    } else {
+      return aai.isAttached(appender);
+    }
+  }
+
+  /**
+    *  Check whether this category is enabled for the <code>DEBUG</code>
+    *  Level.
+    *
+    *  <p> This function is intended to lessen the computational cost of
+    *  disabled log debug statements.
+    *
+    *  <p> For some <code>cat</code> Category object, when you write,
+    *  <pre>
+    *      cat.debug("This is entry number: " + i );
+    *  </pre>
+    *
+    *  <p>You incur the cost constructing the message, concatenatiion in
+    *  this case, regardless of whether the message is logged or not.
+    *
+    *  <p>If you are worried about speed, then you should write
+    *  <pre>
+    *   if(cat.isDebugEnabled()) {
+    *     cat.debug("This is entry number: " + i );
+    *   }
+    *  </pre>
+    *
+    *  <p>This way you will not incur the cost of parameter
+    *  construction if debugging is disabled for <code>cat</code>. On
+    *  the other hand, if the <code>cat</code> is debug enabled, you
+    *  will incur the cost of evaluating whether the category is debug
+    *  enabled twice. Once in <code>isDebugEnabled</code> and once in
+    *  the <code>debug</code>.  This is an insignificant overhead
+    *  since evaluating a category takes about 1%% of the time it
+    *  takes to actually log.
+    *
+    *  @return boolean - <code>true</code> if this category is debug
+    *  enabled, <code>false</code> otherwise.
+    *   */
+  public
+  boolean isDebugEnabled() {
+    if(repository.isDisabled( Level.DEBUG_INT)) {
+        return false;
+    }
+    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
+  }
+
+  /**
+     Check whether this category is enabled for a given {@link
+     Level} passed as parameter.
+
+     See also {@link #isDebugEnabled}.
+
+     @return boolean True if this category is enabled for <code>level</code>.
+  */
+  public
+  boolean isEnabledFor(Priority level) {
+    if(repository.isDisabled(level.level)) {
+        return false;
+    }
+    return level.isGreaterOrEqual(this.getEffectiveLevel());
+  }
+
+  /**
+    Check whether this category is enabled for the info Level.
+    See also {@link #isDebugEnabled}.
+
+    @return boolean - <code>true</code> if this category is enabled
+    for level info, <code>false</code> otherwise.
+  */
+  public
+  boolean isInfoEnabled() {
+    if(repository.isDisabled(Level.INFO_INT)) {
+        return false;
+    }
+    return Level.INFO.isGreaterOrEqual(this.getEffectiveLevel());
+  }
+
+
+  /**
+     Log a localized message. The user supplied parameter
+     <code>key</code> is replaced by its localized version from the
+     resource bundle.
+
+     @see #setResourceBundle
+
+     @since 0.8.4 */
+  public
+  void l7dlog(Priority priority, String key, Throwable t) {
+    if(repository.isDisabled(priority.level)) {
+      return;
+    }
+    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
+      String msg = getResourceBundleString(key);
+      // if message corresponding to 'key' could not be found in the
+      // resource bundle, then default to 'key'.
+      if(msg == null) {
+       msg = key;
+      }
+      forcedLog(FQCN, priority, msg, t);
+    }
+  }
+  /**
+     Log a localized and parameterized message. First, the user
+     supplied <code>key</code> is searched in the resource
+     bundle. Next, the resulting pattern is formatted using
+     {@link java.text.MessageFormat#format(String,Object[])} method with the
+     user supplied object array <code>params</code>.
+
+     @since 0.8.4
+  */
+  public
+  void l7dlog(Priority priority, String key,  Object[] params, Throwable t) {
+    if(repository.isDisabled(priority.level)) {
+      return;
+    }
+    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
+      String pattern = getResourceBundleString(key);
+      String msg;
+      if(pattern == null) {
+        msg = key;
+    } else {
+        msg = java.text.MessageFormat.format(pattern, params);
+    }
+      forcedLog(FQCN, priority, msg, t);
+    }
+  }
+
+  /**
+     This generic form is intended to be used by wrappers.
+   */
+  public
+  void log(Priority priority, Object message, Throwable t) {
+    if(repository.isDisabled(priority.level)) {
+      return;
+    }
+    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, priority, message, t);
+    }
+  }
+
+ /**
+    This generic form is intended to be used by wrappers.
+ */
+  public
+  void log(Priority priority, Object message) {
+    if(repository.isDisabled(priority.level)) {
+      return;
+    }
+    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, priority, message, null);
+    }
+  }
+
+  /**
+
+     This is the most generic printing method. It is intended to be
+     invoked by <b>wrapper</b> classes.
+
+     @param callerFQCN The wrapper class' fully qualified class name.
+     @param level The level of the logging request.
+     @param message The message of the logging request.
+     @param t The throwable of the logging request, may be null.  */
+  public
+  void log(String callerFQCN, Priority level, Object message, Throwable t) {
+    if(repository.isDisabled(level.level)) {
+      return;
+    }
+    if(level.isGreaterOrEqual(this.getEffectiveLevel())) {
+      forcedLog(callerFQCN, level, message, t);
+    }
+  }
+
+    /**
+      *  LoggerRepository forgot the fireRemoveAppenderEvent method,
+      *     if using the stock Hierarchy implementation, then call its fireRemove.
+      *     Custom repositories can implement HierarchyEventListener if they
+      *     want remove notifications.
+     * @param appender appender, may be null.
+     */
+   private void fireRemoveAppenderEvent(final Appender appender) {
+       if (appender != null) {
+         if (repository instanceof Hierarchy) {
+           ((Hierarchy) repository).fireRemoveAppenderEvent(this, appender);
+         } else if (repository instanceof HierarchyEventListener) {
+             ((HierarchyEventListener) repository).removeAppenderEvent(this, appender);
+         }
+       }
+   }
+
+  /**
+     Remove all previously added appenders from this Category
+     instance.
+
+     <p>This is useful when re-reading configuration information.
+  */
+  synchronized
+  public
+  void removeAllAppenders() {
+    if(aai != null) {
+      Vector appenders = new Vector();
+      for (Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements();) {
+          appenders.add(iter.nextElement());
+      }
+      aai.removeAllAppenders();
+      for(Enumeration iter = appenders.elements(); iter.hasMoreElements();) {
+          fireRemoveAppenderEvent((Appender) iter.nextElement());
+      }
+      aai = null;
+    }
+  }
+
+
+  /**
+     Remove the appender passed as parameter form the list of appenders.
+
+     @since 0.8.2
+  */
+  synchronized
+  public
+  void removeAppender(Appender appender) {
+    if(appender == null || aai == null) {
+        return;
+    }
+    boolean wasAttached = aai.isAttached(appender);
+    aai.removeAppender(appender);
+    if (wasAttached) {
+        fireRemoveAppenderEvent(appender);
+    }
+  }
+
+  /**
+     Remove the appender with the name passed as parameter form the
+     list of appenders.
+
+     @since 0.8.2 */
+  synchronized
+  public
+  void removeAppender(String name) {
+    if(name == null || aai == null) {
+        return;
+    }
+    Appender appender = aai.getAppender(name);
+    aai.removeAppender(name);
+    if (appender != null) {
+        fireRemoveAppenderEvent(appender);
+    }
+  }
+
+  /**
+     Set the additivity flag for this Category instance.
+     @since 0.8.1
+   */
+  public
+  void setAdditivity(boolean additive) {
+    this.additive = additive;
+  }
+
+  /**
+     Only the Hiearchy class can set the hiearchy of a
+     category. Default package access is MANDATORY here.  */
+  final
+  void setHierarchy(LoggerRepository repository) {
+    this.repository = repository;
+  }
+
+  /**
+     Set the level of this Category. If you are passing any of
+     <code>Level.DEBUG</code>, <code>Level.INFO</code>,
+     <code>Level.WARN</code>, <code>Level.ERROR</code>,
+     <code>Level.FATAL</code> as a parameter, you need to case them as
+     Level.
+
+     <p>As in <pre> &nbsp;&nbsp;&nbsp;logger.setLevel((Level) Level.DEBUG); </pre>
+
+
+     <p>Null values are admitted.  */
+  public
+  void setLevel(Level level) {
+    this.level = level;
+  }
+
+
+  /**
+     Set the level of this Category.
+
+     <p>Null values are admitted.
+
+     @deprecated Please use {@link #setLevel} instead.
+  */
+  public
+  void setPriority(Priority priority) {
+    this.level = (Level) priority;
+  }
+
+
+  /**
+     Set the resource bundle to be used with localized logging
+     methods {@link #l7dlog(Priority,String,Throwable)} and {@link
+     #l7dlog(Priority,String,Object[],Throwable)}.
+
+     @since 0.8.4
+   */
+  public
+  void setResourceBundle(ResourceBundle bundle) {
+    resourceBundle = bundle;
+  }
+
+  /**
+     Calling this method will <em>safely</em> close and remove all
+     appenders in all the categories including root contained in the
+     default hierachy.
+
+     <p>Some appenders such as {@link org.apache.log4j.net.SocketAppender}
+     and {@link AsyncAppender} need to be closed before the
+     application exists. Otherwise, pending logging events might be
+     lost.
+
+     <p>The <code>shutdown</code> method is careful to close nested
+     appenders before closing regular appenders. This is allows
+     configurations where a regular appender is attached to a category
+     and again to a nested appender.
+
+     @deprecated Please use {@link LogManager#shutdown()} instead.
+
+     @since 1.0
+  */
+  public
+  static
+  void shutdown() {
+    LogManager.shutdown();
+  }
+
+
+  /**
+    Log a message object with the {@link Level#WARN WARN} Level.
+
+    <p>This method first checks if this category is <code>WARN</code>
+    enabled by comparing the level of this category with {@link
+    Level#WARN WARN} Level. If the category is <code>WARN</code>
+    enabled, then it converts the message object passed as parameter
+    to a string by invoking the appropriate
+    {@link org.apache.log4j.or.ObjectRenderer}. It
+    proceeds to call all the registered appenders in this category and
+    also higher in the hieararchy depending on the value of the
+    additivity flag.
+
+    <p><b>WARNING</b> Note that passing a {@link Throwable} to this
+    method will print the name of the Throwable but no stack trace. To
+    print a stack trace use the {@link #warn(Object, Throwable)} form
+    instead.  <p>
+
+    @param message the message object to log.  */
+  public
+  void warn(Object message) {
+    if(repository.isDisabled( Level.WARN_INT)) {
+        return;
+    }
+
+    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.WARN, message, null);
+    }
+  }
+
+  /**
+   Log a message with the <code>WARN</code> level including the
+   stack trace of the {@link Throwable} <code>t</code> passed as
+   parameter.
+
+   <p>See {@link #warn(Object)} for more detailed information.
+
+   @param message the message object to log.
+   @param t the exception to log, including its stack trace.  */
+  public
+  void warn(Object message, Throwable t) {
+    if(repository.isDisabled(Level.WARN_INT)) {
+        return;
+    }
+    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.WARN, message, t);
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/CategoryKey.java b/srcjar/org/apache/log4j/CategoryKey.java
new file mode 100644 (file)
index 0000000..e6f96c6
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+/**
+   CategoryKey is a wrapper for String that apparently accellerated
+   hash table lookup in early JVM's.
+   @author Ceki G&uuml;lc&uuml; 
+*/
+class CategoryKey {
+
+  String   name;  
+  int hashCache;
+
+  CategoryKey(String name) {
+    this.name = name;
+    hashCache = name.hashCode();
+  }
+
+  final
+  public  
+  int hashCode() {
+    return hashCache;
+  }
+
+  final
+  public
+  boolean equals(Object rArg) {
+    if(this == rArg) {
+        return true;
+    }
+    
+    if(rArg != null && CategoryKey.class == rArg.getClass()) {
+        return  name.equals(((CategoryKey)rArg ).name);
+    } else {
+        return false;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/ConsoleAppender.java b/srcjar/org/apache/log4j/ConsoleAppender.java
new file mode 100644 (file)
index 0000000..b44fd5f
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import org.apache.log4j.helpers.LogLog;
+
+/**
+  * ConsoleAppender appends log events to <code>System.out</code> or
+  * <code>System.err</code> using a layout specified by the user. The
+  * default target is <code>System.out</code>.
+  *
+  * @author Ceki G&uuml;lc&uuml; 
+  * @author Curt Arnold
+  * @since 1.1 */
+public class ConsoleAppender extends WriterAppender {
+
+  public static final String SYSTEM_OUT = "System.out";
+  public static final String SYSTEM_ERR = "System.err";
+
+  protected String target = SYSTEM_OUT;
+
+  /**
+   *  Determines if the appender honors reassignments of System.out
+   *  or System.err made after configuration.
+   */
+  private boolean follow = false;
+
+  /**
+    * Constructs an unconfigured appender.
+    */
+  public ConsoleAppender() {
+  }
+
+    /**
+     * Creates a configured appender.
+     *
+     * @param layout layout, may not be null.
+     */
+  public ConsoleAppender(Layout layout) {
+    this(layout, SYSTEM_OUT);
+  }
+
+    /**
+     *   Creates a configured appender.
+     * @param layout layout, may not be null.
+     * @param target target, either "System.err" or "System.out".
+     */
+  public ConsoleAppender(Layout layout, String target) {
+    setLayout(layout);
+    setTarget(target);
+    activateOptions();
+  }
+
+  /**
+   *  Sets the value of the <b>Target</b> option. Recognized values
+   *  are "System.out" and "System.err". Any other value will be
+   *  ignored.  
+   * */
+  public
+  void setTarget(String value) {
+    String v = value.trim();
+
+    if (SYSTEM_OUT.equalsIgnoreCase(v)) {
+      target = SYSTEM_OUT;
+    } else if (SYSTEM_ERR.equalsIgnoreCase(v)) {
+      target = SYSTEM_ERR;
+    } else {
+      targetWarn(value);
+    }
+  }
+
+  /**
+   * Returns the current value of the <b>Target</b> property. The
+   * default value of the option is "System.out".
+   *
+   * See also {@link #setTarget}.
+   * */
+  public
+  String getTarget() {
+    return target;
+  }
+  
+  /**
+   *  Sets whether the appender honors reassignments of System.out
+   *  or System.err made after configuration.
+   *  @param newValue if true, appender will use value of System.out or
+   *  System.err in force at the time when logging events are appended.
+   *  @since 1.2.13
+   */
+  public final void setFollow(final boolean newValue) {
+     follow = newValue;
+  }
+  
+  /**
+   *  Gets whether the appender honors reassignments of System.out
+   *  or System.err made after configuration.
+   *  @return true if appender will use value of System.out or
+   *  System.err in force at the time when logging events are appended.
+   *  @since 1.2.13
+   */
+  public final boolean getFollow() {
+      return follow;
+  }
+
+  void targetWarn(String val) {
+    LogLog.warn("["+val+"] should be System.out or System.err.");
+    LogLog.warn("Using previously set target, System.out by default.");
+  }
+
+  /**
+    *   Prepares the appender for use.
+    */
+   public void activateOptions() {
+        if (follow) {
+            if (target.equals(SYSTEM_ERR)) {
+               setWriter(createWriter(new SystemErrStream()));
+            } else {
+               setWriter(createWriter(new SystemOutStream()));
+            }
+        } else {
+            if (target.equals(SYSTEM_ERR)) {
+               setWriter(createWriter(System.err));
+            } else {
+               setWriter(createWriter(System.out));
+            }
+        }
+
+        super.activateOptions();
+  }
+  
+  /**
+   *  {@inheritDoc}
+   */
+  protected
+  final
+  void closeWriter() {
+     if (follow) {
+        super.closeWriter();
+     }
+  }
+  
+
+    /**
+     * An implementation of OutputStream that redirects to the
+     * current System.err.
+     *
+     */
+    private static class SystemErrStream extends OutputStream {
+        public SystemErrStream() {
+        }
+
+        public void close() {
+        }
+
+        public void flush() {
+            System.err.flush();
+        }
+
+        public void write(final byte[] b) throws IOException {
+            System.err.write(b);
+        }
+
+        public void write(final byte[] b, final int off, final int len)
+            throws IOException {
+            System.err.write(b, off, len);
+        }
+
+        public void write(final int b) throws IOException {
+            System.err.write(b);
+        }
+    }
+
+    /**
+     * An implementation of OutputStream that redirects to the
+     * current System.out.
+     *
+     */
+    private static class SystemOutStream extends OutputStream {
+        public SystemOutStream() {
+        }
+
+        public void close() {
+        }
+
+        public void flush() {
+            System.out.flush();
+        }
+
+        public void write(final byte[] b) throws IOException {
+            System.out.write(b);
+        }
+
+        public void write(final byte[] b, final int off, final int len)
+            throws IOException {
+            System.out.write(b, off, len);
+        }
+
+        public void write(final int b) throws IOException {
+            System.out.write(b);
+        }
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/DailyRollingFileAppender.java b/srcjar/org/apache/log4j/DailyRollingFileAppender.java
new file mode 100644 (file)
index 0000000..79a3b5c
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.InterruptedIOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.Locale;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   DailyRollingFileAppender extends {@link FileAppender} so that the
+   underlying file is rolled over at a user chosen frequency.
+   
+   DailyRollingFileAppender has been observed to exhibit 
+   synchronization issues and data loss.  The log4j extras
+   companion includes alternatives which should be considered
+   for new deployments and which are discussed in the documentation
+   for org.apache.log4j.rolling.RollingFileAppender.
+
+   <p>The rolling schedule is specified by the <b>DatePattern</b>
+   option. This pattern should follow the {@link SimpleDateFormat}
+   conventions. In particular, you <em>must</em> escape literal text
+   within a pair of single quotes. A formatted version of the date
+   pattern is used as the suffix for the rolled file name.
+
+   <p>For example, if the <b>File</b> option is set to
+   <code>/foo/bar.log</code> and the <b>DatePattern</b> set to
+   <code>'.'yyyy-MM-dd</code>, on 2001-02-16 at midnight, the logging
+   file <code>/foo/bar.log</code> will be copied to
+   <code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17
+   will continue in <code>/foo/bar.log</code> until it rolls over
+   the next day.
+
+   <p>Is is possible to specify monthly, weekly, half-daily, daily,
+   hourly, or minutely rollover schedules.
+
+   <p><table border="1" cellpadding="2">
+   <tr>
+   <th>DatePattern</th>
+   <th>Rollover schedule</th>
+   <th>Example</th>
+
+   <tr>
+   <td><code>'.'yyyy-MM</code>
+   <td>Rollover at the beginning of each month</td>
+
+   <td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be
+   copied to <code>/foo/bar.log.2002-05</code>. Logging for the month
+   of June will be output to <code>/foo/bar.log</code> until it is
+   also rolled over the next month.
+
+   <tr>
+   <td><code>'.'yyyy-ww</code>
+
+   <td>Rollover at the first day of each week. The first day of the
+   week depends on the locale.</td>
+
+   <td>Assuming the first day of the week is Sunday, on Saturday
+   midnight, June 9th 2002, the file <i>/foo/bar.log</i> will be
+   copied to <i>/foo/bar.log.2002-23</i>.  Logging for the 24th week
+   of 2002 will be output to <code>/foo/bar.log</code> until it is
+   rolled over the next week.
+
+   <tr>
+   <td><code>'.'yyyy-MM-dd</code>
+
+   <td>Rollover at midnight each day.</td>
+
+   <td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will
+   be copied to <code>/foo/bar.log.2002-03-08</code>. Logging for the
+   9th day of March will be output to <code>/foo/bar.log</code> until
+   it is rolled over the next day.
+
+   <tr>
+   <td><code>'.'yyyy-MM-dd-a</code>
+
+   <td>Rollover at midnight and midday of each day.</td>
+
+   <td>At noon, on March 9th, 2002, <code>/foo/bar.log</code> will be
+   copied to <code>/foo/bar.log.2002-03-09-AM</code>. Logging for the
+   afternoon of the 9th will be output to <code>/foo/bar.log</code>
+   until it is rolled over at midnight.
+
+   <tr>
+   <td><code>'.'yyyy-MM-dd-HH</code>
+
+   <td>Rollover at the top of every hour.</td>
+
+   <td>At approximately 11:00.000 o'clock on March 9th, 2002,
+   <code>/foo/bar.log</code> will be copied to
+   <code>/foo/bar.log.2002-03-09-10</code>. Logging for the 11th hour
+   of the 9th of March will be output to <code>/foo/bar.log</code>
+   until it is rolled over at the beginning of the next hour.
+
+
+   <tr>
+   <td><code>'.'yyyy-MM-dd-HH-mm</code>
+
+   <td>Rollover at the beginning of every minute.</td>
+
+   <td>At approximately 11:23,000, on March 9th, 2001,
+   <code>/foo/bar.log</code> will be copied to
+   <code>/foo/bar.log.2001-03-09-10-22</code>. Logging for the minute
+   of 11:23 (9th of March) will be output to
+   <code>/foo/bar.log</code> until it is rolled over the next minute.
+
+   </table>
+
+   <p>Do not use the colon ":" character in anywhere in the
+   <b>DatePattern</b> option. The text before the colon is interpeted
+   as the protocol specificaion of a URL which is probably not what
+   you want.
+
+
+   @author Eirik Lygre
+   @author Ceki G&uuml;lc&uuml;*/
+public class DailyRollingFileAppender extends FileAppender {
+
+
+  // The code assumes that the following constants are in a increasing
+  // sequence.
+  static final int TOP_OF_TROUBLE=-1;
+  static final int TOP_OF_MINUTE = 0;
+  static final int TOP_OF_HOUR   = 1;
+  static final int HALF_DAY      = 2;
+  static final int TOP_OF_DAY    = 3;
+  static final int TOP_OF_WEEK   = 4;
+  static final int TOP_OF_MONTH  = 5;
+
+
+  /**
+     The date pattern. By default, the pattern is set to
+     "'.'yyyy-MM-dd" meaning daily rollover.
+   */
+  private String datePattern = "'.'yyyy-MM-dd";
+
+  /**
+     The log file will be renamed to the value of the
+     scheduledFilename variable when the next interval is entered. For
+     example, if the rollover period is one hour, the log file will be
+     renamed to the value of "scheduledFilename" at the beginning of
+     the next hour. 
+
+     The precise time when a rollover occurs depends on logging
+     activity. 
+  */
+  private String scheduledFilename;
+
+  /**
+     The next time we estimate a rollover should occur. */
+  private long nextCheck = System.currentTimeMillis () - 1;
+
+  Date now = new Date();
+
+  SimpleDateFormat sdf;
+
+  RollingCalendar rc = new RollingCalendar();
+
+  int checkPeriod = TOP_OF_TROUBLE;
+
+  // The gmtTimeZone is used only in computeCheckPeriod() method.
+  static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
+
+
+  /**
+     The default constructor does nothing. */
+  public DailyRollingFileAppender() {
+  }
+
+  /**
+    Instantiate a <code>DailyRollingFileAppender</code> and open the
+    file designated by <code>filename</code>. The opened filename will
+    become the ouput destination for this appender.
+
+    */
+  public DailyRollingFileAppender (Layout layout, String filename,
+                                  String datePattern) throws IOException {
+    super(layout, filename, true);
+    this.datePattern = datePattern;
+    activateOptions();
+  }
+
+  /**
+     The <b>DatePattern</b> takes a string in the same format as
+     expected by {@link SimpleDateFormat}. This options determines the
+     rollover schedule.
+   */
+  public void setDatePattern(String pattern) {
+    datePattern = pattern;
+  }
+
+  /** Returns the value of the <b>DatePattern</b> option. */
+  public String getDatePattern() {
+    return datePattern;
+  }
+
+  public void activateOptions() {
+    super.activateOptions();
+    if(datePattern != null && fileName != null) {
+      now.setTime(System.currentTimeMillis());
+      sdf = new SimpleDateFormat(datePattern);
+      int type = computeCheckPeriod();
+      printPeriodicity(type);
+      rc.setType(type);
+      File file = new File(fileName);
+      scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));
+
+    } else {
+      LogLog.error("Either File or DatePattern options are not set for appender ["
+                  +name+"].");
+    }
+  }
+
+  void printPeriodicity(int type) {
+    switch(type) {
+    case TOP_OF_MINUTE:
+      LogLog.debug("Appender ["+name+"] to be rolled every minute.");
+      break;
+    case TOP_OF_HOUR:
+      LogLog.debug("Appender ["+name
+                  +"] to be rolled on top of every hour.");
+      break;
+    case HALF_DAY:
+      LogLog.debug("Appender ["+name
+                  +"] to be rolled at midday and midnight.");
+      break;
+    case TOP_OF_DAY:
+      LogLog.debug("Appender ["+name
+                  +"] to be rolled at midnight.");
+      break;
+    case TOP_OF_WEEK:
+      LogLog.debug("Appender ["+name
+                  +"] to be rolled at start of week.");
+      break;
+    case TOP_OF_MONTH:
+      LogLog.debug("Appender ["+name
+                  +"] to be rolled at start of every month.");
+      break;
+    default:
+      LogLog.warn("Unknown periodicity for appender ["+name+"].");
+    }
+  }
+
+
+  // This method computes the roll over period by looping over the
+  // periods, starting with the shortest, and stopping when the r0 is
+  // different from from r1, where r0 is the epoch formatted according
+  // the datePattern (supplied by the user) and r1 is the
+  // epoch+nextMillis(i) formatted according to datePattern. All date
+  // formatting is done in GMT and not local format because the test
+  // logic is based on comparisons relative to 1970-01-01 00:00:00
+  // GMT (the epoch).
+
+  int computeCheckPeriod() {
+    RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault());
+    // set sate to 1970-01-01 00:00:00 GMT
+    Date epoch = new Date(0);
+    if(datePattern != null) {
+      for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
+       SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
+       simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
+       String r0 = simpleDateFormat.format(epoch);
+       rollingCalendar.setType(i);
+       Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
+       String r1 =  simpleDateFormat.format(next);
+       //System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
+       if(r0 != null && r1 != null && !r0.equals(r1)) {
+         return i;
+       }
+      }
+    }
+    return TOP_OF_TROUBLE; // Deliberately head for trouble...
+  }
+
+  /**
+     Rollover the current file to a new file.
+  */
+  void rollOver() throws IOException {
+
+    /* Compute filename, but only if datePattern is specified */
+    if (datePattern == null) {
+      errorHandler.error("Missing DatePattern option in rollOver().");
+      return;
+    }
+
+    String datedFilename = fileName+sdf.format(now);
+    // It is too early to roll over because we are still within the
+    // bounds of the current interval. Rollover will occur once the
+    // next interval is reached.
+    if (scheduledFilename.equals(datedFilename)) {
+      return;
+    }
+
+    // close current file, and rename it to datedFilename
+    this.closeFile();
+
+    File target  = new File(scheduledFilename);
+    if (target.exists()) {
+      target.delete();
+    }
+
+    File file = new File(fileName);
+    boolean result = file.renameTo(target);
+    if(result) {
+      LogLog.debug(fileName +" -> "+ scheduledFilename);
+    } else {
+      LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
+    }
+
+    try {
+      // This will also close the file. This is OK since multiple
+      // close operations are safe.
+      this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
+    }
+    catch(IOException e) {
+      errorHandler.error("setFile("+fileName+", true) call failed.");
+    }
+    scheduledFilename = datedFilename;
+  }
+
+  /**
+   * This method differentiates DailyRollingFileAppender from its
+   * super class.
+   *
+   * <p>Before actually logging, this method will check whether it is
+   * time to do a rollover. If it is, it will schedule the next
+   * rollover time and then rollover.
+   * */
+  protected void subAppend(LoggingEvent event) {
+    long n = System.currentTimeMillis();
+    if (n >= nextCheck) {
+      now.setTime(n);
+      nextCheck = rc.getNextCheckMillis(now);
+      try {
+       rollOver();
+      }
+      catch(IOException ioe) {
+          if (ioe instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             LogLog.error("rollOver() failed.", ioe);
+      }
+    }
+    super.subAppend(event);
+   }
+}
+
+/**
+ *  RollingCalendar is a helper class to DailyRollingFileAppender.
+ *  Given a periodicity type and the current time, it computes the
+ *  start of the next interval.  
+ * */
+class RollingCalendar extends GregorianCalendar {
+  private static final long serialVersionUID = -3560331770601814177L;
+
+  int type = DailyRollingFileAppender.TOP_OF_TROUBLE;
+
+  RollingCalendar() {
+    super();
+  }  
+
+  RollingCalendar(TimeZone tz, Locale locale) {
+    super(tz, locale);
+  }  
+
+  void setType(int type) {
+    this.type = type;
+  }
+
+  public long getNextCheckMillis(Date now) {
+    return getNextCheckDate(now).getTime();
+  }
+
+  public Date getNextCheckDate(Date now) {
+    this.setTime(now);
+
+    switch(type) {
+    case DailyRollingFileAppender.TOP_OF_MINUTE:
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       this.add(Calendar.MINUTE, 1);
+       break;
+    case DailyRollingFileAppender.TOP_OF_HOUR:
+       this.set(Calendar.MINUTE, 0);
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       this.add(Calendar.HOUR_OF_DAY, 1);
+       break;
+    case DailyRollingFileAppender.HALF_DAY:
+       this.set(Calendar.MINUTE, 0);
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       int hour = get(Calendar.HOUR_OF_DAY);
+       if(hour < 12) {
+         this.set(Calendar.HOUR_OF_DAY, 12);
+       } else {
+         this.set(Calendar.HOUR_OF_DAY, 0);
+         this.add(Calendar.DAY_OF_MONTH, 1);
+       }
+       break;
+    case DailyRollingFileAppender.TOP_OF_DAY:
+       this.set(Calendar.HOUR_OF_DAY, 0);
+       this.set(Calendar.MINUTE, 0);
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       this.add(Calendar.DATE, 1);
+       break;
+    case DailyRollingFileAppender.TOP_OF_WEEK:
+       this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
+       this.set(Calendar.HOUR_OF_DAY, 0);
+       this.set(Calendar.MINUTE, 0);
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       this.add(Calendar.WEEK_OF_YEAR, 1);
+       break;
+    case DailyRollingFileAppender.TOP_OF_MONTH:
+       this.set(Calendar.DATE, 1);
+       this.set(Calendar.HOUR_OF_DAY, 0);
+       this.set(Calendar.MINUTE, 0);
+       this.set(Calendar.SECOND, 0);
+       this.set(Calendar.MILLISECOND, 0);
+       this.add(Calendar.MONTH, 1);
+       break;
+    default:
+       throw new IllegalStateException("Unknown periodicity type.");
+    }
+    return getTime();
+  }
+}
diff --git a/srcjar/org/apache/log4j/DefaultCategoryFactory.java b/srcjar/org/apache/log4j/DefaultCategoryFactory.java
new file mode 100644 (file)
index 0000000..c7bb0c4
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggerFactory;
+
+class DefaultCategoryFactory implements LoggerFactory {
+    
+  DefaultCategoryFactory() {
+  }    
+    
+  public
+  Logger makeNewLoggerInstance(String name) {
+    return new Logger(name);
+  }    
+}
diff --git a/srcjar/org/apache/log4j/DefaultThrowableRenderer.java b/srcjar/org/apache/log4j/DefaultThrowableRenderer.java
new file mode 100644 (file)
index 0000000..29bfe06
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.ThrowableRenderer;
+
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.io.LineNumberReader;
+import java.io.StringReader;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+
+/**
+ * Default implementation of ThrowableRenderer using
+ * Throwable.printStackTrace.
+ *
+ * @since 1.2.16
+ */
+public final class DefaultThrowableRenderer implements ThrowableRenderer {
+    /**
+     * Construct new instance.
+     */
+    public DefaultThrowableRenderer() {
+
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] doRender(final Throwable throwable) {
+        return render(throwable);
+    }
+
+    /**
+     * Render throwable using Throwable.printStackTrace.
+     * @param throwable throwable, may not be null.
+     * @return string representation.
+     */
+    public static String[] render(final Throwable throwable) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        try {
+            throwable.printStackTrace(pw);
+        } catch(RuntimeException ex) {
+        }
+        pw.flush();
+        LineNumberReader reader = new LineNumberReader(
+                new StringReader(sw.toString()));
+        ArrayList lines = new ArrayList();
+        try {
+          String line = reader.readLine();
+          while(line != null) {
+            lines.add(line);
+            line = reader.readLine();
+          }
+        } catch(IOException ex) {
+            if (ex instanceof InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            lines.add(ex.toString());
+        }
+        String[] tempRep = new String[lines.size()];
+        lines.toArray(tempRep);
+        return tempRep;
+    }
+}
diff --git a/srcjar/org/apache/log4j/Dispatcher.java b/srcjar/org/apache/log4j/Dispatcher.java
new file mode 100644 (file)
index 0000000..e879ff0
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Obsolete AsyncAppender dispatcher provided for compatibility only.
+ *
+ * @deprecated Since 1.3.
+ */
+class Dispatcher extends Thread {
+    /**
+     * @deprecated
+     */
+  private org.apache.log4j.helpers.BoundedFIFO bf;
+  private AppenderAttachableImpl aai;
+  private boolean interrupted = false;
+  AsyncAppender container;
+
+    /**
+     *
+     * @param bf
+     * @param container
+     * @deprecated
+     */
+  Dispatcher(org.apache.log4j.helpers.BoundedFIFO bf, AsyncAppender container) {
+    this.bf = bf;
+    this.container = container;
+    this.aai = container.aai;
+
+    // It is the user's responsibility to close appenders before
+    // exiting.
+    this.setDaemon(true);
+
+    // set the dispatcher priority to lowest possible value
+    this.setPriority(Thread.MIN_PRIORITY);
+    this.setName("Dispatcher-" + getName());
+
+    // set the dispatcher priority to MIN_PRIORITY plus or minus 2
+    // depending on the direction of MIN to MAX_PRIORITY.
+    //+ (Thread.MAX_PRIORITY > Thread.MIN_PRIORITY ? 1 : -1)*2);
+  }
+
+  void close() {
+    synchronized (bf) {
+      interrupted = true;
+
+      // We have a waiting dispacther if and only if bf.length is
+      // zero.  In that case, we need to give it a death kiss.
+      if (bf.length() == 0) {
+        bf.notify();
+      }
+    }
+  }
+
+  /**
+   * The dispatching strategy is to wait until there are events in the buffer
+   * to process. After having processed an event, we release the monitor
+   * (variable bf) so that new events can be placed in the buffer, instead of
+   * keeping the monitor and processing the remaining events in the buffer.
+   *
+   * <p>
+   * Other approaches might yield better results.
+   * </p>
+   */
+  public void run() {
+    //Category cat = Category.getInstance(Dispatcher.class.getName());
+    LoggingEvent event;
+
+    while (true) {
+      synchronized (bf) {
+        if (bf.length() == 0) {
+          // Exit loop if interrupted but only if the the buffer is empty.
+          if (interrupted) {
+            //cat.info("Exiting.");
+            break;
+          }
+
+          try {
+            //LogLog.debug("Waiting for new event to dispatch.");
+            bf.wait();
+          } catch (InterruptedException e) {
+            break;
+          }
+        }
+
+        event = bf.get();
+
+        if (bf.wasFull()) {
+          //LogLog.debug("Notifying AsyncAppender about freed space.");
+          bf.notify();
+        }
+      }
+
+      // synchronized
+      synchronized (container.aai) {
+        if ((aai != null) && (event != null)) {
+          aai.appendLoopOnAppenders(event);
+        }
+      }
+    }
+
+    // while
+    // close and remove all appenders
+    aai.removeAllAppenders();
+  }
+}
diff --git a/srcjar/org/apache/log4j/EnhancedPatternLayout.java b/srcjar/org/apache/log4j/EnhancedPatternLayout.java
new file mode 100644 (file)
index 0000000..a8158da
--- /dev/null
@@ -0,0 +1,571 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.helpers.PatternConverter;
+import org.apache.log4j.pattern.BridgePatternConverter;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+// Contributors:   Nelson Minar <nelson@monkey.org>
+//                 Anders Kristensen <akristensen@dynamicsoft.com>
+
+/**
+ * This class is an enhanced version of org.apache.log4j.PatternLayout
+ * which was originally developed as part of the abandoned log4j 1.3
+ * effort and has been available in the extras companion.
+ * This pattern layout should be used in preference to
+ * org.apache.log4j.PatternLayout except when compatibility
+ * where PatternLayout has been extended either through subclassing
+ * or alternative pattern parsers.
+ *
+ *
+  * <p>A flexible layout configurable with pattern string. The goal of this class
+  * is to {@link #format format} a {@link LoggingEvent} and return the results
+  * in a {@link StringBuffer}. The format of the result depends on the
+  * <em>conversion pattern</em>.
+  * <p>
+  *
+  * <p>The conversion pattern is closely related to the conversion
+  * pattern of the printf function in C. A conversion pattern is
+  * composed of literal text and format control expressions called
+  * <em>conversion specifiers</em>.
+  *
+  * <p><i>Note that you are free to insert any literal text within the
+  * conversion pattern.</i>
+  * </p>
+
+   <p>Each conversion specifier starts with a percent sign (%) and is
+   followed by optional <em>format modifiers</em> and a <em>conversion
+   character</em>. The conversion character specifies the type of
+   data, e.g. category, priority, date, thread name. The format
+   modifiers control such things as field width, padding, left and
+   right justification. The following is a simple example.
+
+   <p>Let the conversion pattern be <b>"%-5p [%t]: %m%n"</b> and assume
+   that the log4j environment was set to use a EnhancedPatternLayout. Then the
+   statements
+   <pre>
+   Category root = Category.getRoot();
+   root.debug("Message 1");
+   root.warn("Message 2");
+   </pre>
+   would yield the output
+   <pre>
+   DEBUG [main]: Message 1
+   WARN  [main]: Message 2
+   </pre>
+
+   <p>Note that there is no explicit separator between text and
+   conversion specifiers. The pattern parser knows when it has reached
+   the end of a conversion specifier when it reads a conversion
+   character. In the example above the conversion specifier
+   <b>%-5p</b> means the priority of the logging event should be left
+   justified to a width of five characters.
+
+   The recognized conversion characters are
+
+   <p>
+   <table border="1" CELLPADDING="8">
+   <th>Conversion Character</th>
+   <th>Effect</th>
+
+   <tr>
+     <td align=center><b>c</b></td>
+
+     <td>Used to output the category of the logging event. The
+     category conversion specifier can be optionally followed by
+     NameAbbreviator pattern.
+
+     <p>For example, for the category name "alpha.beta.gamma" the pattern
+     <b>%c{2}</b> will output the last two elements ("beta.gamma"),
+     <b>%c{-2}</b> will remove two elements leaving "gamma",
+     <b>%c{1.}</b> will output "a.b.gamma".
+
+     </td>
+   </tr>
+
+   <tr>
+     <td align=center><b>C</b></td>
+
+     <td>Used to output the fully qualified class name of the caller
+     issuing the logging request. This conversion specifier
+     can be optionally followed by <em>precision specifier</em>, that
+     is a decimal constant in brackets.
+
+     <td>Used to output the category of the logging event. The
+     category conversion specifier can be optionally followed by
+     NameAbbreviator pattern.
+
+     <p>For example, for the category name "alpha.beta.gamma" the pattern
+     <b>%c{2}</b> will output the last two elements ("beta.gamma"),
+     <b>%c{-2}</b> will remove two elements leaving "gamma",
+     <b>%c{1.}</b> will output "a.b.gamma".
+
+     <p><b>WARNING</b> Generating the caller class information is
+     slow. Thus, its use should be avoided unless execution speed is
+     not an issue.
+
+     </td>
+     </tr>
+
+   <tr> <td align=center><b>d</b></td> <td>Used to output the date of
+         the logging event. The date conversion specifier may be
+         followed by a set of braces containing a
+         date and time pattern strings {@link java.text.SimpleDateFormat},
+         <em>ABSOLUTE</em>, <em>DATE</em> or <em>ISO8601</em>
+          and a set of braces containing a time zone id per 
+          {@link java.util.TimeZone#getTimeZone(String)}.           
+          For example, <b>%d{HH:mm:ss,SSS}</b>,
+         <b>%d{dd&nbsp;MMM&nbsp;yyyy&nbsp;HH:mm:ss,SSS}</b>,
+         <b>%d{DATE}</b> or <b>%d{HH:mm:ss}{GMT+0}</b>. If no date format specifier is given then
+         ISO8601 format is assumed.  
+     </td>
+   </tr>
+
+   <tr>
+   <td align=center><b>F</b></td>
+
+   <td>Used to output the file name where the logging request was
+   issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+   <tr>
+   <td align=center><b>l</b></td>
+
+     <td>Used to output location information of the caller which generated
+     the logging event.
+
+     <p>The location information depends on the JVM implementation but
+     usually consists of the fully qualified name of the calling
+     method followed by the callers source the file name and line
+     number between parentheses.
+
+     <p>The location information can be very useful. However, its
+     generation is <em>extremely</em> slow and should be avoided
+     unless execution speed is not an issue.
+
+     </td>
+   </tr>
+
+   <tr>
+   <td align=center><b>L</b></td>
+
+   <td>Used to output the line number from where the logging request
+   was issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+
+   <tr>
+     <td align=center><b>m</b></td>
+     <td>Used to output the application supplied message associated with
+     the logging event.</td>
+   </tr>
+
+   <tr>
+   <td align=center><b>M</b></td>
+
+   <td>Used to output the method name where the logging request was
+   issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+   <tr>
+     <td align=center><b>n</b></td>
+
+     <td>Outputs the platform dependent line separator character or
+     characters.
+
+     <p>This conversion character offers practically the same
+     performance as using non-portable line separator strings such as
+     "\n", or "\r\n". Thus, it is the preferred way of specifying a
+     line separator.
+
+
+   </tr>
+
+   <tr>
+     <td align=center><b>p</b></td>
+     <td>Used to output the priority of the logging event.</td>
+   </tr>
+
+   <tr>
+
+     <td align=center><b>r</b></td>
+
+     <td>Used to output the number of milliseconds elapsed since the construction 
+     of the layout until the creation of the logging event.</td>
+   </tr>
+
+
+   <tr>
+     <td align=center><b>t</b></td>
+
+     <td>Used to output the name of the thread that generated the
+     logging event.</td>
+
+   </tr>
+
+   <tr>
+
+     <td align=center><b>x</b></td>
+
+     <td>Used to output the NDC (nested diagnostic context) associated
+     with the thread that generated the logging event.
+     </td>
+   </tr>
+
+
+   <tr>
+     <td align=center><b>X</b></td>
+
+     <td>
+
+     <p>Used to output the MDC (mapped diagnostic context) associated
+     with the thread that generated the logging event. The <b>X</b>
+     conversion character can be followed by the key for the
+     map placed between braces, as in <b>%X{clientNumber}</b> where
+     <code>clientNumber</code> is the key. The value in the MDC
+     corresponding to the key will be output. If no additional sub-option
+     is specified, then the entire contents of the MDC key value pair set
+     is output using a format {{key1,val1},{key2,val2}}</p>
+
+     <p>See {@link MDC} class for more details.
+     </p>
+
+     </td>
+   </tr>
+
+      <tr>
+     <td align=center><b>properties</b></td>
+
+     <td>
+     <p>Used to output the Properties associated
+     with the logging event. The <b>properties</b>
+     conversion word can be followed by the key for the
+     map placed between braces, as in <b>%properties{application}</b> where
+     <code>application</code> is the key. The value in the Properties bundle
+     corresponding to the key will be output. If no additional sub-option
+     is specified, then the entire contents of the Properties key value pair set
+     is output using a format {{key1,val1},{key2,val2}}</p>
+     </td>
+   </tr>
+
+            <tr>
+     <td align=center><b>throwable</b></td>
+
+     <td>
+     <p>Used to output the Throwable trace that has been bound to the LoggingEvent, by
+     default this will output the full trace as one would normally 
+     find by a call to Throwable.printStackTrace().
+     <b>%throwable{short}</b> or <b>%throwable{1}</b> will output the first line of
+     stack trace.   <b>throwable{none}</b> or <b>throwable{0}</b> will suppress
+     the stack trace.  <b>%throwable{n}</b> will output n lines of stack trace
+     if a positive integer or omit the last -n lines if a negative integer.
+     If no %throwable pattern is specified, the appender will take
+     responsibility to output the stack trace as it sees fit.</p>
+     </td>
+   </tr>
+
+   <tr>
+
+     <td align=center><b>%</b></td>
+
+     <td>The sequence %% outputs a single percent sign.
+     </td>
+   </tr>
+
+   </table>
+
+   <p>By default the relevant information is output as is. However,
+   with the aid of format modifiers it is possible to change the
+   minimum field width, the maximum field width, justification
+   and truncation.
+
+   <p>The optional format modifier are placed between the percent sign
+   and the conversion character.
+
+   <p>The <em>left justification flag</em>, the minus sign (-),
+   the <em>right truncation flag</em>, the exclamation mark (!),
+   or any combination appear first.  Followed by the
+   optional <em>minimum field width</em> modifier. This is a decimal
+   constant that represents the minimum number of characters to
+   output. If the data item requires fewer characters, it is padded on
+   either the left or the right until the minimum width is
+   reached. The default is to pad on the left (right justify) but you
+   can specify right padding with the left justification flag. The
+   padding character is space. If the data item is larger than the
+   minimum field width, the field is expanded to accommodate the
+   data. The value is never truncated.
+
+   <p>This behavior can be changed using the <em>maximum field
+   width</em> modifier which is designated by a period followed by a
+   decimal constant. If the data item is longer than the maximum
+   field, then the extra characters are removed from the
+   <em>beginning</em> of the data item and not from the end. For
+   example, it the maximum field width is eight and the data item is
+   ten characters long, then the first two characters of the data item
+   are dropped. This behavior deviates from the printf function in C
+   where truncation is done from the end. The <em>right truncation flag</em>,
+   described previously, will override this behavior.
+
+   <p>Below are various format modifier examples for the category
+   conversion specifier.
+
+   <p>
+   <TABLE BORDER=1 CELLPADDING=8>
+   <th>Format modifier
+   <th>left justify
+   <th>minimum width
+   <th>maximum width
+   <th>comment
+
+   <tr>
+   <td align=center>%20c</td>
+   <td align=center>false</td>
+   <td align=center>20</td>
+   <td align=center>none</td>
+
+   <td>Left pad with spaces if the category name is less than 20
+   characters long.
+
+   <tr> <td align=center>%-20c</td> <td align=center>true</td> <td
+   align=center>20</td> <td align=center>none</td> <td>Right pad with
+   spaces if the category name is less than 20 characters long.
+
+   <tr>
+   <td align=center>%.30c</td>
+   <td align=center>NA</td>
+   <td align=center>none</td>
+   <td align=center>30</td>
+
+   <td>Truncate from the beginning if the category name is longer than 30
+   characters.
+
+   <tr>
+   <td align=center>%!.30c</td>
+   <td align=center>NA</td>
+   <td align=center>none</td>
+   <td align=center>30</td>
+
+   <td>Truncate from the end if the category name is longer than 30
+   characters.
+
+   <tr>
+   <td align=center>%20.30c</td>
+   <td align=center>false</td>
+   <td align=center>20</td>
+   <td align=center>30</td>
+
+   <td>Left pad with spaces if the category name is shorter than 20
+   characters. However, if category name is longer than 30 characters,
+   then truncate from the beginning.
+
+   <tr>
+   <td align=center>%-20.30c</td>
+   <td align=center>true</td>
+   <td align=center>20</td>
+   <td align=center>30</td>
+
+   <td>Right pad with spaces if the category name is shorter than 20
+   characters. However, if category name is longer than 30 characters,
+   then truncate from the beginning.
+
+   </table>
+
+   <p>Below are some examples of conversion patterns.
+
+   <dl>
+
+   <p><dt><b>%r [%t] %-5p %c %x - %m%n</b>
+   <p><dd>This is essentially the TTCC layout.
+
+   <p><dt><b>%-6r [%15.15t] %-5p %30.30c %x - %m%n</b>
+
+   <p><dd>Similar to the TTCC layout except that the relative time is
+   right padded if less than 6 digits, thread name is right padded if
+   less than 15 characters and truncated if longer and the category
+   name is left padded if shorter than 30 characters and truncated if
+   longer.
+
+  </dl>
+
+   <p>The above text is largely inspired from Peter A. Darnell and
+   Philip E. Margolis' highly recommended book "C -- a Software
+   Engineering Approach", ISBN 0-387-97389-3.
+
+   @author <a href="mailto:cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+
+   @since 1.2.16 */
+public class EnhancedPatternLayout extends Layout {
+  /** Default pattern string for log output. Currently set to the
+      string <b>"%m%n"</b> which just prints the application supplied
+      message. */
+  public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
+
+  /** A conversion pattern equivalent to the TTCCCLayout.
+      Current value is <b>%r [%t] %p %c %x - %m%n</b>. */
+  public static final String TTCC_CONVERSION_PATTERN =
+    "%r [%t] %p %c %x - %m%n";
+
+    /**
+     * Initial size of internal buffer, no longer used.
+     * @deprecated since 1.3
+     */
+  protected final int BUF_SIZE = 256;
+
+    /**
+     * Maximum capacity of internal buffer, no longer used.
+     * @deprecated since 1.3
+     */
+  protected final int MAX_CAPACITY = 1024;
+
+  /**
+   * Customized pattern conversion rules are stored under this key in the
+   * {@link org.apache.log4j.spi.LoggerRepository LoggerRepository} object store.
+   */
+  public static final String PATTERN_RULE_REGISTRY = "PATTERN_RULE_REGISTRY";
+
+
+  /**
+    *  Initial converter for pattern.
+    */
+  private PatternConverter head;
+
+  /**
+   * Conversion pattern.
+   */
+  private String conversionPattern;
+
+  /**
+   * True if any element in pattern formats information from exceptions.
+   */
+  private boolean handlesExceptions;
+
+  /**
+     Constructs a EnhancedPatternLayout using the DEFAULT_LAYOUT_PATTERN.
+
+     The default pattern just produces the application supplied message.
+  */
+  public EnhancedPatternLayout() {
+    this(DEFAULT_CONVERSION_PATTERN);
+  }
+
+  /**
+    * Constructs a EnhancedPatternLayout using the supplied conversion pattern.
+   * @param pattern conversion pattern.
+  */
+  public EnhancedPatternLayout(final String pattern) {
+    this.conversionPattern = pattern;
+    head = createPatternParser(
+            (pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse();
+    if (head instanceof BridgePatternConverter) {
+        handlesExceptions = !((BridgePatternConverter) head).ignoresThrowable();
+    } else {
+        handlesExceptions = false;
+    }
+  }
+
+  /**
+   * Set the <b>ConversionPattern</b> option. This is the string which
+   * controls formatting and consists of a mix of literal content and
+   * conversion specifiers.
+   *
+   * @param conversionPattern conversion pattern.
+  */
+  public void setConversionPattern(final String conversionPattern) {
+    this.conversionPattern =
+      OptionConverter.convertSpecialChars(conversionPattern);
+      head = createPatternParser(this.conversionPattern).parse();
+      if (head instanceof BridgePatternConverter) {
+          handlesExceptions = !((BridgePatternConverter) head).ignoresThrowable();
+      } else {
+          handlesExceptions = false;
+      }
+  }
+
+  /**
+   *  Returns the value of the <b>ConversionPattern</b> option.
+   * @return conversion pattern.
+   */
+  public String getConversionPattern() {
+    return conversionPattern;
+  }
+
+
+    /**
+      Returns PatternParser used to parse the conversion string. Subclasses
+      may override this to return a subclass of PatternParser which recognize
+      custom conversion characters.
+
+      @since 0.9.0
+    */
+    protected org.apache.log4j.helpers.PatternParser createPatternParser(String pattern) {
+      return new org.apache.log4j.pattern.BridgePatternParser(pattern);
+    }
+
+
+  /**
+    Activates the conversion pattern. Do not forget to call this method after
+    you change the parameters of the EnhancedPatternLayout instance.
+  */
+  public void activateOptions() {
+      // nothing to do.
+  }
+
+
+  /**
+   *  Formats a logging event to a writer.
+   * @param event logging event to be formatted.
+  */
+  public String format(final LoggingEvent event) {
+      StringBuffer buf = new StringBuffer();
+      for(PatternConverter c = head;
+          c != null;
+          c = c.next) {
+          c.format(buf, event);
+      }
+      return buf.toString();
+  }
+
+  /**
+   * Will return false if any of the conversion specifiers in the pattern
+   * handles {@link Exception Exceptions}.
+   * @return true if the pattern formats any information from exceptions.
+   */
+  public boolean ignoresThrowable() {
+    return !handlesExceptions;
+  }
+}
diff --git a/srcjar/org/apache/log4j/EnhancedThrowableRenderer.java b/srcjar/org/apache/log4j/EnhancedThrowableRenderer.java
new file mode 100644 (file)
index 0000000..c5b8d7b
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.ThrowableRenderer;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Enhanced implementation of ThrowableRenderer.  Uses Throwable.getStackTrace
+ * if running on JDK 1.4 or later and delegates to DefaultThrowableRenderer.render
+ * on earlier virtual machines.
+ *
+ * @since 1.2.16
+ */
+public final class EnhancedThrowableRenderer implements ThrowableRenderer {
+    /**
+     * Throwable.getStackTrace() method.
+     */
+    private Method getStackTraceMethod;
+    /**
+     * StackTraceElement.getClassName() method.
+     */
+    private Method getClassNameMethod;
+
+
+    /**
+     * Construct new instance.
+     */
+    public EnhancedThrowableRenderer() {
+        try {
+            Class[] noArgs = null;
+            getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
+            Class ste = Class.forName("java.lang.StackTraceElement");
+            getClassNameMethod = ste.getMethod("getClassName", noArgs);
+        } catch(Exception ex) {
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] doRender(final Throwable throwable) {
+        if (getStackTraceMethod != null) {
+            try {
+                Object[] noArgs = null;
+                Object[] elements = (Object[]) getStackTraceMethod.invoke(throwable, noArgs);
+                String[] lines = new String[elements.length + 1];
+                lines[0] = throwable.toString();
+                Map classMap = new HashMap();
+                for(int i = 0; i < elements.length; i++) {
+                    lines[i+1] = formatElement(elements[i], classMap);
+                }
+                return lines;
+            } catch(Exception ex) {
+            }
+        }
+        return DefaultThrowableRenderer.render(throwable);
+    }
+
+    /**
+     * Format one element from stack trace.
+     * @param element element, may not be null.
+     * @param classMap map of class name to location.
+     * @return string representation of element.
+     */
+    private String formatElement(final Object element, final Map classMap) {
+        StringBuffer buf = new StringBuffer("\tat ");
+        buf.append(element);
+        try {
+            String className = getClassNameMethod.invoke(element, (Object[]) null).toString();
+            Object classDetails = classMap.get(className);
+            if (classDetails != null) {
+                buf.append(classDetails);
+            } else {
+                Class cls = findClass(className);
+                int detailStart = buf.length();
+                buf.append('[');
+                try {
+                    CodeSource source = cls.getProtectionDomain().getCodeSource();
+                    if (source != null) {
+                        URL locationURL = source.getLocation();
+                        if (locationURL != null) {
+                            //
+                            //   if a file: URL
+                            //
+                            if ("file".equals(locationURL.getProtocol())) {
+                                String path = locationURL.getPath();
+                                if (path != null) {
+                                    //
+                                    //  find the last file separator character
+                                    //
+                                    int lastSlash = path.lastIndexOf('/');
+                                    int lastBack = path.lastIndexOf(File.separatorChar);
+                                    if (lastBack > lastSlash) {
+                                        lastSlash = lastBack;
+                                    }
+                                    //
+                                    //  if no separator or ends with separator (a directory)
+                                    //     then output the URL, otherwise just the file name.
+                                    //
+                                    if (lastSlash <= 0 || lastSlash == path.length() - 1) {
+                                        buf.append(locationURL);
+                                    } else {
+                                        buf.append(path.substring(lastSlash + 1));
+                                    }
+                                }
+                            } else {
+                                buf.append(locationURL);
+                            }
+                        }
+                    }
+                } catch(SecurityException ex) {
+                }
+                buf.append(':');
+                Package pkg = cls.getPackage();
+                if (pkg != null) {
+                    String implVersion = pkg.getImplementationVersion();
+                    if (implVersion != null) {
+                        buf.append(implVersion);
+                    }
+                }
+                buf.append(']');
+                classMap.put(className, buf.substring(detailStart));
+            }
+        } catch(Exception ex) {
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Find class given class name.
+     * @param className class name, may not be null.
+     * @return class, will not be null.
+     * @throws ClassNotFoundException thrown if class can not be found.
+     */
+    private Class findClass(final String className) throws ClassNotFoundException {
+     try {
+       return Thread.currentThread().getContextClassLoader().loadClass(className);
+     } catch (ClassNotFoundException e) {
+       try {
+         return Class.forName(className);
+       } catch (ClassNotFoundException e1) {
+          return getClass().getClassLoader().loadClass(className);
+      }
+    }
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/FileAppender.java b/srcjar/org/apache/log4j/FileAppender.java
new file mode 100644 (file)
index 0000000..0728695
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.Writer;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.QuietWriter;
+import org.apache.log4j.spi.ErrorCode;
+
+// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
+//              Ben Sandee
+
+/**
+ *  FileAppender appends log events to a file.
+ *
+ *  <p>Support for <code>java.io.Writer</code> and console appending
+ *  has been deprecated and then removed. See the replacement
+ *  solutions: {@link WriterAppender} and {@link ConsoleAppender}.
+ *
+ * @author Ceki G&uuml;lc&uuml; 
+ * */
+public class FileAppender extends WriterAppender {
+
+  /** Controls file truncatation. The default value for this variable
+   * is <code>true</code>, meaning that by default a
+   * <code>FileAppender</code> will append to an existing file and not
+   * truncate it.
+   *
+   * <p>This option is meaningful only if the FileAppender opens the
+   * file.
+   */
+  protected boolean fileAppend = true;
+
+  /**
+     The name of the log file. */
+  protected String fileName = null;
+
+  /**
+     Do we do bufferedIO? */
+  protected boolean bufferedIO = false;
+
+  /**
+   * Determines the size of IO buffer be. Default is 8K. 
+   */
+  protected int bufferSize = 8*1024;
+
+
+  /**
+     The default constructor does not do anything.
+  */
+  public
+  FileAppender() {
+  }
+
+  /**
+    Instantiate a <code>FileAppender</code> and open the file
+    designated by <code>filename</code>. The opened filename will
+    become the output destination for this appender.
+
+    <p>If the <code>append</code> parameter is true, the file will be
+    appended to. Otherwise, the file designated by
+    <code>filename</code> will be truncated before being opened.
+
+    <p>If the <code>bufferedIO</code> parameter is <code>true</code>,
+    then buffered IO will be used to write to the output file.
+
+  */
+  public
+  FileAppender(Layout layout, String filename, boolean append, boolean bufferedIO,
+              int bufferSize) throws IOException {
+    this.layout = layout;
+    this.setFile(filename, append, bufferedIO, bufferSize);
+  }
+
+  /**
+    Instantiate a FileAppender and open the file designated by
+    <code>filename</code>. The opened filename will become the output
+    destination for this appender.
+
+    <p>If the <code>append</code> parameter is true, the file will be
+    appended to. Otherwise, the file designated by
+    <code>filename</code> will be truncated before being opened.
+  */
+  public
+  FileAppender(Layout layout, String filename, boolean append)
+                                                             throws IOException {
+    this.layout = layout;
+    this.setFile(filename, append, false, bufferSize);
+  }
+
+  /**
+     Instantiate a FileAppender and open the file designated by
+    <code>filename</code>. The opened filename will become the output
+    destination for this appender.
+
+    <p>The file will be appended to.  */
+  public
+  FileAppender(Layout layout, String filename) throws IOException {
+    this(layout, filename, true);
+  }
+
+  /**
+     The <b>File</b> property takes a string value which should be the
+     name of the file to append to.
+
+     <p><font color="#DD0044"><b>Note that the special values
+     "System.out" or "System.err" are no longer honored.</b></font>
+
+     <p>Note: Actual opening of the file is made when {@link
+     #activateOptions} is called, not when the options are set.  */
+  public void setFile(String file) {
+    // Trim spaces from both ends. The users probably does not want
+    // trailing spaces in file names.
+    String val = file.trim();
+    fileName = val;
+  }
+
+  /**
+      Returns the value of the <b>Append</b> option.
+   */
+  public
+  boolean getAppend() {
+    return fileAppend;
+  }
+
+
+  /** Returns the value of the <b>File</b> option. */
+  public
+  String getFile() {
+    return fileName;
+  }
+
+  /**
+     If the value of <b>File</b> is not <code>null</code>, then {@link
+     #setFile} is called with the values of <b>File</b>  and
+     <b>Append</b> properties.
+
+     @since 0.8.1 */
+  public
+  void activateOptions() {
+    if(fileName != null) {
+      try {
+       setFile(fileName, fileAppend, bufferedIO, bufferSize);
+      }
+      catch(java.io.IOException e) {
+       errorHandler.error("setFile("+fileName+","+fileAppend+") call failed.",
+                          e, ErrorCode.FILE_OPEN_FAILURE);
+      }
+    } else {
+      //LogLog.error("File option not set for appender ["+name+"].");
+      LogLog.warn("File option not set for appender ["+name+"].");
+      LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
+    }
+  }
+
+ /**
+     Closes the previously opened file.
+  */
+  protected
+  void closeFile() {
+    if(this.qw != null) {
+      try {
+       this.qw.close();
+      }
+      catch(java.io.IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+       // Exceptionally, it does not make sense to delegate to an
+       // ErrorHandler. Since a closed appender is basically dead.
+       LogLog.error("Could not close " + qw, e);
+      }
+    }
+  }
+
+  /**
+     Get the value of the <b>BufferedIO</b> option.
+
+     <p>BufferedIO will significatnly increase performance on heavily
+     loaded systems.
+
+  */
+  public
+  boolean getBufferedIO() {
+    return this.bufferedIO;
+  }
+
+
+  /**
+     Get the size of the IO buffer.
+  */
+  public
+  int getBufferSize() {
+    return this.bufferSize;
+  }
+
+
+
+  /**
+     The <b>Append</b> option takes a boolean value. It is set to
+     <code>true</code> by default. If true, then <code>File</code>
+     will be opened in append mode by {@link #setFile setFile} (see
+     above). Otherwise, {@link #setFile setFile} will open
+     <code>File</code> in truncate mode.
+
+     <p>Note: Actual opening of the file is made when {@link
+     #activateOptions} is called, not when the options are set.
+   */
+  public
+  void setAppend(boolean flag) {
+    fileAppend = flag;
+  }
+
+  /**
+     The <b>BufferedIO</b> option takes a boolean value. It is set to
+     <code>false</code> by default. If true, then <code>File</code>
+     will be opened and the resulting {@link java.io.Writer} wrapped
+     around a {@link BufferedWriter}.
+
+     BufferedIO will significatnly increase performance on heavily
+     loaded systems.
+
+  */
+  public
+  void setBufferedIO(boolean bufferedIO) {
+    this.bufferedIO = bufferedIO;
+    if(bufferedIO) {
+      immediateFlush = false;
+    }
+  }
+
+
+  /**
+     Set the size of the IO buffer.
+  */
+  public
+  void setBufferSize(int bufferSize) {
+    this.bufferSize = bufferSize;
+  }
+
+  /**
+    <p>Sets and <i>opens</i> the file where the log output will
+    go. The specified file must be writable.
+
+    <p>If there was already an opened file, then the previous file
+    is closed first.
+
+    <p><b>Do not use this method directly. To configure a FileAppender
+    or one of its subclasses, set its properties one by one and then
+    call activateOptions.</b>
+
+    @param fileName The path to the log file.
+    @param append   If true will append to fileName. Otherwise will
+        truncate fileName.  */
+  public
+  synchronized
+  void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
+                                                            throws IOException {
+    LogLog.debug("setFile called: "+fileName+", "+append);
+
+    // It does not make sense to have immediate flush and bufferedIO.
+    if(bufferedIO) {
+      setImmediateFlush(false);
+    }
+
+    reset();
+    FileOutputStream ostream = null;
+    try {
+          //
+          //   attempt to create file
+          //
+          ostream = new FileOutputStream(fileName, append);
+    } catch(FileNotFoundException ex) {
+          //
+          //   if parent directory does not exist then
+          //      attempt to create it and try to create file
+          //      see bug 9150
+          //
+          String parentName = new File(fileName).getParent();
+          if (parentName != null) {
+             File parentDir = new File(parentName);
+             if(!parentDir.exists() && parentDir.mkdirs()) {
+                ostream = new FileOutputStream(fileName, append);
+             } else {
+                throw ex;
+             }
+          } else {
+             throw ex;
+          }
+    }
+    Writer fw = createWriter(ostream);
+    if(bufferedIO) {
+      fw = new BufferedWriter(fw, bufferSize);
+    }
+    this.setQWForFiles(fw);
+    this.fileName = fileName;
+    this.fileAppend = append;
+    this.bufferedIO = bufferedIO;
+    this.bufferSize = bufferSize;
+    writeHeader();
+    LogLog.debug("setFile ended");
+  }
+
+
+  /**
+     Sets the quiet writer being used.
+
+     This method is overriden by {@link RollingFileAppender}.
+   */
+  protected
+  void setQWForFiles(Writer writer) {
+     this.qw = new QuietWriter(writer, errorHandler);
+  }
+
+
+  /**
+     Close any previously opened file and call the parent's
+     <code>reset</code>.  */
+  protected
+  void reset() {
+    closeFile();
+    this.fileName = null;
+    super.reset();
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/HTMLLayout.java b/srcjar/org/apache/log4j/HTMLLayout.java
new file mode 100644 (file)
index 0000000..1649208
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.helpers.Transform;
+
+/**
+ * This layout outputs events in a HTML table.
+ *
+ * Appenders using this layout should have their encoding
+ * set to UTF-8 or UTF-16, otherwise events containing
+ * non ASCII characters could result in corrupted
+ * log files.
+ *
+ *  @author Ceki G&uuml;lc&uuml;
+ */
+public class HTMLLayout extends Layout {
+
+  protected final int BUF_SIZE = 256;
+  protected final int MAX_CAPACITY = 1024;
+
+  static String TRACE_PREFIX = "<br>&nbsp;&nbsp;&nbsp;&nbsp;";
+
+  // output buffer appended to when format() is invoked
+  private StringBuffer sbuf = new StringBuffer(BUF_SIZE);
+
+  /**
+     A string constant used in naming the option for setting the the
+     location information flag.  Current value of this string
+     constant is <b>LocationInfo</b>.
+
+     <p>Note that all option keys are case sensitive.
+
+     @deprecated Options are now handled using the JavaBeans paradigm.
+     This constant is not longer needed and will be removed in the
+     <em>near</em> term.
+
+  */
+  public static final String LOCATION_INFO_OPTION = "LocationInfo";
+
+  /**
+     A string constant used in naming the option for setting the the
+     HTML document title.  Current value of this string
+     constant is <b>Title</b>.
+  */
+  public static final String TITLE_OPTION = "Title";
+
+  // Print no location info by default
+  boolean locationInfo = false;
+
+  String title = "Log4J Log Messages";
+
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. By
+     default, it is set to false which means there will be no location
+     information output by this layout. If the the option is set to
+     true, then the file name and line number of the statement
+     at the origin of the log statement will be output.
+
+     <p>If you are embedding this layout within an {@link
+     org.apache.log4j.net.SMTPAppender} then make sure to set the
+     <b>LocationInfo</b> option of that appender as well.
+   */
+  public
+  void setLocationInfo(boolean flag) {
+    locationInfo = flag;
+  }
+
+  /**
+     Returns the current value of the <b>LocationInfo</b> option.
+   */
+  public
+  boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+    The <b>Title</b> option takes a String value. This option sets the
+    document title of the generated HTML document.
+
+    <p>Defaults to 'Log4J Log Messages'.
+  */
+  public
+  void setTitle(String title) {
+    this.title = title;
+  }
+
+  /**
+     Returns the current value of the <b>Title</b> option.
+  */
+  public
+  String getTitle() {
+    return title;
+  }
+
+ /**
+     Returns the content type output by this layout, i.e "text/html".
+  */
+  public
+  String getContentType() {
+    return "text/html";
+  }
+
+  /**
+     No options to activate.
+  */
+  public
+  void activateOptions() {
+  }
+
+  public
+  String format(LoggingEvent event) {
+
+    if(sbuf.capacity() > MAX_CAPACITY) {
+      sbuf = new StringBuffer(BUF_SIZE);
+    } else {
+      sbuf.setLength(0);
+    }
+
+    sbuf.append(Layout.LINE_SEP + "<tr>" + Layout.LINE_SEP);
+
+    sbuf.append("<td>");
+    sbuf.append(event.timeStamp - LoggingEvent.getStartTime());
+    sbuf.append("</td>" + Layout.LINE_SEP);
+
+    String escapedThread = Transform.escapeTags(event.getThreadName());
+    sbuf.append("<td title=\"" + escapedThread + " thread\">");
+    sbuf.append(escapedThread);
+    sbuf.append("</td>" + Layout.LINE_SEP);
+
+    sbuf.append("<td title=\"Level\">");
+    if (event.getLevel().equals(Level.DEBUG)) {
+      sbuf.append("<font color=\"#339933\">");
+      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
+      sbuf.append("</font>");
+    }
+    else if(event.getLevel().isGreaterOrEqual(Level.WARN)) {
+      sbuf.append("<font color=\"#993300\"><strong>");
+      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
+      sbuf.append("</strong></font>");
+    } else {
+      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
+    }
+    sbuf.append("</td>" + Layout.LINE_SEP);
+
+    String escapedLogger = Transform.escapeTags(event.getLoggerName());
+    sbuf.append("<td title=\"" + escapedLogger + " category\">");
+    sbuf.append(escapedLogger);
+    sbuf.append("</td>" + Layout.LINE_SEP);
+
+    if(locationInfo) {
+      LocationInfo locInfo = event.getLocationInformation();
+      sbuf.append("<td>");
+      sbuf.append(Transform.escapeTags(locInfo.getFileName()));
+      sbuf.append(':');
+      sbuf.append(locInfo.getLineNumber());
+      sbuf.append("</td>" + Layout.LINE_SEP);
+    }
+
+    sbuf.append("<td title=\"Message\">");
+    sbuf.append(Transform.escapeTags(event.getRenderedMessage()));
+    sbuf.append("</td>" + Layout.LINE_SEP);
+    sbuf.append("</tr>" + Layout.LINE_SEP);
+
+    if (event.getNDC() != null) {
+      sbuf.append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : xx-small;\" colspan=\"6\" title=\"Nested Diagnostic Context\">");
+      sbuf.append("NDC: " + Transform.escapeTags(event.getNDC()));
+      sbuf.append("</td></tr>" + Layout.LINE_SEP);
+    }
+
+    String[] s = event.getThrowableStrRep();
+    if(s != null) {
+      sbuf.append("<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : xx-small;\" colspan=\"6\">");
+      appendThrowableAsHTML(s, sbuf);
+      sbuf.append("</td></tr>" + Layout.LINE_SEP);
+    }
+
+    return sbuf.toString();
+  }
+
+  void appendThrowableAsHTML(String[] s, StringBuffer sbuf) {
+    if(s != null) {
+      int len = s.length;
+      if(len == 0) {
+        return;
+    }
+      sbuf.append(Transform.escapeTags(s[0]));
+      sbuf.append(Layout.LINE_SEP);
+      for(int i = 1; i < len; i++) {
+       sbuf.append(TRACE_PREFIX);
+       sbuf.append(Transform.escapeTags(s[i]));
+       sbuf.append(Layout.LINE_SEP);
+      }
+    }
+  }
+
+  /**
+     Returns appropriate HTML headers.
+  */
+  public
+  String getHeader() {
+    StringBuffer sbuf = new StringBuffer();
+    sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"  + Layout.LINE_SEP);
+    sbuf.append("<html>" + Layout.LINE_SEP);
+    sbuf.append("<head>" + Layout.LINE_SEP);
+    sbuf.append("<title>" + title + "</title>" + Layout.LINE_SEP);
+    sbuf.append("<style type=\"text/css\">"  + Layout.LINE_SEP);
+    sbuf.append("<!--"  + Layout.LINE_SEP);
+    sbuf.append("body, table {font-family: arial,sans-serif; font-size: x-small;}" + Layout.LINE_SEP);
+    sbuf.append("th {background: #336699; color: #FFFFFF; text-align: left;}" + Layout.LINE_SEP);
+    sbuf.append("-->" + Layout.LINE_SEP);
+    sbuf.append("</style>" + Layout.LINE_SEP);
+    sbuf.append("</head>" + Layout.LINE_SEP);
+    sbuf.append("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">" + Layout.LINE_SEP);
+    sbuf.append("<hr size=\"1\" noshade>" + Layout.LINE_SEP);
+    sbuf.append("Log session start time " + new java.util.Date() + "<br>" + Layout.LINE_SEP);
+    sbuf.append("<br>" + Layout.LINE_SEP);
+    sbuf.append("<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">" + Layout.LINE_SEP);
+    sbuf.append("<tr>" + Layout.LINE_SEP);
+    sbuf.append("<th>Time</th>" + Layout.LINE_SEP);
+    sbuf.append("<th>Thread</th>" + Layout.LINE_SEP);
+    sbuf.append("<th>Level</th>" + Layout.LINE_SEP);
+    sbuf.append("<th>Category</th>" + Layout.LINE_SEP);
+    if(locationInfo) {
+      sbuf.append("<th>File:Line</th>" + Layout.LINE_SEP);
+    }
+    sbuf.append("<th>Message</th>" + Layout.LINE_SEP);
+    sbuf.append("</tr>" + Layout.LINE_SEP);
+    return sbuf.toString();
+  }
+
+  /**
+     Returns the appropriate HTML footers.
+  */
+  public
+  String getFooter() {
+    StringBuffer sbuf = new StringBuffer();
+    sbuf.append("</table>" + Layout.LINE_SEP);
+    sbuf.append("<br>" + Layout.LINE_SEP);
+    sbuf.append("</body></html>");
+    return sbuf.toString();
+  }
+
+  /**
+     The HTML layout handles the throwable contained in logging
+     events. Hence, this method return <code>false</code>.  */
+  public
+  boolean ignoresThrowable() {
+    return false;
+  }
+}
diff --git a/srcjar/org/apache/log4j/Hierarchy.java b/srcjar/org/apache/log4j/Hierarchy.java
new file mode 100644 (file)
index 0000000..6c82c98
--- /dev/null
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// WARNING This class MUST not have references to the Category or
+// WARNING RootCategory classes in its static initiliazation neither
+// WARNING directly nor indirectly.
+
+// Contributors:
+//                Luke Blanshard <luke@quiq.com>
+//                Mario Schomburg - IBM Global Services/Germany
+//                Anders Kristensen
+//                Igor Poteryaev
+
+package org.apache.log4j;
+
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+
+/**
+   This class is specialized in retrieving loggers by name and also
+   maintaining the logger hierarchy.
+
+   <p><em>The casual user does not have to deal with this class
+   directly.</em>
+
+   <p>The structure of the logger hierarchy is maintained by the
+   {@link #getLogger} method. The hierarchy is such that children link
+   to their parent but parents do not have any pointers to their
+   children. Moreover, loggers can be instantiated in any order, in
+   particular descendant before ancestor.
+
+   <p>In case a descendant is created before a particular ancestor,
+   then it creates a provision node for the ancestor and adds itself
+   to the provision node. Other descendants of the same ancestor add
+   themselves to the previously created provision node.
+
+   @author Ceki G&uuml;lc&uuml;
+
+*/
+public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
+
+  private LoggerFactory defaultFactory;
+  private Vector listeners;
+
+  Hashtable ht;
+  Logger root;
+  RendererMap rendererMap;
+
+  int thresholdInt;
+  Level threshold;
+
+  boolean emittedNoAppenderWarning = false;
+  boolean emittedNoResourceBundleWarning = false;
+
+  private ThrowableRenderer throwableRenderer = null;
+
+  /**
+     Create a new logger hierarchy.
+
+     @param root The root of the new hierarchy.
+
+   */
+  public
+  Hierarchy(Logger root) {
+    ht = new Hashtable();
+    listeners = new Vector(1);
+    this.root = root;
+    // Enable all level levels by default.
+    setThreshold(Level.ALL);
+    this.root.setHierarchy(this);
+    rendererMap = new RendererMap();
+    defaultFactory = new DefaultCategoryFactory();
+  }
+
+  /**
+     Add an object renderer for a specific class.
+   */
+  public
+  void addRenderer(Class classToRender, ObjectRenderer or) {
+    rendererMap.put(classToRender, or);
+  }
+
+  public
+  void addHierarchyEventListener(HierarchyEventListener listener) {
+    if(listeners.contains(listener)) {
+      LogLog.warn("Ignoring attempt to add an existent listener.");
+    } else {
+      listeners.addElement(listener);
+    }
+  }
+
+  /**
+     This call will clear all logger definitions from the internal
+     hashtable. Invoking this method will irrevocably mess up the
+     logger hierarchy.
+
+     <p>You should <em>really</em> know what you are doing before
+     invoking this method.
+
+     @since 0.9.0 */
+  public
+  void clear() {
+    //System.out.println("\n\nAbout to clear internal hash table.");
+    ht.clear();
+  }
+
+  public
+  void emitNoAppenderWarning(Category cat) {
+    // No appenders in hierarchy, warn user only once.
+    if(!this.emittedNoAppenderWarning) {
+      LogLog.warn("No appenders could be found for logger (" +
+                  cat.getName() + ").");
+      LogLog.warn("Please initialize the log4j system properly.");
+      LogLog.warn("See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.");
+      this.emittedNoAppenderWarning = true;
+    }
+  }
+
+  /**
+     Check if the named logger exists in the hierarchy. If so return
+     its reference, otherwise returns <code>null</code>.
+
+     @param name The name of the logger to search for.
+
+  */
+  public
+  Logger exists(String name) {
+    Object o = ht.get(new CategoryKey(name));
+    if(o instanceof Logger) {
+      return (Logger) o;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+     The string form of {@link #setThreshold(Level)}.
+  */
+  public
+  void setThreshold(String levelStr) {
+    Level l = Level.toLevel(levelStr, null);
+    if(l != null) {
+      setThreshold(l);
+    } else {
+      LogLog.warn("Could not convert ["+levelStr+"] to Level.");
+    }
+  }
+
+
+  /**
+     Enable logging for logging requests with level <code>l</code> or
+     higher. By default all levels are enabled.
+
+     @param l The minimum level for which logging requests are sent to
+     their appenders.  */
+  public
+  void setThreshold(Level l) {
+    if(l != null) {
+      thresholdInt = l.level;
+      threshold = l;
+    }
+  }
+
+  public
+  void fireAddAppenderEvent(Category logger, Appender appender) {
+    if(listeners != null) {
+      int size = listeners.size();
+      HierarchyEventListener listener;
+      for(int i = 0; i < size; i++) {
+       listener = (HierarchyEventListener) listeners.elementAt(i);
+       listener.addAppenderEvent(logger, appender);
+      }
+    }
+  }
+
+  void fireRemoveAppenderEvent(Category logger, Appender appender) {
+    if(listeners != null) {
+      int size = listeners.size();
+      HierarchyEventListener listener;
+      for(int i = 0; i < size; i++) {
+       listener = (HierarchyEventListener) listeners.elementAt(i);
+       listener.removeAppenderEvent(logger, appender);
+      }
+    }
+  }
+
+  /**
+     Returns a {@link Level} representation of the <code>enable</code>
+     state.
+
+     @since 1.2 */
+  public
+  Level getThreshold() {
+    return threshold;
+  }
+
+  /**
+     Returns an integer representation of the this repository's
+     threshold.
+
+     @since 1.2 */
+  //public
+  //int getThresholdInt() {
+  //  return thresholdInt;
+  //}
+
+
+  /**
+     Return a new logger instance named as the first parameter using
+     the default factory.
+
+     <p>If a logger of that name already exists, then it will be
+     returned.  Otherwise, a new logger will be instantiated and
+     then linked with its existing ancestors as well as children.
+
+     @param name The name of the logger to retrieve.
+
+ */
+  public
+  Logger getLogger(String name) {
+    return getLogger(name, defaultFactory);
+  }
+
+ /**
+     Return a new logger instance named as the first parameter using
+     <code>factory</code>.
+
+     <p>If a logger of that name already exists, then it will be
+     returned.  Otherwise, a new logger will be instantiated by the
+     <code>factory</code> parameter and linked with its existing
+     ancestors as well as children.
+
+     @param name The name of the logger to retrieve.
+     @param factory The factory that will make the new logger instance.
+
+ */
+  public
+  Logger getLogger(String name, LoggerFactory factory) {
+    //System.out.println("getInstance("+name+") called.");
+    CategoryKey key = new CategoryKey(name);
+    // Synchronize to prevent write conflicts. Read conflicts (in
+    // getChainedLevel method) are possible only if variable
+    // assignments are non-atomic.
+    Logger logger;
+
+    synchronized(ht) {
+      Object o = ht.get(key);
+      if(o == null) {
+       logger = factory.makeNewLoggerInstance(name);
+       logger.setHierarchy(this);
+       ht.put(key, logger);
+       updateParents(logger);
+       return logger;
+      } else if(o instanceof Logger) {
+       return (Logger) o;
+      } else if (o instanceof ProvisionNode) {
+       //System.out.println("("+name+") ht.get(this) returned ProvisionNode");
+       logger = factory.makeNewLoggerInstance(name);
+       logger.setHierarchy(this);
+       ht.put(key, logger);
+       updateChildren((ProvisionNode) o, logger);
+       updateParents(logger);
+       return logger;
+      }
+      else {
+       // It should be impossible to arrive here
+       return null;  // but let's keep the compiler happy.
+      }
+    }
+  }
+
+  /**
+     Returns all the currently defined categories in this hierarchy as
+     an {@link java.util.Enumeration Enumeration}.
+
+     <p>The root logger is <em>not</em> included in the returned
+     {@link Enumeration}.  */
+  public
+  Enumeration getCurrentLoggers() {
+    // The accumlation in v is necessary because not all elements in
+    // ht are Logger objects as there might be some ProvisionNodes
+    // as well.
+    Vector v = new Vector(ht.size());
+
+    Enumeration elems = ht.elements();
+    while(elems.hasMoreElements()) {
+      Object o = elems.nextElement();
+      if(o instanceof Logger) {
+       v.addElement(o);
+      }
+    }
+    return v.elements();
+  }
+
+  /**
+     @deprecated Please use {@link #getCurrentLoggers} instead.
+   */
+  public
+  Enumeration getCurrentCategories() {
+    return getCurrentLoggers();
+  }
+
+
+  /**
+     Get the renderer map for this hierarchy.
+  */
+  public
+  RendererMap getRendererMap() {
+    return rendererMap;
+  }
+
+
+  /**
+     Get the root of this hierarchy.
+
+     @since 0.9.0
+   */
+  public
+  Logger getRootLogger() {
+    return root;
+  }
+
+  /**
+     This method will return <code>true</code> if this repository is
+     disabled for <code>level</code> object passed as parameter and
+     <code>false</code> otherwise. See also the {@link
+     #setThreshold(Level) threshold} emthod.  */
+  public
+  boolean isDisabled(int level) {
+    return thresholdInt > level;
+  }
+
+  /**
+     @deprecated Deprecated with no replacement.
+  */
+  public
+  void overrideAsNeeded(String override) {
+    LogLog.warn("The Hiearchy.overrideAsNeeded method has been deprecated.");
+  }
+
+  /**
+     Reset all values contained in this hierarchy instance to their
+     default.  This removes all appenders from all categories, sets
+     the level of all non-root categories to <code>null</code>,
+     sets their additivity flag to <code>true</code> and sets the level
+     of the root logger to {@link Level#DEBUG DEBUG}.  Moreover,
+     message disabling is set its default "off" value.
+
+     <p>Existing categories are not removed. They are just reset.
+
+     <p>This method should be used sparingly and with care as it will
+     block all logging until it is completed.</p>
+
+     @since 0.8.5 */
+  public
+  void resetConfiguration() {
+
+    getRootLogger().setLevel(Level.DEBUG);
+    root.setResourceBundle(null);
+    setThreshold(Level.ALL);
+
+    // the synchronization is needed to prevent JDK 1.2.x hashtable
+    // surprises
+    synchronized(ht) {
+      shutdown(); // nested locks are OK
+
+      Enumeration cats = getCurrentLoggers();
+      while(cats.hasMoreElements()) {
+       Logger c = (Logger) cats.nextElement();
+       c.setLevel(null);
+       c.setAdditivity(true);
+       c.setResourceBundle(null);
+      }
+    }
+    rendererMap.clear();
+    throwableRenderer = null;
+  }
+
+  /**
+     Does nothing.
+
+     @deprecated Deprecated with no replacement.
+   */
+  public
+  void setDisableOverride(String override) {
+    LogLog.warn("The Hiearchy.setDisableOverride method has been deprecated.");
+  }
+
+
+
+  /**
+     Used by subclasses to add a renderer to the hierarchy passed as parameter.
+   */
+  public
+  void setRenderer(Class renderedClass, ObjectRenderer renderer) {
+    rendererMap.put(renderedClass, renderer);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void setThrowableRenderer(final ThrowableRenderer renderer) {
+      throwableRenderer = renderer;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public ThrowableRenderer getThrowableRenderer() {
+      return throwableRenderer;
+  }
+
+
+  /**
+     Shutting down a hierarchy will <em>safely</em> close and remove
+     all appenders in all categories including the root logger.
+
+     <p>Some appenders such as {@link org.apache.log4j.net.SocketAppender}
+     and {@link AsyncAppender} need to be closed before the
+     application exists. Otherwise, pending logging events might be
+     lost.
+
+     <p>The <code>shutdown</code> method is careful to close nested
+     appenders before closing regular appenders. This is allows
+     configurations where a regular appender is attached to a logger
+     and again to a nested appender.
+
+
+     @since 1.0 */
+  public
+  void shutdown() {
+    Logger root = getRootLogger();
+
+    // begin by closing nested appenders
+    root.closeNestedAppenders();
+
+    synchronized(ht) {
+      Enumeration cats = this.getCurrentLoggers();
+      while(cats.hasMoreElements()) {
+       Logger c = (Logger) cats.nextElement();
+       c.closeNestedAppenders();
+      }
+
+      // then, remove all appenders
+      root.removeAllAppenders();
+      cats = this.getCurrentLoggers();
+      while(cats.hasMoreElements()) {
+       Logger c = (Logger) cats.nextElement();
+       c.removeAllAppenders();
+      }
+    }
+  }
+
+
+  /**
+     This method loops through all the *potential* parents of
+     'cat'. There 3 possible cases:
+
+     1) No entry for the potential parent of 'cat' exists
+
+        We create a ProvisionNode for this potential parent and insert
+        'cat' in that provision node.
+
+     2) There entry is of type Logger for the potential parent.
+
+        The entry is 'cat's nearest existing parent. We update cat's
+        parent field with this entry. We also break from the loop
+        because updating our parent's parent is our parent's
+        responsibility.
+
+     3) There entry is of type ProvisionNode for this potential parent.
+
+        We add 'cat' to the list of children for this potential parent.
+   */
+  final
+  private
+  void updateParents(Logger cat) {
+    String name = cat.name;
+    int length = name.length();
+    boolean parentFound = false;
+
+    //System.out.println("UpdateParents called for " + name);
+
+    // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z"
+    for(int i = name.lastIndexOf('.', length-1); i >= 0;
+                                        i = name.lastIndexOf('.', i-1))  {
+      String substr = name.substring(0, i);
+
+      //System.out.println("Updating parent : " + substr);
+      CategoryKey key = new CategoryKey(substr); // simple constructor
+      Object o = ht.get(key);
+      // Create a provision node for a future parent.
+      if(o == null) {
+       //System.out.println("No parent "+substr+" found. Creating ProvisionNode.");
+       ProvisionNode pn = new ProvisionNode(cat);
+       ht.put(key, pn);
+      } else if(o instanceof Category) {
+       parentFound = true;
+       cat.parent = (Category) o;
+       //System.out.println("Linking " + cat.name + " -> " + ((Category) o).name);
+       break; // no need to update the ancestors of the closest ancestor
+      } else if(o instanceof ProvisionNode) {
+       ((ProvisionNode) o).addElement(cat);
+      } else {
+       Exception e = new IllegalStateException("unexpected object type " +
+                                       o.getClass() + " in ht.");
+       e.printStackTrace();
+      }
+    }
+    // If we could not find any existing parents, then link with root.
+    if(!parentFound) {
+        cat.parent = root;
+    }
+  }
+
+  /**
+      We update the links for all the children that placed themselves
+      in the provision node 'pn'. The second argument 'cat' is a
+      reference for the newly created Logger, parent of all the
+      children in 'pn'
+
+      We loop on all the children 'c' in 'pn':
+
+         If the child 'c' has been already linked to a child of
+         'cat' then there is no need to update 'c'.
+
+        Otherwise, we set cat's parent field to c's parent and set
+        c's parent field to cat.
+
+  */
+  final
+  private
+  void updateChildren(ProvisionNode pn, Logger logger) {
+    //System.out.println("updateChildren called for " + logger.name);
+    final int last = pn.size();
+
+    for(int i = 0; i < last; i++) {
+      Logger l = (Logger) pn.elementAt(i);
+      //System.out.println("Updating child " +p.name);
+
+      // Unless this child already points to a correct (lower) parent,
+      // make cat.parent point to l.parent and l.parent to cat.
+      if(!l.parent.name.startsWith(logger.name)) {
+       logger.parent = l.parent;
+       l.parent = logger;
+      }
+    }
+  }
+
+}
+
+
diff --git a/srcjar/org/apache/log4j/Layout.java b/srcjar/org/apache/log4j/Layout.java
new file mode 100644 (file)
index 0000000..63015aa
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   Extend this abstract class to create your own log layout format.
+   
+   @author Ceki G&uuml;lc&uuml;
+
+*/
+  
+public abstract class Layout implements OptionHandler {
+
+  // Note that the line.separator property can be looked up even by
+  // applets.
+  public final static String LINE_SEP = System.getProperty("line.separator");
+  public final static int LINE_SEP_LEN  = LINE_SEP.length();
+
+
+  /**
+     Implement this method to create your own layout format.
+  */
+  abstract
+  public
+  String format(LoggingEvent event);
+
+  /**
+     Returns the content type output by this layout. The base class
+     returns "text/plain". 
+  */
+  public
+  String getContentType() {
+    return "text/plain";
+  }
+
+  /**
+     Returns the header for the layout format. The base class returns
+     <code>null</code>.  */
+  public
+  String getHeader() {
+    return null;
+  }
+
+  /**
+     Returns the footer for the layout format. The base class returns
+     <code>null</code>.  */
+  public
+  String getFooter() {
+    return null;
+  }
+
+
+
+  /**
+     If the layout handles the throwable object contained within
+     {@link LoggingEvent}, then the layout should return
+     <code>false</code>. Otherwise, if the layout ignores throwable
+     object, then the layout should return <code>true</code>.
+     If ignoresThrowable is true, the appender is responsible for
+     rendering the throwable.
+
+     <p>The {@link SimpleLayout}, {@link TTCCLayout}, {@link
+     PatternLayout} all return <code>true</code>. The {@link
+     org.apache.log4j.xml.XMLLayout} returns <code>false</code>.
+
+     @since 0.8.4 */
+  abstract
+  public
+  boolean ignoresThrowable();
+
+}
diff --git a/srcjar/org/apache/log4j/Level.java b/srcjar/org/apache/log4j/Level.java
new file mode 100644 (file)
index 0000000..a61f177
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:  Kitching Simon <Simon.Kitching@orange.ch>
+//                Nicholas Wolff
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Defines the minimum set of levels recognized by the system, that is <code>OFF</code>, <code>FATAL</code>, <code>ERROR</code>,
+ * <code>WARN</code>, <code>INFO</code>, <code>DEBUG</code> and <code>ALL</code>.
+ * 
+ * <p>
+ * The <code>Level</code> class may be subclassed to define a larger level set.
+ * </p>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class Level extends Priority implements Serializable {
+
+    private static final String ALL_NAME = "ALL";
+
+    private static final String TRACE_NAME = "TRACE";
+
+    private static final String DEBUG_NAME = "DEBUG";
+
+    private static final String INFO_NAME = "INFO";
+
+    private static final String WARN_NAME = "WARN";
+
+    private static final String ERROR_NAME = "ERROR";
+
+    private static final String FATAL_NAME = "FATAL";
+
+    private static final String OFF_NAME = "OFF";
+
+    /**
+     * TRACE level integer value.
+     * 
+     * @since 1.2.12
+     */
+    public static final int TRACE_INT = 5000;
+
+    /**
+     * The <code>OFF</code> has the highest possible rank and is intended to turn off logging.
+     */
+    final static public Level OFF = new Level(OFF_INT, OFF_NAME, 0);
+
+    /**
+     * The <code>FATAL</code> level designates very severe error events that will presumably lead the application to abort.
+     */
+    final static public Level FATAL = new Level(FATAL_INT, FATAL_NAME, 0);
+
+    /**
+     * The <code>ERROR</code> level designates error events that might still allow the application to continue running.
+     */
+    final static public Level ERROR = new Level(ERROR_INT, ERROR_NAME, 3);
+
+    /**
+     * The <code>WARN</code> level designates potentially harmful situations.
+     */
+    final static public Level WARN = new Level(WARN_INT, WARN_NAME, 4);
+
+    /**
+     * The <code>INFO</code> level designates informational messages that highlight the progress of the application at coarse-grained level.
+     */
+    final static public Level INFO = new Level(INFO_INT, INFO_NAME, 6);
+
+    /**
+     * The <code>DEBUG</code> Level designates fine-grained informational events that are most useful to debug an application.
+     */
+    final static public Level DEBUG = new Level(DEBUG_INT, DEBUG_NAME, 7);
+
+    /**
+     * The <code>TRACE</code> Level designates finer-grained informational events than the <code>DEBUG</code level.
+     * 
+     * @since 1.2.12
+     */
+    public static final Level TRACE = new Level(TRACE_INT, TRACE_NAME, 7);
+
+    /**
+     * The <code>ALL</code> has the lowest possible rank and is intended to turn on all logging.
+     */
+    final static public Level ALL = new Level(ALL_INT, ALL_NAME, 7);
+
+    /**
+     * Serialization version id.
+     */
+    static final long serialVersionUID = 3491141966387921974L;
+
+    /**
+     * Instantiate a Level object.
+     */
+    protected Level(int level, String levelStr, int syslogEquivalent) {
+        super(level, levelStr, syslogEquivalent);
+    }
+
+    /**
+     * Convert the string passed as argument to a level. If the conversion fails, then this method returns {@link #DEBUG}.
+     */
+    public static Level toLevel(String sArg) {
+        return toLevel(sArg, Level.DEBUG);
+    }
+
+    /**
+     * Convert an integer passed as argument to a level. If the conversion fails, then this method returns {@link #DEBUG}.
+     */
+    public static Level toLevel(int val) {
+        return toLevel(val, Level.DEBUG);
+    }
+
+    /**
+     * Convert an integer passed as argument to a level. If the conversion fails, then this method returns the specified default.
+     */
+    public static Level toLevel(int val, Level defaultLevel) {
+        switch (val) {
+        case ALL_INT:
+            return ALL;
+        case DEBUG_INT:
+            return Level.DEBUG;
+        case INFO_INT:
+            return Level.INFO;
+        case WARN_INT:
+            return Level.WARN;
+        case ERROR_INT:
+            return Level.ERROR;
+        case FATAL_INT:
+            return Level.FATAL;
+        case OFF_INT:
+            return OFF;
+        case TRACE_INT:
+            return Level.TRACE;
+        default:
+            return defaultLevel;
+        }
+    }
+
+    /**
+     * Convert the string passed as argument to a level. If the conversion fails, then this method returns the value of
+     * <code>defaultLevel</code>.
+     */
+    public static Level toLevel(String sArg, Level defaultLevel) {
+        if (sArg == null) {
+            return defaultLevel;
+        }
+        String s = sArg.toUpperCase();
+
+        if (s.equals(ALL_NAME)) {
+            return Level.ALL;
+        }
+        if (s.equals(DEBUG_NAME)) {
+            return Level.DEBUG;
+        }
+        if (s.equals(INFO_NAME)) {
+            return Level.INFO;
+        }
+        if (s.equals(WARN_NAME)) {
+            return Level.WARN;
+        }
+        if (s.equals(ERROR_NAME)) {
+            return Level.ERROR;
+        }
+        if (s.equals(FATAL_NAME)) {
+            return Level.FATAL;
+        }
+        if (s.equals(OFF_NAME)) {
+            return Level.OFF;
+        }
+        if (s.equals(TRACE_NAME)) {
+            return Level.TRACE;
+        }
+        //
+        // For Turkish i problem, see bug 40937
+        //
+        if (s.equals("\u0130NFO")) {
+            return Level.INFO;
+        }
+        return defaultLevel;
+    }
+
+    /**
+     * Custom deserialization of Level.
+     * 
+     * @param s
+     *            serialization stream.
+     * @throws IOException
+     *             if IO exception.
+     * @throws ClassNotFoundException
+     *             if class not found.
+     */
+    private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
+        s.defaultReadObject();
+        level = s.readInt();
+        syslogEquivalent = s.readInt();
+        levelStr = s.readUTF();
+        if (levelStr == null) {
+            levelStr = "";
+        }
+    }
+
+    /**
+     * Serialize level.
+     * 
+     * @param s
+     *            serialization stream.
+     * @throws IOException
+     *             if exception during serialization.
+     */
+    private void writeObject(final ObjectOutputStream s) throws IOException {
+        s.defaultWriteObject();
+        s.writeInt(level);
+        s.writeInt(syslogEquivalent);
+        s.writeUTF(levelStr);
+    }
+
+    /**
+     * Resolved deserialized level to one of the stock instances. May be overriden in classes derived from Level.
+     * 
+     * @return resolved object.
+     * @throws ObjectStreamException
+     *             if exception during resolution.
+     */
+    private Object readResolve() throws ObjectStreamException {
+        //
+        // if the deserizalized object is exactly an instance of Level
+        //
+        if (getClass() == Level.class) {
+            return toLevel(level);
+        }
+        //
+        // extension of Level can't substitute stock item
+        //
+        return this;
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/LogMF.java b/srcjar/org/apache/log4j/LogMF.java
new file mode 100644 (file)
index 0000000..2df99fc
--- /dev/null
@@ -0,0 +1,1677 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.util.Locale;
+
+
+/**
+ * This class provides parameterized logging services
+ * using the pattern syntax of java.text.MessageFormat.
+ * Message formatting is only performed when the 
+ * request exceeds the threshold level of the logger.
+ * When the pattern only contains literal text and
+ * default conversion patterns (that is "{0}" and similar)
+ * a simple fast compatible formatter is used.  
+ * If the pattern contains more complex conversion patterns,
+ * formatting will be delegated to java.text.MessageFormatter
+ * which can be substantially slower.
+ *
+ * @see org.apache.log4j.LogSF
+ * @since 1.2.16
+ *
+ */
+public final class LogMF extends LogXF {
+    /**
+     * private constructor.
+     *
+     */
+    private LogMF() {
+    }
+
+    /**
+     * Number format.
+     */
+    private static NumberFormat numberFormat = null;
+    /**
+     * Locale at time of last number format request.
+     */
+    private static Locale numberLocale = null;
+    /**
+     * Date format.
+     */
+    private static DateFormat dateFormat = null;
+    /**
+     * Locale at time of last date format request.
+     */
+    private static Locale dateLocale = null;
+
+    /**
+     * Format number.
+     * @param n number to format, may not be null.
+     * @return formatted value.
+     */
+    private static synchronized String formatNumber(final Object n) {
+        Locale currentLocale = Locale.getDefault();
+        if (currentLocale != numberLocale || numberFormat == null) {
+            numberLocale = currentLocale;
+            numberFormat = NumberFormat.getInstance(currentLocale);
+        }
+        return numberFormat.format(n);
+    }
+
+
+    /**
+     * Format date.
+     * @param d date, may not be null.
+     * @return formatted value.
+     */
+    private static synchronized String formatDate(final Object d) {
+        Locale currentLocale = Locale.getDefault();
+        if (currentLocale != dateLocale || dateFormat == null) {
+            dateLocale = currentLocale;
+            dateFormat = DateFormat.getDateTimeInstance(
+                                DateFormat.SHORT,
+                                DateFormat.SHORT,
+                                currentLocale);
+        }
+        return dateFormat.format(d);
+    }
+
+    /**
+     * Format a single parameter like a "{0}" formatting specifier.
+     *
+     * @param arg0 parameter, may be null.
+     * @return string representation of arg0.
+     */
+    private static String formatObject(final Object arg0) {
+        if (arg0 instanceof String) {
+            return arg0.toString();
+        } else if (arg0 instanceof Double ||
+                   arg0 instanceof Float) {
+           return formatNumber(arg0);
+        } else if (arg0 instanceof Date) {
+            return formatDate(arg0);
+        }
+        return String.valueOf(arg0);
+    }
+
+
+    /**
+     * Determines if pattern contains only {n} format elements
+     * and not apostrophes.
+     *
+     * @param pattern pattern, may not be null.
+     * @return true if pattern only contains {n} format elements.
+     */
+    private static boolean isSimple(final String pattern) {
+        if (pattern.indexOf('\'') != -1) {
+            return false;
+        }
+        for(int pos = pattern.indexOf('{');
+            pos != -1;
+            pos = pattern.indexOf('{', pos + 1)) {
+            if (pos + 2 >= pattern.length() ||
+                    pattern.charAt(pos+2) != '}' ||
+                    pattern.charAt(pos+1) < '0' ||
+                    pattern.charAt(pos+1) > '9') {
+                return false;
+            }
+        }
+        return true;
+
+    }
+
+    /**
+     * Formats arguments using MessageFormat.
+     * @param pattern pattern, may be malformed or null.
+     * @param arguments arguments, may be null or mismatched.
+     * @return Message string or null
+     */
+    private static String format(final String pattern,
+                                 final Object[] arguments) {
+        if (pattern == null) {
+            return null;
+        } else if(isSimple(pattern)) {
+            String formatted[] = new String[10];
+            int prev = 0;
+            String retval = "";
+            int pos = pattern.indexOf('{');
+            while(pos >= 0) {
+                if(pos + 2 < pattern.length() && 
+                      pattern.charAt(pos+2) == '}' &&
+                      pattern.charAt(pos+1) >= '0' &&
+                      pattern.charAt(pos+1) <= '9') {
+                    int index = pattern.charAt(pos+1) - '0';
+                    retval += pattern.substring(prev, pos);
+                    if (formatted[index] == null) {
+                         if (arguments == null || index >= arguments.length) {
+                            formatted[index] = pattern.substring(pos, pos+3);
+                         } else {
+                            formatted[index] = formatObject(arguments[index]);
+                         }
+                    }
+                    retval += formatted[index];
+                    prev = pos + 3;
+                    pos = pattern.indexOf('{', prev);
+                } else {
+                    pos = pattern.indexOf('{', pos + 1);
+                }
+            }
+            retval += pattern.substring(prev);
+            return retval;
+        }
+        try {
+            return MessageFormat.format(pattern, arguments);
+        } catch (IllegalArgumentException ex) {
+            return pattern;
+        }
+    }
+
+    /**
+     * Formats a single argument using MessageFormat.
+     * @param pattern pattern, may be malformed or null.
+     * @param arguments arguments, may be null or mismatched.
+     * @return Message string or null
+     */
+    private static String format(final String pattern,
+                                 final Object arg0) {
+        if (pattern == null) {
+            return null;
+        } else if(isSimple(pattern)) {
+            String formatted = null;
+            int prev = 0;
+            String retval = "";
+            int pos = pattern.indexOf('{');
+            while(pos >= 0) {
+                if(pos + 2 < pattern.length() &&
+                      pattern.charAt(pos+2) == '}' &&
+                      pattern.charAt(pos+1) >= '0' &&
+                      pattern.charAt(pos+1) <= '9') {
+                    int index = pattern.charAt(pos+1) - '0';
+                    retval += pattern.substring(prev, pos);
+                    if (index != 0) {
+                        retval += pattern.substring(pos, pos+3);
+                    } else {
+                        if (formatted == null) {
+                            formatted = formatObject(arg0);
+                        }
+                        retval += formatted;
+                    }
+                    prev = pos + 3;
+                    pos = pattern.indexOf('{', prev);
+                } else {
+                    pos = pattern.indexOf('{', pos + 1);
+                }
+            }
+            retval += pattern.substring(prev);
+            return retval;
+        }
+        try {
+            return MessageFormat.format(pattern, new Object[] { arg0 });
+        } catch (IllegalArgumentException ex) {
+            return pattern;
+        }
+    }
+
+
+    /**
+     * Formats arguments using MessageFormat using a pattern from
+     * a resource bundle.
+     * @param resourceBundleName name of resource bundle, may be null.
+     * @param key key for pattern in resource bundle, may be null.
+     * @param arguments arguments, may be null or mismatched.
+     * @return Message string or null
+     */
+    private static String format(
+            final String resourceBundleName,
+            final String key,
+            final Object[] arguments) {
+        String pattern;
+        if (resourceBundleName != null) {
+            try {
+                ResourceBundle bundle =
+                        ResourceBundle.getBundle(resourceBundleName);
+                pattern = bundle.getString(key);
+            } catch (Exception ex) {
+                pattern = key;
+            }
+        } else {
+            pattern = key;
+        }
+        return format(pattern, arguments);
+    }
+
+
+    /**
+     * Fully Qualified Class Name of this class.
+     */
+    private static final String FQCN = LogMF.class.getName();
+
+    /**
+     * Equivalent of Logger.forcedLog.
+     *
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param msg message, may be null.
+     */
+    private static void forcedLog(final Logger logger,
+                                  final Level level,
+                                  final String msg) {
+        logger.callAppenders(new LoggingEvent(FQCN, logger, level, msg, null));
+    }
+
+    /**
+     * Equivalent of Logger.forcedLog.
+     *
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param msg message, may be null.
+     * @param t throwable.
+     */
+    private static void forcedLog(final Logger logger,
+                                  final Level level,
+                                  final String msg,
+                                  final Throwable t) {
+        logger.callAppenders(new LoggingEvent(FQCN, logger, level, msg, t));
+    }
+    /**
+         * Log a parameterized message at trace level.
+         * @param logger logger, may not be null.
+         * @param pattern pattern, may be null.
+         * @param arguments an array of arguments to be
+         *          formatted and substituted.
+         */
+    public static void trace(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at error level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void error(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            forcedLog(logger, Level.ERROR, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at fatal level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void fatal(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.FATAL)) {
+            forcedLog(logger, Level.FATAL, format(pattern, arguments));
+        }
+    }
+
+    /**
+         * Log a parameterized message at trace level.
+         * @param logger logger, may not be null.
+         * @param t throwable, may be null.
+         * @param pattern pattern, may be null.
+         * @param arguments an array of arguments to be
+         *          formatted and substituted.
+         */
+    public static void trace(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void debug(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void info(final Logger logger,
+                            final Throwable t,
+                            final String pattern,
+        final Object[] arguments) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void warn(final Logger logger,
+                            final Throwable t,
+                            final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at error level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void error(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            forcedLog(logger, Level.ERROR, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at fatal level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void fatal(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.FATAL)) {
+            forcedLog(logger, Level.FATAL, format(pattern, arguments), t);
+        }
+    }
+
+
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern,
+                    toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, parameters));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+     * @param t throwable, may be null.
+      * @param pattern pattern, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final Throwable t,
+                             final String pattern,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, parameters), t);
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(param1)));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final boolean param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final byte param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final char param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final short param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final int param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final long param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final float param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final double param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+     * Log a parameterized message at specified level.
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                            final Level level,
+                            final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at specifed level.
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                           final Level level,
+                           final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at specified level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param level level, may not be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                           final Level level,
+                           final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, parameters));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+     * @param t throwable, may be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final Throwable t,
+                             final String bundleName,
+                             final String key,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, parameters), t);
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param1)));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final boolean param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final char param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final byte param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final short param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final int param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final long param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final float param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final double param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param0, param1)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+     * @param param2 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1,
+                             final Object param2) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param0, param1, param2)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+     * @param param2 Parameter to the log message.
+     * @param param3 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1,
+                             final Object param2,
+                             final Object param3) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key,
+                            toArray(param0, param1, param2, param3)));
+        }
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/LogManager.java b/srcjar/org/apache/log4j/LogManager.java
new file mode 100644 (file)
index 0000000..ef48a0f
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.RepositorySelector;
+import org.apache.log4j.spi.DefaultRepositorySelector;
+import org.apache.log4j.spi.RootLogger;
+import org.apache.log4j.spi.NOPLoggerRepository;
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.helpers.LogLog;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+
+import java.util.Enumeration;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+
+/**
+ * Gets {@link Logger} instances and operates on the current {@link LoggerRepository}.
+ * 
+ * <p>
+ * When the <code>LogManager</code> class is loaded into memory the default initialization procedure runs. The default initialization
+ * procedure</a> is described in the <a href="../../../../manual.html#defaultInit">short log4j manual</a>.
+ * </p>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class LogManager {
+
+  /**
+   * @deprecated This variable is for internal use only. It will
+   * become package protected in future versions.
+   * */
+  static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
+  
+  static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  
+   
+  /**
+   * @deprecated This variable is for internal use only. It will
+   * become private in future versions.
+   * */
+  static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
+
+  /**
+   * @deprecated This variable is for internal use only. It will
+   * become private in future versions.
+   * */
+  static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
+
+  /**
+  * @deprecated This variable is for internal use only. It will
+  * become private in future versions.
+  */
+  public static final String DEFAULT_INIT_OVERRIDE_KEY = 
+                                                 "log4j.defaultInitOverride";
+
+
+  static private Object guard = null;
+  static private RepositorySelector repositorySelector;
+
+  static {
+    // By default we use a DefaultRepositorySelector which always returns 'h'.
+    Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
+    repositorySelector = new DefaultRepositorySelector(h);
+
+    /** Search for the properties file log4j.properties in the CLASSPATH.  */
+    String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
+                                                      null);
+
+    // if there is no default init override, then get the resource
+    // specified by the user or the default config file.
+    if(override == null || "false".equalsIgnoreCase(override)) {
+
+      String configurationOptionStr = OptionConverter.getSystemProperty(
+                                                         DEFAULT_CONFIGURATION_KEY, 
+                                                         null);
+
+      String configuratorClassName = OptionConverter.getSystemProperty(
+                                                   CONFIGURATOR_CLASS_KEY, 
+                                                  null);
+
+      URL url = null;
+
+      // if the user has not specified the log4j.configuration
+      // property, we search first for the file "log4j.xml" and then
+      // "log4j.properties"
+      if(configurationOptionStr == null) {     
+       url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
+       if(url == null) {
+         url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
+       }
+      } else {
+       try {
+         url = new URL(configurationOptionStr);
+       } catch (MalformedURLException ex) {
+         // so, resource is not a URL:
+         // attempt to get the resource from the class path
+         url = Loader.getResource(configurationOptionStr); 
+       }       
+      }
+      
+      // If we have a non-null url, then delegate the rest of the
+      // configuration to the OptionConverter.selectAndConfigure
+      // method.
+      if(url != null) {
+           LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
+        try {
+            OptionConverter.selectAndConfigure(url, configuratorClassName,
+                                          LogManager.getLoggerRepository());
+        } catch (NoClassDefFoundError e) {
+            LogLog.warn("Error during default initialization", e);
+        }
+      } else {
+           LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
+      }
+    } else {
+        LogLog.debug("Default initialization of overridden by " + 
+            DEFAULT_INIT_OVERRIDE_KEY + "property."); 
+    }  
+  } 
+
+  /**
+     Sets <code>LoggerFactory</code> but only if the correct
+     <em>guard</em> is passed as parameter.
+     
+     <p>Initally the guard is null.  If the guard is
+     <code>null</code>, then invoking this method sets the logger
+     factory and the guard. Following invocations will throw a {@link
+     IllegalArgumentException}, unless the previously set
+     <code>guard</code> is passed as the second parameter.
+
+     <p>This allows a high-level component to set the {@link
+     RepositorySelector} used by the <code>LogManager</code>.
+     
+     <p>For example, when tomcat starts it will be able to install its
+     own repository selector. However, if and when Tomcat is embedded
+     within JBoss, then JBoss will install its own repository selector
+     and Tomcat will use the repository selector set by its container,
+     JBoss.  */
+  static
+  public
+  void setRepositorySelector(RepositorySelector selector, Object guard) 
+                                                 throws IllegalArgumentException {
+    if((LogManager.guard != null) && (LogManager.guard != guard)) {
+      throw new IllegalArgumentException(
+           "Attempted to reset the LoggerFactory without possessing the guard.");
+    }
+
+    if(selector == null) {
+      throw new IllegalArgumentException("RepositorySelector must be non-null.");
+    }
+
+    LogManager.guard = guard;
+    LogManager.repositorySelector = selector;
+  }
+
+
+    /**
+     * This method tests if called from a method that
+     * is known to result in class members being abnormally
+     * set to null but is assumed to be harmless since the
+     * all classes are in the process of being unloaded.
+     *
+     * @param ex exception used to determine calling stack.
+     * @return true if calling stack is recognized as likely safe.
+     */
+  private static boolean isLikelySafeScenario(final Exception ex) {
+      StringWriter stringWriter = new StringWriter();
+      ex.printStackTrace(new PrintWriter(stringWriter));
+      String msg = stringWriter.toString();
+      return msg.indexOf("org.apache.catalina.loader.WebappClassLoader.stop") != -1;
+  }
+
+  static
+  public
+  LoggerRepository getLoggerRepository() {
+    if (repositorySelector == null) {
+        repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
+        guard = null;
+        Exception ex = new IllegalStateException("Class invariant violation");
+        String msg =
+                "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
+        if (isLikelySafeScenario(ex)) {
+            LogLog.debug(msg, ex);
+        } else {
+            LogLog.error(msg, ex);
+        }
+    }
+    return repositorySelector.getLoggerRepository();
+  }
+
+  /**
+     Retrieve the appropriate root logger.
+   */
+  public
+  static 
+  Logger getRootLogger() {
+     // Delegate the actual manufacturing of the logger to the logger repository.
+    return getLoggerRepository().getRootLogger();
+  }
+
+  /**
+     Retrieve the appropriate {@link Logger} instance.  
+  */
+  public
+  static 
+  Logger getLogger(final String name) {
+     // Delegate the actual manufacturing of the logger to the logger repository.
+    return getLoggerRepository().getLogger(name);
+  }
+
+ /**
+     Retrieve the appropriate {@link Logger} instance.  
+  */
+  public
+  static 
+  Logger getLogger(final Class clazz) {
+     // Delegate the actual manufacturing of the logger to the logger repository.
+    return getLoggerRepository().getLogger(clazz.getName());
+  }
+
+
+  /**
+     Retrieve the appropriate {@link Logger} instance.  
+  */
+  public
+  static 
+  Logger getLogger(final String name, final LoggerFactory factory) {
+     // Delegate the actual manufacturing of the logger to the logger repository.
+    return getLoggerRepository().getLogger(name, factory);
+  }  
+
+  public
+  static
+  Logger exists(final String name) {
+    return getLoggerRepository().exists(name);
+  }
+
+  public
+  static
+  Enumeration getCurrentLoggers() {
+    return getLoggerRepository().getCurrentLoggers();
+  }
+
+  public
+  static
+  void shutdown() {
+    getLoggerRepository().shutdown();
+  }
+
+  public
+  static
+  void resetConfiguration() {
+    getLoggerRepository().resetConfiguration();
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/LogSF.java b/srcjar/org/apache/log4j/LogSF.java
new file mode 100644 (file)
index 0000000..8302e20
--- /dev/null
@@ -0,0 +1,1541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.ResourceBundle;
+
+
+/**
+ * This class provides parameterized logging services
+ * using the SLF4J pattern syntax.
+ * <p>
+ * Message formatting is only performed when the 
+ * request exceeds the threshold level of the logger.
+ *
+ * @since 1.2.16
+ *
+ */
+public final class LogSF extends LogXF {
+    /**
+     * private constructor.
+     *
+     */
+    private LogSF() {
+    }
+
+
+
+
+    /**
+     * Formats arguments using SLF4J-like formatter.
+     * @param pattern pattern, may be malformed.
+     * @param arguments arguments.
+     * @return Message string
+     */
+    private static String format(final String pattern,
+                                 final Object[] arguments) {
+        if (pattern != null) {
+            String retval = "";
+            int count = 0;
+            int prev = 0;
+            int pos = pattern.indexOf("{");
+            while(pos >= 0) {
+                if (pos == 0 || pattern.charAt(pos-1) != '\\') {
+                    retval += pattern.substring(prev, pos);
+                    if (pos + 1 < pattern.length() && pattern.charAt(pos+1) == '}') {
+                        if(arguments != null && count < arguments.length) {
+                            retval += arguments[count++];
+                        } else {
+                            retval += "{}";
+                        }
+                        prev = pos + 2;
+                    } else {
+                        retval += "{";
+                        prev = pos + 1;
+                    }
+                } else {
+                    retval += pattern.substring(prev, pos - 1) + "{";
+                    prev = pos + 1;
+                }
+                pos = pattern.indexOf("{", prev);
+            }
+            return retval + pattern.substring(prev);
+        }
+        return null;
+    }
+
+    /**
+     * Formats arguments using MessageFormat.
+     * @param pattern pattern, may be malformed.
+     * @param arg0 argument, may be null or mismatched.
+     * @return Message string
+     */
+    private static String format(final String pattern, final Object arg0) {
+        if (pattern != null) {
+            //
+            //  if there is an escaped brace, delegate to multi-param formatter
+            if (pattern.indexOf("\\{") >= 0) {
+                return format(pattern, new Object[] { arg0 });
+            }
+            int pos = pattern.indexOf("{}");
+            if (pos >= 0) {
+                return pattern.substring(0, pos) + arg0 + pattern.substring(pos+2);
+            }
+        }
+        return pattern;
+    }
+
+    /**
+     * Formats arguments using MessageFormat using a pattern from
+     * a resource bundle.
+     * @param resourceBundleName name of resource bundle, may be null.
+     * @param key key for pattern in resource bundle, may be null.
+     * @param arguments arguments, may be null or mismatched.
+     * @return Message string or null
+     */
+    private static String format(
+            final String resourceBundleName,
+            final String key,
+            final Object[] arguments) {
+        String pattern;
+        if (resourceBundleName != null) {
+            try {
+                ResourceBundle bundle =
+                        ResourceBundle.getBundle(resourceBundleName);
+                pattern = bundle.getString(key);
+            } catch (Exception ex) {
+                pattern = key;
+            }
+        } else {
+            pattern = key;
+        }
+        return format(pattern, arguments);
+    }
+
+
+    /**
+     * Fully Qualified Class Name of this class.
+     */
+    private static final String FQCN = LogSF.class.getName();
+
+    /**
+     * Equivalent of Logger.forcedLog.
+     *
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param msg message, may be null.
+     */
+    private static void forcedLog(final Logger logger,
+                                  final Level level,
+                                  final String msg) {
+        logger.callAppenders(new LoggingEvent(FQCN, logger, level, msg, null));
+    }
+
+    /**
+     * Equivalent of Logger.forcedLog.
+     *
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param msg message, may be null.
+     * @param t throwable.
+     */
+    private static void forcedLog(final Logger logger,
+                                  final Level level,
+                                  final String msg,
+                                  final Throwable t) {
+        logger.callAppenders(new LoggingEvent(FQCN, logger, level, msg, t));
+    }
+    /**
+         * Log a parameterized message at trace level.
+         * @param logger logger, may not be null.
+         * @param pattern pattern, may be null.
+         * @param arguments an array of arguments to be
+         *          formatted and substituted.
+         */
+    public static void trace(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at error level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void error(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            forcedLog(logger, Level.ERROR, format(pattern, arguments));
+        }
+    }
+
+    /**
+     * Log a parameterized message at fatal level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void fatal(final Logger logger, final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.FATAL)) {
+            forcedLog(logger, Level.FATAL, format(pattern, arguments));
+        }
+    }
+
+    /**
+         * Log a parameterized message at trace level.
+         * @param logger logger, may not be null.
+         * @param t throwable, may be null.
+         * @param pattern pattern, may be null.
+         * @param arguments an array of arguments to be
+         *          formatted and substituted.
+         */
+    public static void trace(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void debug(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void info(final Logger logger,
+                            final Throwable t,
+                            final String pattern,
+        final Object[] arguments) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void warn(final Logger logger,
+                            final Throwable t,
+                            final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at error level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void error(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            forcedLog(logger, Level.ERROR, format(pattern, arguments), t);
+        }
+    }
+
+    /**
+     * Log a parameterized message at fatal level.
+     * @param logger logger, may not be null.
+     * @param t throwable, may be null.
+     * @param pattern pattern, may be null.
+     * @param arguments an array of arguments to be formatted and substituted.
+     */
+    public static void fatal(final Logger logger,
+                             final Throwable t,
+                             final String pattern,
+        final Object[] arguments) {
+        if (logger.isEnabledFor(Level.FATAL)) {
+            forcedLog(logger, Level.FATAL, format(pattern, arguments), t);
+        }
+    }
+
+
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at trace level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void trace(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(TRACE)) {
+            forcedLog(logger, TRACE,
+                    format(pattern, toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at debug level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void debug(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isDebugEnabled()) {
+            forcedLog(logger, Level.DEBUG,
+                    format(pattern, toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern,
+                    toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at info level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void info(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isInfoEnabled()) {
+            forcedLog(logger, Level.INFO, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final boolean argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final char argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final byte argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final short argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final int argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final long argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final float argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final double argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, valueOf(argument)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param argument a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object argument) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern, argument));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at warn level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void warn(final Logger logger, final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            forcedLog(logger, Level.WARN, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, parameters));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+     * @param t throwable, may be null.
+      * @param pattern pattern, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final Throwable t,
+                             final String pattern,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, parameters), t);
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(param1)));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final boolean param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final byte param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final char param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final short param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final int param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final long param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final float param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message at specified level.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param pattern pattern, may be null.
+     * @param param1 parameter to the log message.
+      */
+    public static void log(final Logger logger,
+                             final Level level,
+                             final String pattern,
+                             final double param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+     * Log a parameterized message at specified level.
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                            final Level level,
+                            final String pattern,
+        final Object arg0, final Object arg1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(arg0, arg1)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at specifed level.
+     * @param logger logger, may not be null.
+     * @param level level, may not be null.
+     * @param pattern pattern, may be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                           final Level level,
+                           final String pattern,
+        final Object arg0, final Object arg1, final Object arg2) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(pattern, toArray(arg0, arg1, arg2)));
+        }
+    }
+
+    /**
+     * Log a parameterized message at specified level.
+     * @param logger logger, may not be null.
+     * @param pattern pattern, may be null.
+     * @param level level, may not be null.
+     * @param arg0 a value to be formatted and substituted.
+     * @param arg1 a value to be formatted and substituted.
+     * @param arg2 a value to be formatted and substituted.
+     * @param arg3 a value to be formatted and substituted.
+     */
+    public static void log(final Logger logger,
+                           final Level level,
+                           final String pattern,
+        final Object arg0, final Object arg1, final Object arg2,
+        final Object arg3) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level, format(pattern,
+                    toArray(arg0, arg1, arg2, arg3)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, parameters));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+     * @param t throwable, may be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param parameters parameters to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final Throwable t,
+                             final String bundleName,
+                             final String key,
+                             final Object[] parameters) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, parameters), t);
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param1)));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final boolean param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final char param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final byte param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final short param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final int param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final long param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final float param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final double param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(valueOf(param1))));
+        }
+    }
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param0, param1)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+     * @param param2 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1,
+                             final Object param2) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key, toArray(param0, param1, param2)));
+        }
+    }
+
+
+    /**
+      * Log a parameterized message using a pattern from a resource bundle.
+      * @param logger logger, may not be null.
+      * @param level level, may not be null.
+      * @param bundleName resource bundle name, may be null.
+     * @param key key, may be null.
+     * @param param0 Parameter to the log message.
+     * @param param1 Parameter to the log message.
+     * @param param2 Parameter to the log message.
+     * @param param3 Parameter to the log message.
+      */
+    public static void logrb(final Logger logger,
+                             final Level level,
+                             final String bundleName,
+                             final String key,
+                             final Object param0,
+                             final Object param1,
+                             final Object param2,
+                             final Object param3) {
+        if (logger.isEnabledFor(level)) {
+            forcedLog(logger, level,
+                    format(bundleName, key,
+                            toArray(param0, param1, param2, param3)));
+        }
+    }
+}
diff --git a/srcjar/org/apache/log4j/LogXF.java b/srcjar/org/apache/log4j/LogXF.java
new file mode 100644 (file)
index 0000000..de2b94b
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This is a base class for LogMF and LogSF parameterized logging classes.
+ *
+ *
+ * @see org.apache.log4j.LogMF
+ * @see org.apache.log4j.LogSF
+ * @since 1.2.16
+ */
+public abstract class LogXF {
+    /**
+     * Trace level.
+     */
+    protected static final Level TRACE = new Level(5000, "TRACE", 7);
+    /**
+     * Fully Qualified Class Name of this class.
+     */
+    private static final String FQCN = LogXF.class.getName();
+
+    protected LogXF() {
+    }
+
+    /**
+     * Returns a Boolean instance representing the specified boolean.
+     * Boolean.valueOf was added in JDK 1.4.
+     *
+     * @param b a boolean value.
+     * @return a Boolean instance representing b.
+     */
+    protected static Boolean valueOf(final boolean b) {
+        if (b) {
+            return Boolean.TRUE;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * Returns a Character instance representing the specified char.
+     * Character.valueOf was added in JDK 1.5.
+     *
+     * @param c a character value.
+     * @return a Character instance representing c.
+     */
+    protected static Character valueOf(final char c) {
+        return new Character(c);
+    }
+
+    /**
+     * Returns a Byte instance representing the specified byte.
+     * Byte.valueOf was added in JDK 1.5.
+     *
+     * @param b a byte value.
+     * @return a Byte instance representing b.
+     */
+    protected static Byte valueOf(final byte b) {
+        return new Byte(b);
+    }
+
+    /**
+     * Returns a Short instance representing the specified short.
+     * Short.valueOf was added in JDK 1.5.
+     *
+     * @param b a short value.
+     * @return a Byte instance representing b.
+     */
+    protected static Short valueOf(final short b) {
+        return new Short(b);
+    }
+
+    /**
+     * Returns an Integer instance representing the specified int.
+     * Integer.valueOf was added in JDK 1.5.
+     *
+     * @param b an int value.
+     * @return an Integer instance representing b.
+     */
+    protected static Integer valueOf(final int b) {
+        return new Integer(b);
+    }
+
+    /**
+     * Returns a Long instance representing the specified long.
+     * Long.valueOf was added in JDK 1.5.
+     *
+     * @param b a long value.
+     * @return a Long instance representing b.
+     */
+    protected static Long valueOf(final long b) {
+        return new Long(b);
+    }
+
+    /**
+     * Returns a Float instance representing the specified float.
+     * Float.valueOf was added in JDK 1.5.
+     *
+     * @param b a float value.
+     * @return a Float instance representing b.
+     */
+    protected static Float valueOf(final float b) {
+        return new Float(b);
+    }
+
+    /**
+     * Returns a Double instance representing the specified double.
+     * Double.valueOf was added in JDK 1.5.
+     *
+     * @param b a double value.
+     * @return a Byte instance representing b.
+     */
+    protected static Double valueOf(final double b) {
+        return new Double(b);
+    }
+
+    /**
+     * Create new array.
+     *
+     * @param param1 parameter 1.
+     * @return new array.
+     */
+    protected static Object[] toArray(final Object param1) {
+        return new Object[]{
+                param1
+        };
+    }
+
+    /**
+     * Create new array.
+     *
+     * @param param1 parameter 1.
+     * @param param2 parameter 2.
+     * @return new array.
+     */
+    protected static Object[] toArray(final Object param1,
+                                      final Object param2) {
+        return new Object[]{
+                param1, param2
+        };
+    }
+
+    /**
+     * Create new array.
+     *
+     * @param param1 parameter 1.
+     * @param param2 parameter 2.
+     * @param param3 parameter 3.
+     * @return new array.
+     */
+    protected static Object[] toArray(final Object param1,
+                                      final Object param2,
+                                      final Object param3) {
+        return new Object[]{
+                param1, param2, param3
+        };
+    }
+
+    /**
+     * Create new array.
+     *
+     * @param param1 parameter 1.
+     * @param param2 parameter 2.
+     * @param param3 parameter 3.
+     * @param param4 parameter 4.
+     * @return new array.
+     */
+    protected static Object[] toArray(final Object param1,
+                                      final Object param2,
+                                      final Object param3,
+                                      final Object param4) {
+        return new Object[]{
+                param1, param2, param3, param4
+        };
+    }
+
+    /**
+     * Log an entering message at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     */
+    public static void entering(final Logger logger,
+                                final String sourceClass,
+                                final String sourceMethod) {
+        if (logger.isDebugEnabled()) {
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    sourceClass + "." + sourceMethod + " ENTRY", null));
+        }
+    }
+
+    /**
+     * Log an entering message with a parameter at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param param        parameter, may be null.
+     */
+    public static void entering(final Logger logger,
+                                final String sourceClass,
+                                final String sourceMethod,
+                                final String param) {
+        if (logger.isDebugEnabled()) {
+            String msg = sourceClass + "." + sourceMethod + " ENTRY " + param;
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    msg, null));
+        }
+    }
+
+    /**
+     * Log an entering message with a parameter at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param param        parameter, may be null.
+     */
+    public static void entering(final Logger logger,
+                                final String sourceClass,
+                                final String sourceMethod,
+                                final Object param) {
+        if (logger.isDebugEnabled()) {
+            String msg = sourceClass + "." + sourceMethod + " ENTRY ";
+            if (param == null) {
+                msg += "null";
+            } else {
+                try {
+                    msg += param;
+                } catch(Throwable ex) {
+                    msg += "?";
+                }
+            }
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    msg, null));
+        }
+    }
+
+    /**
+     * Log an entering message with an array of parameters at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param params       parameters, may be null.
+     */
+    public static void entering(final Logger logger,
+                                final String sourceClass,
+                                final String sourceMethod,
+                                final Object[] params) {
+        if (logger.isDebugEnabled()) {
+            String msg = sourceClass + "." + sourceMethod + " ENTRY ";
+            if (params != null && params.length > 0) {
+                String delim = "{";
+                for (int i = 0; i < params.length; i++) {
+                    try {
+                        msg += delim + params[i];
+                    } catch(Throwable ex) {
+                        msg += delim + "?";
+                    }
+                    delim = ",";
+                }
+                msg += "}";
+            } else {
+                msg += "{}";
+            }
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    msg, null));
+        }
+    }
+
+    /**
+     * Log an exiting message at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     */
+    public static void exiting(final Logger logger,
+                               final String sourceClass,
+                               final String sourceMethod) {
+        if (logger.isDebugEnabled()) {
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    sourceClass + "." + sourceMethod + " RETURN", null));
+        }
+    }
+
+    /**
+     * Log an exiting message with result at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param result       result, may be null.
+     */
+    public static void exiting(
+            final Logger logger,
+            final String sourceClass,
+            final String sourceMethod,
+            final String result) {
+        if (logger.isDebugEnabled()) {
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    sourceClass + "." + sourceMethod + " RETURN " + result, null));
+        }
+    }
+
+    /**
+     * Log an exiting message with result at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param result       result, may be null.
+     */
+    public static void exiting(
+            final Logger logger,
+            final String sourceClass,
+            final String sourceMethod,
+            final Object result) {
+        if (logger.isDebugEnabled()) {
+            String msg = sourceClass + "." + sourceMethod + " RETURN ";
+            if (result == null) {
+                msg += "null";
+            } else {
+                try {
+                    msg += result;
+                } catch(Throwable ex) {
+                    msg += "?";
+                }
+            }
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    msg, null));
+        }
+    }
+
+    /**
+     * Logs a throwing message at DEBUG level.
+     *
+     * @param logger       logger, may not be null.
+     * @param sourceClass  source class, may be null.
+     * @param sourceMethod method, may be null.
+     * @param thrown      throwable, may be null.
+     */
+    public static void throwing(
+            final Logger logger,
+            final String sourceClass,
+            final String sourceMethod,
+            final Throwable thrown) {
+        if (logger.isDebugEnabled()) {
+            logger.callAppenders(new LoggingEvent(FQCN, logger, Level.DEBUG,
+                    sourceClass + "." + sourceMethod + " THROW", thrown));
+        }
+    }
+}
diff --git a/srcjar/org/apache/log4j/Logger.java b/srcjar/org/apache/log4j/Logger.java
new file mode 100644 (file)
index 0000000..cbd9e86
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggerFactory;
+
+
+/**
+  This is the central class in the log4j package. Most logging
+  operations, except configuration, are done through this class.
+
+  @since log4j 1.2
+
+  @author Ceki G&uuml;lc&uuml; */
+public class Logger extends Category {
+
+  /**
+     The fully qualified name of the Logger class. See also the
+     getFQCN method. */
+  private static final String FQCN = Logger.class.getName();
+
+
+  protected
+  Logger(String name) {
+    super(name);
+  }
+
+  /**
+   * Retrieve a logger named according to the value of the
+   * <code>name</code> parameter. If the named logger already exists,
+   * then the existing instance will be returned. Otherwise, a new
+   * instance is created.  
+   *
+   * <p>By default, loggers do not have a set level but inherit it
+   * from their neareast ancestor with a set level. This is one of the
+   * central features of log4j.
+   *
+   * @param name The name of the logger to retrieve.  
+  */
+  static
+  public
+  Logger getLogger(String name) {
+    return LogManager.getLogger(name);
+  }
+
+  /**
+   * Shorthand for <code>getLogger(clazz.getName())</code>.
+   *
+   * @param clazz The name of <code>clazz</code> will be used as the
+   * name of the logger to retrieve.  See {@link #getLogger(String)}
+   * for more detailed information.
+   */
+  static
+  public
+  Logger getLogger(Class clazz) {
+    return LogManager.getLogger(clazz.getName());
+  }
+
+
+  /**
+   * Return the root logger for the current logger repository.
+   * <p>
+   * The {@link #getName Logger.getName()} method for the root logger always returns
+   * string value: "root". However, calling
+   * <code>Logger.getLogger("root")</code> does not retrieve the root
+   * logger but a logger just under root named "root".
+   * <p>
+   * In other words, calling this method is the only way to retrieve the 
+   * root logger.
+   */
+  public
+  static
+  Logger getRootLogger() {
+    return LogManager.getRootLogger();
+  }
+
+  /**
+     Like {@link #getLogger(String)} except that the type of logger
+     instantiated depends on the type returned by the {@link
+     LoggerFactory#makeNewLoggerInstance} method of the
+     <code>factory</code> parameter.
+
+     <p>This method is intended to be used by sub-classes.
+
+     @param name The name of the logger to retrieve.
+
+     @param factory A {@link LoggerFactory} implementation that will
+     actually create a new Instance.
+
+     @since 0.8.5 */
+  public
+  static
+  Logger getLogger(String name, LoggerFactory factory) {
+    return LogManager.getLogger(name, factory);
+  }
+
+    /**
+     * Log a message object with the {@link org.apache.log4j.Level#TRACE TRACE} level.
+     *
+     * @param message the message object to log.
+     * @see #debug(Object) for an explanation of the logic applied.
+     * @since 1.2.12
+     */
+    public void trace(Object message) {
+      if (repository.isDisabled(Level.TRACE_INT)) {
+        return;
+      }
+
+      if (Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.TRACE, message, null);
+      }
+    }
+
+    /**
+     * Log a message object with the <code>TRACE</code> level including the
+     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+     *
+     * <p>
+     * See {@link #debug(Object)} form for more detailed information.
+     * </p>
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     * @since 1.2.12
+     */
+    public void trace(Object message, Throwable t) {
+      if (repository.isDisabled(Level.TRACE_INT)) {
+        return;
+      }
+
+      if (Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel())) {
+        forcedLog(FQCN, Level.TRACE, message, t);
+      }
+    }
+
+    /**
+     * Check whether this category is enabled for the TRACE  Level.
+     * @since 1.2.12
+     *
+     * @return boolean - <code>true</code> if this category is enabled for level
+     *         TRACE, <code>false</code> otherwise.
+     */
+    public boolean isTraceEnabled() {
+        if (repository.isDisabled(Level.TRACE_INT)) {
+            return false;
+          }
+
+          return Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel());
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/MDC.java b/srcjar/org/apache/log4j/MDC.java
new file mode 100644 (file)
index 0000000..c07efed
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.ThreadLocalMap;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+
+/**
+ * The MDC class is similar to the {@link NDC} class except that it is
+ * based on a map instead of a stack. It provides <em>mapped
+ * diagnostic contexts</em>. A <em>Mapped Diagnostic Context</em>, or
+ * MDC in short, is an instrument for distinguishing interleaved log
+ * output from different sources. Log output is typically interleaved
+ * when a server handles multiple clients near-simultaneously.
+ * <p/>
+ * <p><b><em>The MDC is managed on a per thread basis</em></b>. A
+ * child thread automatically inherits a <em>copy</em> of the mapped
+ * diagnostic context of its parent.
+ * <p/>
+ * <p>The MDC class requires JDK 1.2 or above. Under JDK 1.1 the MDC
+ * will always return empty values but otherwise will not affect or
+ * harm your application.</p>
+ *
+ * <p>Attention: the application is required to clean up. In web applications
+ * this can happen with creating a Servlet Filter and overriding the
+ * onFilter method like:</p>
+ *
+ * <pre>
+ * try {
+ *    MDC.put(myKey);
+ *    chain.doFilter(request, response);
+ * } finally {
+ *    MDC.remove(myKey);
+ * }
+ * </pre>
+ *
+ * <p>Please also see: {@link http://logging.apache.org/log4j/1.2/faq.html#mdcmemoryleak}</p>
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @since 1.2
+ */
+public class MDC {
+
+    final static MDC mdc = new MDC();
+
+    static final int HT_SIZE = 7;
+
+    boolean java1;
+
+    Object tlm;
+
+    private Method removeMethod;
+
+    private MDC() {
+        java1 = Loader.isJava1();
+        if (!java1) {
+            tlm = new ThreadLocalMap();
+        }
+
+        try {
+            removeMethod = ThreadLocal.class.getMethod("remove", null);
+        } catch (NoSuchMethodException e) {
+            // don't do anything - java prior 1.5
+        }
+    }
+
+    /**
+     * Put a context value (the <code>o</code> parameter) as identified
+     * with the <code>key</code> parameter into the current thread's
+     * context map.
+     * <p/>
+     * <p>If the current thread does not have a context map it is
+     * created as a side effect.
+     */
+    public static void put(String key, Object o) {
+        if (mdc != null) {
+            mdc.put0(key, o);
+        }
+    }
+
+    /**
+     * Get the context identified by the <code>key</code> parameter.
+     * <p/>
+     * <p>This method has no side effects.
+     */
+    public static Object get(String key) {
+        if (mdc != null) {
+            return mdc.get0(key);
+        }
+        return null;
+    }
+
+    /**
+     * Remove the the context identified by the <code>key</code>
+     * parameter.
+     */
+    public static void remove(String key) {
+        if (mdc != null) {
+            mdc.remove0(key);
+        }
+    }
+
+
+    /**
+     * Get the current thread's MDC as a hashtable. This method is
+     * intended to be used internally.
+     */
+    public static Hashtable getContext() {
+        if (mdc != null) {
+            return mdc.getContext0();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Remove all values from the MDC.
+     *
+     * @since 1.2.16
+     */
+    public static void clear() {
+        if (mdc != null) {
+            mdc.clear0();
+        }
+    }
+
+
+    private void put0(String key, Object o) {
+        if (java1 || tlm == null) {
+            return;
+        } else {
+            Hashtable ht = (Hashtable) ((ThreadLocalMap) tlm).get();
+            if (ht == null) {
+                ht = new Hashtable(HT_SIZE);
+                ((ThreadLocalMap) tlm).set(ht);
+            }
+            ht.put(key, o);
+        }
+    }
+
+    private Object get0(String key) {
+        if (java1 || tlm == null) {
+            return null;
+        } else {
+            Hashtable ht = (Hashtable) ((ThreadLocalMap) tlm).get();
+            if (ht != null && key != null) {
+                return ht.get(key);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private void remove0(String key) {
+        if (!java1 && tlm != null) {
+            Hashtable ht = (Hashtable) ((ThreadLocalMap) tlm).get();
+            if (ht != null) {
+                ht.remove(key);
+                // clean up if this was the last key
+                if (ht.isEmpty()) {
+                        clear0();
+                }
+            }
+        }
+    }
+
+    private Hashtable getContext0() {
+        if (java1 || tlm == null) {
+            return null;
+        } else {
+            return (Hashtable) ((ThreadLocalMap) tlm).get();
+        }
+    }
+
+    private void clear0() {
+        if (!java1 && tlm != null) {
+            Hashtable ht = (Hashtable) ((ThreadLocalMap) tlm).get();
+            if (ht != null) {
+                ht.clear();
+            }
+            if (removeMethod != null) {
+                // java 1.3/1.4 does not have remove - will suffer from a memory leak
+                try {
+                    removeMethod.invoke(tlm, null);
+                } catch (IllegalAccessException e) {
+                    // should not happen
+                } catch (InvocationTargetException e) {
+                    // should not happen
+                }
+            }
+        }
+    }
+}
diff --git a/srcjar/org/apache/log4j/NDC.java b/srcjar/org/apache/log4j/NDC.java
new file mode 100644 (file)
index 0000000..3b25e99
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//      Contributors:      Dan Milstein 
+//                         Ray Millard
+
+package org.apache.log4j;
+
+import java.util.Hashtable;
+import java.util.Stack;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.apache.log4j.helpers.LogLog;
+
+/**
+   The NDC class implements <i>nested diagnostic contexts</i> as
+   defined by Neil Harrison in the article "Patterns for Logging
+   Diagnostic Messages" part of the book "<i>Pattern Languages of
+   Program Design 3</i>" edited by Martin et al.
+
+   <p>A Nested Diagnostic Context, or NDC in short, is an instrument
+   to distinguish interleaved log output from different sources. Log
+   output is typically interleaved when a server handles multiple
+   clients near-simultaneously.
+
+   <p>Interleaved log output can still be meaningful if each log entry
+   from different contexts had a distinctive stamp. This is where NDCs
+   come into play.
+
+   <p><em><b>Note that NDCs are managed on a per thread
+   basis</b></em>. NDC operations such as {@link #push push}, {@link
+   #pop}, {@link #clear}, {@link #getDepth} and {@link #setMaxDepth}
+   affect the NDC of the <em>current</em> thread only. NDCs of other
+   threads remain unaffected.
+
+   <p>For example, a servlet can build a per client request NDC
+   consisting the clients host name and other information contained in
+   the the request. <em>Cookies</em> are another source of distinctive
+   information. To build an NDC one uses the {@link #push push}
+   operation. Simply put,
+
+   <p><ul>
+     <li>Contexts can be nested.
+
+     <p><li>When entering a context, call <code>NDC.push</code>. As a
+     side effect, if there is no nested diagnostic context for the
+     current thread, this method will create it.
+
+     <p><li>When leaving a context, call <code>NDC.pop</code>.
+
+     <p><li><b>When exiting a thread make sure to call {@link #remove
+     NDC.remove()}</b>.  
+   </ul>
+   
+   <p>There is no penalty for forgetting to match each
+   <code>push</code> operation with a corresponding <code>pop</code>,
+   except the obvious mismatch between the real application context
+   and the context set in the NDC.
+
+   <p>If configured to do so, {@link PatternLayout} and {@link
+   TTCCLayout} instances automatically retrieve the nested diagnostic
+   context for the current thread without any user intervention.
+   Hence, even if a servlet is serving multiple clients
+   simultaneously, the logs emanating from the same code (belonging to
+   the same category) can still be distinguished because each client
+   request will have a different NDC tag.
+
+   <p>Heavy duty systems should call the {@link #remove} method when
+   leaving the run method of a thread. This ensures that the memory
+   used by the thread can be freed by the Java garbage
+   collector. There is a mechanism to lazily remove references to dead
+   threads. In practice, this means that you can be a little sloppy
+   and sometimes forget to call {@link #remove} before exiting a
+   thread.
+   
+   <p>A thread may inherit the nested diagnostic context of another
+   (possibly parent) thread using the {@link #inherit inherit}
+   method. A thread may obtain a copy of its NDC with the {@link
+   #cloneStack cloneStack} method and pass the reference to any other
+   thread, in particular to a child.
+   
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.7.0
+  
+*/
+public class NDC {
+
+  // The synchronized keyword is not used in this class. This may seem
+  // dangerous, especially since the class will be used by
+  // multiple-threads. In particular, all threads share the same
+  // hashtable (the "ht" variable). This is OK since java hashtables
+  // are thread safe. Same goes for Stacks.
+
+  // More importantly, when inheriting diagnostic contexts the child
+  // thread is handed a clone of the parent's NDC.  It follows that
+  // each thread has its own NDC (i.e. stack).
+
+  static Hashtable ht = new Hashtable();
+
+  static int pushCounter = 0; // the number of times push has been called
+                              // after the latest call to lazyRemove
+
+  // The number of times we allow push to be called before we call lazyRemove
+  // 5 is a relatively small number. As such, lazyRemove is not called too
+  // frequently. We thus avoid the cost of creating an Enumeration too often.
+  // The higher this number, the longer is the avarage period for which all
+  // logging calls in all threads are blocked.
+  static final int REAP_THRESHOLD = 5;
+  
+  // No instances allowed.
+  private NDC() {}
+  
+  /**
+   *   Get NDC stack for current thread.
+   *   @return NDC stack for current thread.
+   */
+  private static Stack getCurrentStack() {
+      if (ht != null) {
+          return (Stack) ht.get(Thread.currentThread());
+      }
+      return null;
+  }
+
+
+  /**
+     Clear any nested diagnostic information if any. This method is
+     useful in cases where the same thread can be potentially used
+     over and over in different unrelated contexts.
+
+     <p>This method is equivalent to calling the {@link #setMaxDepth}
+     method with a zero <code>maxDepth</code> argument.
+     
+     @since 0.8.4c */
+  public
+  static
+  void clear() {
+    Stack stack = getCurrentStack();    
+    if(stack != null) {
+        stack.setSize(0);
+    }    
+  }
+
+  
+  /**
+     Clone the diagnostic context for the current thread.
+
+     <p>Internally a diagnostic context is represented as a stack.  A
+     given thread can supply the stack (i.e. diagnostic context) to a
+     child thread so that the child can inherit the parent thread's
+     diagnostic context.
+
+     <p>The child thread uses the {@link #inherit inherit} method to
+     inherit the parent's diagnostic context.
+     
+     @return Stack A clone of the current thread's  diagnostic context.
+
+  */
+  public
+  static
+  Stack cloneStack() {
+    Stack stack = getCurrentStack();
+    if(stack == null) {
+        return null;
+    } else {
+      return (Stack) stack.clone();
+    }
+  }
+
+  
+  /**
+     Inherit the diagnostic context of another thread.
+
+     <p>The parent thread can obtain a reference to its diagnostic
+     context using the {@link #cloneStack} method.  It should
+     communicate this information to its child so that it may inherit
+     the parent's diagnostic context.
+
+     <p>The parent's diagnostic context is cloned before being
+     inherited. In other words, once inherited, the two diagnostic
+     contexts can be managed independently.
+     
+     <p>In java, a child thread cannot obtain a reference to its
+     parent, unless it is directly handed the reference. Consequently,
+     there is no client-transparent way of inheriting diagnostic
+     contexts. Do you know any solution to this problem?
+
+     @param stack The diagnostic context of the parent thread.
+
+  */
+  public
+  static
+  void inherit(Stack stack) {
+    if(stack != null) {
+        ht.put(Thread.currentThread(), stack);
+    }
+  }
+
+
+  /**
+     <font color="#FF4040"><b>Never use this method directly, use the {@link
+     org.apache.log4j.spi.LoggingEvent#getNDC} method instead</b></font>.
+  */
+  static
+  public
+  String get() {
+    Stack s = getCurrentStack();
+    if(s != null && !s.isEmpty()) {
+        return ((DiagnosticContext) s.peek()).fullMessage;
+    } else {
+        return null;
+    }
+  }
+  
+  /**
+   * Get the current nesting depth of this diagnostic context.
+   *
+   * @see #setMaxDepth
+   * @since 0.7.5
+   */
+  public
+  static
+  int getDepth() {
+    Stack stack = getCurrentStack();          
+    if(stack == null) {
+        return 0;
+    } else {
+        return stack.size();
+    }      
+  }
+
+  private
+  static
+  void lazyRemove() {
+    if (ht == null) {
+        return;
+    }
+     
+    // The synchronization on ht is necessary to prevent JDK 1.2.x from
+    // throwing ConcurrentModificationExceptions at us. This sucks BIG-TIME.
+    // One solution is to write our own hashtable implementation.
+    Vector v;
+    
+    synchronized(ht) {
+      // Avoid calling clean-up too often.
+      if(++pushCounter <= REAP_THRESHOLD) {
+       return; // We release the lock ASAP.
+      } else {
+       pushCounter = 0; // OK let's do some work.
+      }
+
+      int misses = 0;
+      v = new Vector(); 
+      Enumeration enumeration = ht.keys();
+      // We give up after 4 straigt missses. That is 4 consecutive
+      // inspected threads in 'ht' that turn out to be alive.
+      // The higher the proportion on dead threads in ht, the higher the
+      // chances of removal.
+      while(enumeration.hasMoreElements() && (misses <= 4)) {
+       Thread t = (Thread) enumeration.nextElement();
+       if(t.isAlive()) {
+         misses++;
+       } else {
+         misses = 0;
+         v.addElement(t);
+       }
+      }
+    } // synchronized
+
+    int size = v.size();
+    for(int i = 0; i < size; i++) {
+      Thread t = (Thread) v.elementAt(i);
+      LogLog.debug("Lazy NDC removal for thread [" + t.getName() + "] ("+ 
+                  ht.size() + ").");
+      ht.remove(t);
+    }
+  }
+
+  /**
+     Clients should call this method before leaving a diagnostic
+     context.
+
+     <p>The returned value is the value that was pushed last. If no
+     context is available, then the empty string "" is returned.
+     
+     @return String The innermost diagnostic context.
+     
+     */
+  public
+  static
+  String pop() {
+    Stack stack = getCurrentStack();
+    if(stack != null && !stack.isEmpty()) {
+        return ((DiagnosticContext) stack.pop()).message;
+    } else {
+        return "";
+    }
+  }
+
+  /**
+     Looks at the last diagnostic context at the top of this NDC
+     without removing it.
+
+     <p>The returned value is the value that was pushed last. If no
+     context is available, then the empty string "" is returned.
+     
+     @return String The innermost diagnostic context.
+     
+     */
+  public
+  static
+  String peek() {
+    Stack stack = getCurrentStack();
+    if(stack != null && !stack.isEmpty()) {
+        return ((DiagnosticContext) stack.peek()).message;
+    } else {
+        return "";
+    }
+  }
+  
+  /**
+     Push new diagnostic context information for the current thread.
+
+     <p>The contents of the <code>message</code> parameter is
+     determined solely by the client.  
+     
+     @param message The new diagnostic context information.  */
+  public
+  static
+  void push(String message) {
+    Stack stack = getCurrentStack();
+      
+    if(stack == null) {
+      DiagnosticContext dc = new DiagnosticContext(message, null);      
+      stack = new Stack();
+      Thread key = Thread.currentThread();
+      ht.put(key, stack);
+      stack.push(dc);
+    } else if (stack.isEmpty()) {
+      DiagnosticContext dc = new DiagnosticContext(message, null);            
+      stack.push(dc);
+    } else {
+      DiagnosticContext parent = (DiagnosticContext) stack.peek();
+      stack.push(new DiagnosticContext(message, parent));
+    }    
+  }
+
+  /**
+     Remove the diagnostic context for this thread.
+
+     <p>Each thread that created a diagnostic context by calling
+     {@link #push} should call this method before exiting. Otherwise,
+     the memory used by the <b>thread</b> cannot be reclaimed by the
+     VM.
+
+     <p>As this is such an important problem in heavy duty systems and
+     because it is difficult to always guarantee that the remove
+     method is called before exiting a thread, this method has been
+     augmented to lazily remove references to dead threads. In
+     practice, this means that you can be a little sloppy and
+     occasionally forget to call {@link #remove} before exiting a
+     thread. However, you must call <code>remove</code> sometime. If
+     you never call it, then your application is sure to run out of
+     memory.
+     
+  */
+  static
+  public
+  void remove() {
+    if (ht != null) {
+        ht.remove(Thread.currentThread());
+    
+        // Lazily remove dead-thread references in ht.
+        lazyRemove();
+    }
+  }
+
+  /**
+     Set maximum depth of this diagnostic context. If the current
+     depth is smaller or equal to <code>maxDepth</code>, then no
+     action is taken.
+
+     <p>This method is a convenient alternative to multiple {@link
+     #pop} calls. Moreover, it is often the case that at the end of
+     complex call sequences, the depth of the NDC is
+     unpredictable. The <code>setMaxDepth</code> method circumvents
+     this problem.
+
+     <p>For example, the combination
+     <pre>
+       void foo() {
+       &nbsp;  int depth = NDC.getDepth();
+
+       &nbsp;  ... complex sequence of calls
+
+       &nbsp;  NDC.setMaxDepth(depth);
+       }
+     </pre>
+
+     ensures that between the entry and exit of foo the depth of the
+     diagnostic stack is conserved.
+     
+     @see #getDepth
+     @since 0.7.5 */
+  static
+  public
+  void setMaxDepth(int maxDepth) {
+    Stack stack = getCurrentStack();    
+    if(stack != null && maxDepth < stack.size()) {
+        stack.setSize(maxDepth);
+    }
+  }
+  
+  // =====================================================================
+   private static class DiagnosticContext {
+
+    String fullMessage;
+    String message;
+    
+    DiagnosticContext(String message, DiagnosticContext parent) {
+      this.message = message;
+      if(parent != null) {
+       fullMessage = parent.fullMessage + ' ' + message;
+      } else {
+       fullMessage = message;
+      }
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/PatternLayout.java b/srcjar/org/apache/log4j/PatternLayout.java
new file mode 100644 (file)
index 0000000..d668a75
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.PatternParser;
+import org.apache.log4j.helpers.PatternConverter;
+
+
+// Contributors:   Nelson Minar <nelson@monkey.org>
+//                 Anders Kristensen <akristensen@dynamicsoft.com>
+
+/**
+
+   A flexible layout configurable with pattern string.
+   
+   This code is known to have synchronization and other issues
+   which are not present in org.apache.log4j.EnhancedPatternLayout.
+   EnhancedPatternLayout should be used in preference to PatternLayout.
+   EnhancedPatternLayout is distributed in the log4j extras companion.
+
+   <p>The goal of this class is to {@link #format format} a {@link
+   LoggingEvent} and return the results as a String. The results
+   depend on the <em>conversion pattern</em>.
+
+   <p>The conversion pattern is closely related to the conversion
+   pattern of the printf function in C. A conversion pattern is
+   composed of literal text and format control expressions called
+   <em>conversion specifiers</em>.
+
+   <p><i>You are free to insert any literal text within the conversion
+   pattern.</i>
+
+   <p>Each conversion specifier starts with a percent sign (%) and is
+   followed by optional <em>format modifiers</em> and a <em>conversion
+   character</em>. The conversion character specifies the type of
+   data, e.g. category, priority, date, thread name. The format
+   modifiers control such things as field width, padding, left and
+   right justification. The following is a simple example.
+
+   <p>Let the conversion pattern be <b>"%-5p [%t]: %m%n"</b> and assume
+   that the log4j environment was set to use a PatternLayout. Then the
+   statements
+   <pre>
+   Category root = Category.getRoot();
+   root.debug("Message 1");
+   root.warn("Message 2");
+   </pre>
+   would yield the output
+   <pre>
+   DEBUG [main]: Message 1
+   WARN  [main]: Message 2
+   </pre>
+
+   <p>Note that there is no explicit separator between text and
+   conversion specifiers. The pattern parser knows when it has reached
+   the end of a conversion specifier when it reads a conversion
+   character. In the example above the conversion specifier
+   <b>%-5p</b> means the priority of the logging event should be left
+   justified to a width of five characters.
+
+   The recognized conversion characters are
+
+   <p>
+   <table border="1" CELLPADDING="8">
+   <th>Conversion Character</th>
+   <th>Effect</th>
+
+   <tr>
+     <td align=center><b>c</b></td>
+
+     <td>Used to output the category of the logging event. The
+     category conversion specifier can be optionally followed by
+     <em>precision specifier</em>, that is a decimal constant in
+     brackets.
+
+     <p>If a precision specifier is given, then only the corresponding
+     number of right most components of the category name will be
+     printed. By default the category name is printed in full.
+
+     <p>For example, for the category name "a.b.c" the pattern
+     <b>%c{2}</b> will output "b.c".
+
+     </td>
+   </tr>
+
+   <tr>
+     <td align=center><b>C</b></td>
+
+     <td>Used to output the fully qualified class name of the caller
+     issuing the logging request. This conversion specifier
+     can be optionally followed by <em>precision specifier</em>, that
+     is a decimal constant in brackets.
+
+     <p>If a precision specifier is given, then only the corresponding
+     number of right most components of the class name will be
+     printed. By default the class name is output in fully qualified form.
+
+     <p>For example, for the class name "org.apache.xyz.SomeClass", the
+     pattern <b>%C{1}</b> will output "SomeClass".
+
+     <p><b>WARNING</b> Generating the caller class information is
+     slow. Thus, use should be avoided unless execution speed is
+     not an issue.
+
+     </td>
+     </tr>
+
+   <tr> <td align=center><b>d</b></td> <td>Used to output the date of
+        the logging event. The date conversion specifier may be
+        followed by a <em>date format specifier</em> enclosed between
+        braces. For example, <b>%d{HH:mm:ss,SSS}</b> or
+        <b>%d{dd&nbsp;MMM&nbsp;yyyy&nbsp;HH:mm:ss,SSS}</b>.  If no
+        date format specifier is given then ISO8601 format is
+        assumed.
+
+        <p>The date format specifier admits the same syntax as the
+        time pattern string of the {@link
+        java.text.SimpleDateFormat}. Although part of the standard
+        JDK, the performance of <code>SimpleDateFormat</code> is
+        quite poor.
+
+        <p>For better results it is recommended to use the log4j date
+        formatters. These can be specified using one of the strings
+        "ABSOLUTE", "DATE" and "ISO8601" for specifying {@link
+        org.apache.log4j.helpers.AbsoluteTimeDateFormat
+        AbsoluteTimeDateFormat}, {@link
+        org.apache.log4j.helpers.DateTimeDateFormat DateTimeDateFormat}
+        and respectively {@link
+        org.apache.log4j.helpers.ISO8601DateFormat
+        ISO8601DateFormat}. For example, <b>%d{ISO8601}</b> or
+        <b>%d{ABSOLUTE}</b>.
+
+        <p>These dedicated date formatters perform significantly
+        better than {@link java.text.SimpleDateFormat}.
+     </td>
+   </tr>
+
+   <tr>
+   <td align=center><b>F</b></td>
+
+   <td>Used to output the file name where the logging request was
+   issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+   <tr>
+   <td align=center><b>l</b></td>
+
+     <td>Used to output location information of the caller which generated
+     the logging event.
+
+     <p>The location information depends on the JVM implementation but
+     usually consists of the fully qualified name of the calling
+     method followed by the callers source the file name and line
+     number between parentheses.
+
+     <p>The location information can be very useful. However, its
+     generation is <em>extremely</em> slow and should be avoided
+     unless execution speed is not an issue.
+
+     </td>
+   </tr>
+
+   <tr>
+   <td align=center><b>L</b></td>
+
+   <td>Used to output the line number from where the logging request
+   was issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+
+   <tr>
+     <td align=center><b>m</b></td>
+     <td>Used to output the application supplied message associated with
+     the logging event.</td>
+   </tr>
+
+   <tr>
+   <td align=center><b>M</b></td>
+
+   <td>Used to output the method name where the logging request was
+   issued.
+
+   <p><b>WARNING</b> Generating caller location information is
+   extremely slow and should be avoided unless execution speed
+   is not an issue.
+
+   </tr>
+
+   <tr>
+     <td align=center><b>n</b></td>
+
+     <td>Outputs the platform dependent line separator character or
+     characters.
+
+     <p>This conversion character offers practically the same
+     performance as using non-portable line separator strings such as
+     "\n", or "\r\n". Thus, it is the preferred way of specifying a
+     line separator.
+
+
+   </tr>
+
+   <tr>
+     <td align=center><b>p</b></td>
+     <td>Used to output the priority of the logging event.</td>
+   </tr>
+
+   <tr>
+
+     <td align=center><b>r</b></td>
+
+     <td>Used to output the number of milliseconds elapsed from the construction 
+     of the layout until the creation of the logging event.</td>
+   </tr>
+
+
+   <tr>
+     <td align=center><b>t</b></td>
+
+     <td>Used to output the name of the thread that generated the
+     logging event.</td>
+
+   </tr>
+
+   <tr>
+
+     <td align=center><b>x</b></td>
+
+     <td>Used to output the NDC (nested diagnostic context) associated
+     with the thread that generated the logging event.
+     </td>
+   </tr>
+
+
+   <tr>
+     <td align=center><b>X</b></td>
+
+     <td> 
+     
+     <p>Used to output the MDC (mapped diagnostic context) associated
+     with the thread that generated the logging event. The <b>X</b>
+     conversion character <em>must</em> be followed by the key for the
+     map placed between braces, as in <b>%X{clientNumber}</b> where
+     <code>clientNumber</code> is the key. The value in the MDC
+     corresponding to the key will be output.</p>
+     
+     <p>See {@link MDC} class for more details.
+     </p>
+     
+     </td>
+   </tr>
+
+   <tr>
+
+     <td align=center><b>%</b></td>
+
+     <td>The sequence %% outputs a single percent sign.
+     </td>
+   </tr>
+
+   </table>
+
+   <p>By default the relevant information is output as is. However,
+   with the aid of format modifiers it is possible to change the
+   minimum field width, the maximum field width and justification.
+
+   <p>The optional format modifier is placed between the percent sign
+   and the conversion character.
+
+   <p>The first optional format modifier is the <em>left justification
+   flag</em> which is just the minus (-) character. Then comes the
+   optional <em>minimum field width</em> modifier. This is a decimal
+   constant that represents the minimum number of characters to
+   output. If the data item requires fewer characters, it is padded on
+   either the left or the right until the minimum width is
+   reached. The default is to pad on the left (right justify) but you
+   can specify right padding with the left justification flag. The
+   padding character is space. If the data item is larger than the
+   minimum field width, the field is expanded to accommodate the
+   data. The value is never truncated.
+
+   <p>This behavior can be changed using the <em>maximum field
+   width</em> modifier which is designated by a period followed by a
+   decimal constant. If the data item is longer than the maximum
+   field, then the extra characters are removed from the
+   <em>beginning</em> of the data item and not from the end. For
+   example, it the maximum field width is eight and the data item is
+   ten characters long, then the first two characters of the data item
+   are dropped. This behavior deviates from the printf function in C
+   where truncation is done from the end.
+
+   <p>Below are various format modifier examples for the category
+   conversion specifier.
+
+   <p>
+   <TABLE BORDER=1 CELLPADDING=8>
+   <th>Format modifier
+   <th>left justify
+   <th>minimum width
+   <th>maximum width
+   <th>comment
+
+   <tr>
+   <td align=center>%20c</td>
+   <td align=center>false</td>
+   <td align=center>20</td>
+   <td align=center>none</td>
+
+   <td>Left pad with spaces if the category name is less than 20
+   characters long.
+
+   <tr> <td align=center>%-20c</td> <td align=center>true</td> <td
+   align=center>20</td> <td align=center>none</td> <td>Right pad with
+   spaces if the category name is less than 20 characters long.
+
+   <tr>
+   <td align=center>%.30c</td>
+   <td align=center>NA</td>
+   <td align=center>none</td>
+   <td align=center>30</td>
+
+   <td>Truncate from the beginning if the category name is longer than 30
+   characters.
+
+   <tr>
+   <td align=center>%20.30c</td>
+   <td align=center>false</td>
+   <td align=center>20</td>
+   <td align=center>30</td>
+
+   <td>Left pad with spaces if the category name is shorter than 20
+   characters. However, if category name is longer than 30 characters,
+   then truncate from the beginning.
+
+   <tr>
+   <td align=center>%-20.30c</td>
+   <td align=center>true</td>
+   <td align=center>20</td>
+   <td align=center>30</td>
+
+   <td>Right pad with spaces if the category name is shorter than 20
+   characters. However, if category name is longer than 30 characters,
+   then truncate from the beginning.
+
+   </table>
+
+   <p>Below are some examples of conversion patterns.
+
+   <dl>
+
+   <p><dt><b>%r [%t] %-5p %c %x - %m%n</b>
+   <p><dd>This is essentially the TTCC layout.
+
+   <p><dt><b>%-6r [%15.15t] %-5p %30.30c %x - %m%n</b>
+
+   <p><dd>Similar to the TTCC layout except that the relative time is
+   right padded if less than 6 digits, thread name is right padded if
+   less than 15 characters and truncated if longer and the category
+   name is left padded if shorter than 30 characters and truncated if
+   longer.
+
+  </dl>
+
+   <p>The above text is largely inspired from Peter A. Darnell and
+   Philip E. Margolis' highly recommended book "C -- a Software
+   Engineering Approach", ISBN 0-387-97389-3.
+
+   @author <a href="mailto:cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+
+   @since 0.8.2 */
+public class PatternLayout extends Layout {
+
+
+  /** Default pattern string for log output. Currently set to the
+      string <b>"%m%n"</b> which just prints the application supplied
+      message. */
+  public final static String DEFAULT_CONVERSION_PATTERN ="%m%n";
+
+  /** A conversion pattern equivalent to the TTCCCLayout.
+      Current value is <b>%r [%t] %p %c %x - %m%n</b>. */
+  public final static String TTCC_CONVERSION_PATTERN
+                                             = "%r [%t] %p %c %x - %m%n";
+
+
+  protected final int BUF_SIZE = 256;
+  protected final int MAX_CAPACITY = 1024;
+
+
+  // output buffer appended to when format() is invoked
+  private StringBuffer sbuf = new StringBuffer(BUF_SIZE);
+
+  private String pattern;
+
+  private PatternConverter head;
+
+  /**
+     Constructs a PatternLayout using the DEFAULT_LAYOUT_PATTERN.
+
+     The default pattern just produces the application supplied message.
+  */
+  public PatternLayout() {
+    this(DEFAULT_CONVERSION_PATTERN);
+  }
+
+  /**
+     Constructs a PatternLayout using the supplied conversion pattern.
+  */
+  public PatternLayout(String pattern) {
+    this.pattern = pattern;
+    head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN :
+                            pattern).parse();
+  }
+
+   /**
+     Set the <b>ConversionPattern</b> option. This is the string which
+     controls formatting and consists of a mix of literal content and
+     conversion specifiers.
+   */
+  public
+  void setConversionPattern(String conversionPattern) {
+    pattern = conversionPattern;
+    head = createPatternParser(conversionPattern).parse();
+  }
+
+  /**
+     Returns the value of the <b>ConversionPattern</b> option.
+   */
+  public
+  String getConversionPattern() {
+    return pattern;
+  }
+
+  /**
+     Does not do anything as options become effective
+  */
+  public
+  void activateOptions() {
+    // nothing to do.
+  }
+
+ /**
+     The PatternLayout does not handle the throwable contained within
+     {@link LoggingEvent LoggingEvents}. Thus, it returns
+     <code>true</code>.
+
+     @since 0.8.4 */
+  public
+  boolean ignoresThrowable() {
+    return true;
+  }
+
+  /**
+    Returns PatternParser used to parse the conversion string. Subclasses
+    may override this to return a subclass of PatternParser which recognize
+    custom conversion characters.
+
+    @since 0.9.0
+  */
+  protected PatternParser createPatternParser(String pattern) {
+    return new PatternParser(pattern);
+  }
+
+
+  /**
+     Produces a formatted string as specified by the conversion pattern.
+  */
+  public String format(LoggingEvent event) {
+    // Reset working stringbuffer
+    if(sbuf.capacity() > MAX_CAPACITY) {
+      sbuf = new StringBuffer(BUF_SIZE);
+    } else {
+      sbuf.setLength(0);
+    }
+
+    PatternConverter c = head;
+
+    while(c != null) {
+      c.format(sbuf, event);
+      c = c.next;
+    }
+    return sbuf.toString();
+  }
+}
diff --git a/srcjar/org/apache/log4j/Priority.java b/srcjar/org/apache/log4j/Priority.java
new file mode 100644 (file)
index 0000000..53e94b4
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:  Kitching Simon <Simon.Kitching@orange.ch>
+
+package org.apache.log4j;
+
+/**
+   <font color="#AA4444">Refrain from using this class directly, use
+   the {@link Level} class instead</font>.
+
+   @author Ceki G&uuml;lc&uuml; */
+public class Priority {
+
+  transient int level;
+  transient String levelStr;
+  transient int syslogEquivalent;
+
+  public final static int OFF_INT = Integer.MAX_VALUE;
+  public final static int FATAL_INT = 50000;
+  public final static int ERROR_INT = 40000;
+  public final static int WARN_INT  = 30000;
+  public final static int INFO_INT  = 20000;
+  public final static int DEBUG_INT = 10000;
+    //public final static int FINE_INT = DEBUG_INT;
+  public final static int ALL_INT = Integer.MIN_VALUE;
+
+  /**
+   * @deprecated Use {@link Level#FATAL} instead.
+   */
+  final static public Priority FATAL = new Level(FATAL_INT, "FATAL", 0);
+
+  /**
+   * @deprecated Use {@link Level#ERROR} instead.
+   */
+  final static public Priority ERROR = new Level(ERROR_INT, "ERROR", 3);
+
+  /**
+   * @deprecated Use {@link Level#WARN} instead.
+   */
+  final static public Priority WARN  = new Level(WARN_INT, "WARN",  4);
+
+  /**
+   * @deprecated Use {@link Level#INFO} instead.
+   */
+  final static public Priority INFO  = new Level(INFO_INT, "INFO",  6);
+
+  /**
+   * @deprecated Use {@link Level#DEBUG} instead.
+   */
+  final static public Priority DEBUG = new Level(DEBUG_INT, "DEBUG", 7);
+
+
+  /**
+    * Default constructor for deserialization.
+    */
+  protected Priority() {
+      level = DEBUG_INT;
+      levelStr = "DEBUG";
+      syslogEquivalent = 7;
+  }
+
+  /**
+     Instantiate a level object.
+   */
+  protected
+  Priority(int level, String levelStr, int syslogEquivalent) {
+    this.level = level;
+    this.levelStr = levelStr;
+    this.syslogEquivalent = syslogEquivalent;
+  }
+
+  /**
+     Two priorities are equal if their level fields are equal.
+     @since 1.2
+   */
+  public
+  boolean equals(Object o) {
+    if(o instanceof Priority) {
+      Priority r = (Priority) o;
+      return (this.level == r.level);
+    } else {
+      return false;
+    }
+  }
+
+  /**
+     Return the syslog equivalent of this priority as an integer.
+   */
+  public
+  final
+  int getSyslogEquivalent() {
+    return syslogEquivalent;
+  }
+
+
+   
+  /**
+     Returns <code>true</code> if this level has a higher or equal
+     level than the level passed as argument, <code>false</code>
+     otherwise.  
+     
+     <p>You should think twice before overriding the default
+     implementation of <code>isGreaterOrEqual</code> method.
+
+  */
+  public
+  boolean isGreaterOrEqual(Priority r) {
+    return level >= r.level;
+  }
+
+  /**
+     Return all possible priorities as an array of Level objects in
+     descending order.
+
+     @deprecated This method will be removed with no replacement.
+  */
+  public
+  static
+  Priority[] getAllPossiblePriorities() {
+    return new Priority[] {Priority.FATAL, Priority.ERROR, Level.WARN, 
+                          Priority.INFO, Priority.DEBUG};
+  }
+
+
+  /**
+     Returns the string representation of this priority.
+   */
+  final
+  public
+  String toString() {
+    return levelStr;
+  }
+
+  /**
+     Returns the integer representation of this level.
+   */
+  public
+  final
+  int toInt() {
+    return level;
+  }
+
+  /**
+   * @deprecated Please use the {@link Level#toLevel(String)} method instead.
+  */
+  public
+  static
+  Priority toPriority(String sArg) {
+    return Level.toLevel(sArg);
+  }
+
+  /**
+   * @deprecated Please use the {@link Level#toLevel(int)} method instead.   
+   */
+  public
+  static
+  Priority toPriority(int val) {
+    return toPriority(val, Priority.DEBUG);
+  }
+
+  /**
+   * @deprecated Please use the {@link Level#toLevel(int, Level)} method instead.   
+  */
+  public
+  static
+  Priority toPriority(int val, Priority defaultPriority) {
+    return Level.toLevel(val, (Level) defaultPriority);
+  }
+
+  /**
+   * @deprecated Please use the {@link Level#toLevel(String, Level)} method instead.   
+   */
+  public
+  static
+  Priority toPriority(String sArg, Priority defaultPriority) {                  
+    return Level.toLevel(sArg, (Level) defaultPriority);
+  }
+}
diff --git a/srcjar/org/apache/log4j/PropertyConfigurator.java b/srcjar/org/apache/log4j/PropertyConfigurator.java
new file mode 100644 (file)
index 0000000..3e5cc26
--- /dev/null
@@ -0,0 +1,1003 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Contributors: "Luke Blanshard" <Luke@quiq.com>
+//              "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
+//               Anders Kristensen <akristensen@dynamicsoft.com>
+
+package org.apache.log4j;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.FileWatchdog;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.apache.log4j.spi.ErrorHandler;
+
+/**
+   Allows the configuration of log4j from an external file.  See
+   <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
+   expected format.
+
+   <p>It is sometimes useful to see how log4j is reading configuration
+   files. You can enable log4j internal logging by defining the
+   <b>log4j.debug</b> variable.
+
+   <P>As of log4j version 0.8.5, at class initialization time class,
+   the file <b>log4j.properties</b> will be searched from the search
+   path used to load classes. If the file can be found, then it will
+   be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
+   method.
+
+   <p>The <code>PropertyConfigurator</code> does not handle the
+   advanced configuration features supported by the {@link
+   org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
+   support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers},
+   nested appenders such as the {@link org.apache.log4j.AsyncAppender
+   AsyncAppender}, etc.
+
+   <p>All option <em>values</em> admit variable substitution. The
+   syntax of variable substitution is similar to that of Unix
+   shells. The string between an opening <b>&quot;${&quot;</b> and
+   closing <b>&quot;}&quot;</b> is interpreted as a key. The value of
+   the substituted variable can be defined as a system property or in
+   the configuration file itself. The value of the key is first
+   searched in the system properties, and if not found there, it is
+   then searched in the configuration file being parsed.  The
+   corresponding value replaces the ${variableName} sequence. For
+   example, if <code>java.home</code> system property is set to
+   <code>/home/xyz</code>, then every occurrence of the sequence
+   <code>${java.home}</code> will be interpreted as
+   <code>/home/xyz</code>.
+
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Anders Kristensen
+   @since 0.8.1 */
+public class PropertyConfigurator implements Configurator {
+
+  /**
+     Used internally to keep track of configured appenders.
+   */
+  protected Hashtable registry = new Hashtable(11);  
+  private LoggerRepository repository;
+  protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
+
+  static final String      CATEGORY_PREFIX = "log4j.category.";
+  static final String      LOGGER_PREFIX   = "log4j.logger.";
+  static final String       FACTORY_PREFIX = "log4j.factory";
+  static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
+  static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
+  static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
+  static final String      APPENDER_PREFIX = "log4j.appender.";
+  static final String      RENDERER_PREFIX = "log4j.renderer.";
+  static final String      THRESHOLD_PREFIX = "log4j.threshold";
+  private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
+  private static final String LOGGER_REF       = "logger-ref";
+  private static final String ROOT_REF         = "root-ref";
+  private static final String APPENDER_REF_TAG         = "appender-ref";  
+  
+
+  /** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
+      LoggerFactory}.  Currently set to "<code>log4j.loggerFactory</code>".  */
+  public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
+
+    /**
+     * If property set to true, then hierarchy will be reset before configuration.
+     */
+  private static final String RESET_KEY = "log4j.reset";
+
+  static final private String INTERNAL_ROOT_NAME = "root";
+
+  /**
+    Read configuration from a file. <b>The existing configuration is
+    not cleared nor reset.</b> If you require a different behavior,
+    then call {@link  LogManager#resetConfiguration
+    resetConfiguration} method before calling
+    <code>doConfigure</code>.
+
+    <p>The configuration file consists of statements in the format
+    <code>key=value</code>. The syntax of different configuration
+    elements are discussed below.
+
+    <h3>Repository-wide threshold</h3>
+
+    <p>The repository-wide threshold filters logging requests by level
+    regardless of logger. The syntax is:
+
+    <pre>
+    log4j.threshold=[level]
+    </pre>
+
+    <p>The level value can consist of the string values OFF, FATAL,
+    ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
+    custom level value can be specified in the form
+    level#classname. By default the repository-wide threshold is set
+    to the lowest possible value, namely the level <code>ALL</code>.
+    </p>
+
+
+    <h3>Appender configuration</h3>
+
+    <p>Appender configuration syntax is:
+    <pre>
+    # For appender named <i>appenderName</i>, set its class.
+    # Note: The appender name can contain dots.
+    log4j.appender.appenderName=fully.qualified.name.of.appender.class
+
+    # Set appender specific options.
+    log4j.appender.appenderName.option1=value1
+    ...
+    log4j.appender.appenderName.optionN=valueN
+    </pre>
+
+    For each named appender you can configure its {@link Layout}. The
+    syntax for configuring an appender's layout is:
+    <pre>
+    log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
+    log4j.appender.appenderName.layout.option1=value1
+    ....
+    log4j.appender.appenderName.layout.optionN=valueN
+    </pre>
+
+    The syntax for adding {@link Filter}s to an appender is:
+    <pre>
+    log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
+    log4j.appender.appenderName.filter.ID.option1=value1
+    ...
+    log4j.appender.appenderName.filter.ID.optionN=valueN
+    </pre>
+    The first line defines the class name of the filter identified by ID;
+    subsequent lines with the same ID specify filter option - value
+    pairs. Multiple filters are added to the appender in the lexicographic
+    order of IDs.
+
+    The syntax for adding an {@link ErrorHandler} to an appender is:
+    <pre>
+    log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
+    log4j.appender.appenderName.errorhandler.root-ref={true|false}
+    log4j.appender.appenderName.errorhandler.logger-ref=loggerName
+    log4j.appender.appenderName.errorhandler.appender-ref=appenderName
+    log4j.appender.appenderName.errorhandler.option1=value1
+    ...
+    log4j.appender.appenderName.errorhandler.optionN=valueN
+    </pre>
+
+    <h3>Configuring loggers</h3>
+
+    <p>The syntax for configuring the root logger is:
+    <pre>
+      log4j.rootLogger=[level], appenderName, appenderName, ...
+    </pre>
+
+    <p>This syntax means that an optional <em>level</em> can be
+    supplied followed by appender names separated by commas.
+
+    <p>The level value can consist of the string values OFF, FATAL,
+    ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
+    custom level value can be specified in the form
+    <code>level#classname</code>.
+
+    <p>If a level value is specified, then the root level is set
+    to the corresponding level.  If no level value is specified,
+    then the root level remains untouched.
+
+    <p>The root logger can be assigned multiple appenders.
+
+    <p>Each <i>appenderName</i> (separated by commas) will be added to
+    the root logger. The named appender is defined using the
+    appender syntax defined above.
+
+    <p>For non-root categories the syntax is almost the same:
+    <pre>
+    log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
+    </pre>
+
+    <p>The meaning of the optional level value is discussed above
+    in relation to the root logger. In addition however, the value
+    INHERITED can be specified meaning that the named logger should
+    inherit its level from the logger hierarchy.
+
+    <p>If no level value is supplied, then the level of the
+    named logger remains untouched.
+
+    <p>By default categories inherit their level from the
+    hierarchy. However, if you set the level of a logger and later
+    decide that that logger should inherit its level, then you should
+    specify INHERITED as the value for the level value. NULL is a
+    synonym for INHERITED.
+
+    <p>Similar to the root logger syntax, each <i>appenderName</i>
+    (separated by commas) will be attached to the named logger.
+
+    <p>See the <a href="../../../../manual.html#additivity">appender
+    additivity rule</a> in the user manual for the meaning of the
+    <code>additivity</code> flag.
+
+    <h3>ObjectRenderers</h3>
+
+    You can customize the way message objects of a given type are
+    converted to String before being logged. This is done by
+    specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
+    for the object type would like to customize.
+
+    <p>The syntax is:
+
+    <pre>
+    log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
+    </pre>
+
+    As in,
+    <pre>
+    log4j.renderer.my.Fruit=my.FruitRenderer
+    </pre>
+
+   <h3>ThrowableRenderer</h3>
+
+   You can customize the way an instance of Throwable is
+   converted to String before being logged. This is done by
+   specifying an {@link org.apache.log4j.spi.ThrowableRenderer ThrowableRenderer}.
+
+   <p>The syntax is:
+
+   <pre>
+   log4j.throwableRenderer=fully.qualified.name.of.rendering.class
+   log4j.throwableRenderer.paramName=paramValue
+   </pre>
+
+   As in,
+   <pre>
+   log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
+   </pre>
+
+    <h3>Logger Factories</h3>
+
+    The usage of custom logger factories is discouraged and no longer
+    documented.
+
+    <h3>Resetting Hierarchy</h3>
+
+    The hierarchy will be reset before configuration when
+    log4j.reset=true is present in the properties file.
+
+    <h3>Example</h3>
+
+    <p>An example configuration is given below. Other configuration
+    file examples are given in the <code>examples</code> folder.
+
+    <pre>
+
+    # Set options for appender named "A1".
+    # Appender "A1" will be a SyslogAppender
+    log4j.appender.A1=org.apache.log4j.net.SyslogAppender
+
+    # The syslog daemon resides on www.abc.net
+    log4j.appender.A1.SyslogHost=www.abc.net
+
+    # A1's layout is a PatternLayout, using the conversion pattern
+    # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
+    # include # the relative time since the start of the application in
+    # milliseconds, followed by the level of the log request,
+    # followed by the two rightmost components of the logger name,
+    # followed by the callers method name, followed by the line number,
+    # the nested diagnostic context and finally the message itself.
+    # Refer to the documentation of {@link PatternLayout} for further information
+    # on the syntax of the ConversionPattern key.
+    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+    log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
+
+    # Set options for appender named "A2"
+    # A2 should be a RollingFileAppender, with maximum file size of 10 MB
+    # using at most one backup file. A2's layout is TTCC, using the
+    # ISO8061 date format with context printing enabled.
+    log4j.appender.A2=org.apache.log4j.RollingFileAppender
+    log4j.appender.A2.MaxFileSize=10MB
+    log4j.appender.A2.MaxBackupIndex=1
+    log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
+    log4j.appender.A2.layout.ContextPrinting=enabled
+    log4j.appender.A2.layout.DateFormat=ISO8601
+
+    # Root logger set to DEBUG using the A2 appender defined above.
+    log4j.rootLogger=DEBUG, A2
+
+    # Logger definitions:
+    # The SECURITY logger inherits is level from root. However, it's output
+    # will go to A1 appender defined above. It's additivity is non-cumulative.
+    log4j.logger.SECURITY=INHERIT, A1
+    log4j.additivity.SECURITY=false
+
+    # Only warnings or above will be logged for the logger "SECURITY.access".
+    # Output will go to A1.
+    log4j.logger.SECURITY.access=WARN
+
+
+    # The logger "class.of.the.day" inherits its level from the
+    # logger hierarchy.  Output will go to the appender's of the root
+    # logger, A2 in this case.
+    log4j.logger.class.of.the.day=INHERIT
+    </pre>
+
+    <p>Refer to the <b>setOption</b> method in each Appender and
+    Layout for class specific options.
+
+    <p>Use the <code>#</code> or <code>!</code> characters at the
+    beginning of a line for comments.
+
+   <p>
+   @param configFileName The name of the configuration file where the
+   configuration information is stored.
+
+  */
+  public
+  void doConfigure(String configFileName, LoggerRepository hierarchy) {
+    Properties props = new Properties();
+    FileInputStream istream = null;
+    try {
+      istream = new FileInputStream(configFileName);
+      props.load(istream);
+      istream.close();
+    }
+    catch (Exception e) {
+      if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+          Thread.currentThread().interrupt();
+      }
+      LogLog.error("Could not read configuration file ["+configFileName+"].", e);
+      LogLog.error("Ignoring configuration file [" + configFileName+"].");
+      return;
+    } finally {
+        if(istream != null) {
+            try {
+                istream.close();
+            } catch(InterruptedIOException ignore) {
+                Thread.currentThread().interrupt();
+            } catch(Throwable ignore) {
+            }
+
+        }
+    }
+    // If we reach here, then the config file is alright.
+    doConfigure(props, hierarchy);
+  }
+
+  /**
+   */
+  static
+  public
+  void configure(String configFilename) {
+    new PropertyConfigurator().doConfigure(configFilename,
+                                          LogManager.getLoggerRepository());
+  }
+
+  /**
+  Read configuration options from url <code>configURL</code>.
+
+  @since 0.8.2
+*/
+public
+static
+void configure(java.net.URL configURL) {
+ new PropertyConfigurator().doConfigure(configURL,
+                    LogManager.getLoggerRepository());
+}
+
+/**
+Reads configuration options from an InputStream.
+
+@since 1.2.17
+*/
+public
+static
+void configure(InputStream inputStream) {
+new PropertyConfigurator().doConfigure(inputStream,
+                  LogManager.getLoggerRepository());
+}
+
+
+  /**
+     Read configuration options from <code>properties</code>.
+
+     See {@link #doConfigure(String, LoggerRepository)} for the expected format.
+  */
+  static
+  public
+  void configure(Properties properties) {
+    new PropertyConfigurator().doConfigure(properties,
+                                          LogManager.getLoggerRepository());
+  }
+
+  /**
+     Like {@link #configureAndWatch(String, long)} except that the
+     default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
+     used.
+
+     @param configFilename A file in key=value format.
+
+  */
+  static
+  public
+  void configureAndWatch(String configFilename) {
+    configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
+  }
+
+
+  /**
+     Read the configuration file <code>configFilename</code> if it
+     exists. Moreover, a thread will be created that will periodically
+     check if <code>configFilename</code> has been created or
+     modified. The period is determined by the <code>delay</code>
+     argument. If a change or file creation is detected, then
+     <code>configFilename</code> is read to configure log4j.
+
+      @param configFilename A file in key=value format.
+      @param delay The delay in milliseconds to wait between each check.
+  */
+  static
+  public
+  void configureAndWatch(String configFilename, long delay) {
+    PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
+    pdog.setDelay(delay);
+    pdog.start();
+  }
+
+
+  /**
+     Read configuration options from <code>properties</code>.
+
+     See {@link #doConfigure(String, LoggerRepository)} for the expected format.
+  */
+  public
+  void doConfigure(Properties properties, LoggerRepository hierarchy) {
+       repository = hierarchy;
+    String value = properties.getProperty(LogLog.DEBUG_KEY);
+    if(value == null) {
+      value = properties.getProperty("log4j.configDebug");
+      if(value != null) {
+        LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
+    }
+    }
+
+    if(value != null) {
+      LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
+    }
+
+      //
+      //   if log4j.reset=true then
+      //        reset hierarchy
+    String reset = properties.getProperty(RESET_KEY);
+    if (reset != null && OptionConverter.toBoolean(reset, false)) {
+          hierarchy.resetConfiguration();
+    }
+
+    String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
+                                                      properties);
+    if(thresholdStr != null) {
+      hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
+                                                    Level.ALL));
+      LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
+    }
+    
+    configureRootCategory(properties, hierarchy);
+    configureLoggerFactory(properties);
+    parseCatsAndRenderers(properties, hierarchy);
+
+    LogLog.debug("Finished configuring.");
+    // We don't want to hold references to appenders preventing their
+    // garbage collection.
+    registry.clear();
+  }
+
+    /**
+     * Read configuration options from an InputStream.
+     * 
+     * @since 1.2.17
+     */
+    public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
+        Properties props = new Properties();
+        try {
+            props.load(inputStream);
+        } catch (IOException e) {
+            if (e instanceof InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not read configuration file from InputStream [" + inputStream
+                 + "].", e);
+            LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
+            return;
+          }
+        this.doConfigure(props, hierarchy);
+    }
+
+  /**
+     Read configuration options from url <code>configURL</code>.
+   */
+  public
+  void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
+    Properties props = new Properties();
+    LogLog.debug("Reading configuration from URL " + configURL);
+    InputStream istream = null;
+    URLConnection uConn = null;
+    try {
+      uConn = configURL.openConnection();
+      uConn.setUseCaches(false);
+      istream = uConn.getInputStream();
+      props.load(istream);
+    }
+    catch (Exception e) {
+      if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+          Thread.currentThread().interrupt();
+      }
+      LogLog.error("Could not read configuration file from URL [" + configURL
+                  + "].", e);
+      LogLog.error("Ignoring configuration file [" + configURL +"].");
+      return;
+    }
+    finally {
+        if (istream != null) {
+            try {
+                istream.close();
+            } catch(InterruptedIOException ignore) {
+                Thread.currentThread().interrupt();
+            } catch(IOException ignore) {
+            } catch(RuntimeException ignore) {
+            }
+        }
+    }
+    doConfigure(props, hierarchy);
+  }
+
+
+  // --------------------------------------------------------------------------
+  // Internal stuff
+  // --------------------------------------------------------------------------
+
+  /**
+     Check the provided <code>Properties</code> object for a
+     {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
+     entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
+     exists, an attempt is made to create an instance using the default
+     constructor.  This instance is used for subsequent Category creations
+     within this configurator.
+
+     @see #parseCatsAndRenderers
+   */
+  protected void configureLoggerFactory(Properties props) {
+    String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
+                                                          props);
+    if(factoryClassName != null) {
+      LogLog.debug("Setting category factory to ["+factoryClassName+"].");
+      loggerFactory = (LoggerFactory)
+                 OptionConverter.instantiateByClassName(factoryClassName,
+                                                        LoggerFactory.class,
+                                                        loggerFactory);
+      PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
+    }
+  }
+
+  /*
+  void configureOptionHandler(OptionHandler oh, String prefix,
+                             Properties props) {
+    String[] options = oh.getOptionStrings();
+    if(options == null)
+      return;
+
+    String value;
+    for(int i = 0; i < options.length; i++) {
+      value =  OptionConverter.findAndSubst(prefix + options[i], props);
+      LogLog.debug(
+         "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
+      // Some option handlers assume that null value are not passed to them.
+      // So don't remove this check
+      if(value != null) {
+       oh.setOption(options[i], value);
+      }
+    }
+    oh.activateOptions();
+  }
+  */
+
+
+  void configureRootCategory(Properties props, LoggerRepository hierarchy) {
+    String effectiveFrefix = ROOT_LOGGER_PREFIX;
+    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
+
+    if(value == null) {
+      value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
+      effectiveFrefix = ROOT_CATEGORY_PREFIX;
+    }
+
+    if(value == null) {
+        LogLog.debug("Could not find root logger information. Is this OK?");
+    } else {
+      Logger root = hierarchy.getRootLogger();
+      synchronized(root) {
+       parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
+      }
+    }
+  }
+
+
+  /**
+     Parse non-root elements, such non-root categories and renderers.
+  */
+  protected
+  void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
+    Enumeration enumeration = props.propertyNames();
+    while(enumeration.hasMoreElements()) {
+      String key = (String) enumeration.nextElement();
+      if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
+       String loggerName = null;
+       if(key.startsWith(CATEGORY_PREFIX)) {
+         loggerName = key.substring(CATEGORY_PREFIX.length());
+       } else if(key.startsWith(LOGGER_PREFIX)) {
+         loggerName = key.substring(LOGGER_PREFIX.length());
+       }
+       String value =  OptionConverter.findAndSubst(key, props);
+       Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
+       synchronized(logger) {
+         parseCategory(props, logger, key, loggerName, value);
+         parseAdditivityForLogger(props, logger, loggerName);
+       }
+      } else if(key.startsWith(RENDERER_PREFIX)) {
+       String renderedClass = key.substring(RENDERER_PREFIX.length());
+       String renderingClass = OptionConverter.findAndSubst(key, props);
+       if(hierarchy instanceof RendererSupport) {
+         RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
+                                 renderingClass);
+       }
+      } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
+          if (hierarchy instanceof ThrowableRendererSupport) {
+            ThrowableRenderer tr = (ThrowableRenderer)
+                  OptionConverter.instantiateByKey(props,
+                          THROWABLE_RENDERER_PREFIX,
+                          org.apache.log4j.spi.ThrowableRenderer.class,
+                          null);
+            if(tr == null) {
+                LogLog.error(
+                    "Could not instantiate throwableRenderer.");
+            } else {
+                PropertySetter setter = new PropertySetter(tr);
+                setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
+                ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
+
+            }
+          }
+      }
+    }
+  }
+
+  /**
+     Parse the additivity option for a non-root category.
+   */
+  void parseAdditivityForLogger(Properties props, Logger cat,
+                                 String loggerName) {
+    String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
+                                            props);
+    LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
+    // touch additivity only if necessary
+    if((value != null) && (!value.equals(""))) {
+      boolean additivity = OptionConverter.toBoolean(value, true);
+      LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
+                  additivity);
+      cat.setAdditivity(additivity);
+    }
+  }
+
+  /**
+     This method must work for the root category as well.
+   */
+  void parseCategory(Properties props, Logger logger, String optionKey,
+                    String loggerName, String value) {
+
+    LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
+    // We must skip over ',' but not white space
+    StringTokenizer st = new StringTokenizer(value, ",");
+
+    // If value is not in the form ", appender.." or "", then we should set
+    // the level of the loggeregory.
+
+    if(!(value.startsWith(",") || value.equals(""))) {
+
+      // just to be on the safe side...
+      if(!st.hasMoreTokens()) {
+        return;
+    }
+
+      String levelStr = st.nextToken();
+      LogLog.debug("Level token is [" + levelStr + "].");
+
+      // If the level value is inherited, set category level value to
+      // null. We also check that the user has not specified inherited for the
+      // root category.
+      if(INHERITED.equalsIgnoreCase(levelStr) || 
+                                         NULL.equalsIgnoreCase(levelStr)) {
+       if(loggerName.equals(INTERNAL_ROOT_NAME)) {
+         LogLog.warn("The root logger cannot be set to null.");
+       } else {
+         logger.setLevel(null);
+       }
+      } else {
+       logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
+      }
+      LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
+    }
+
+    // Begin by removing all existing appenders.
+    logger.removeAllAppenders();
+
+    Appender appender;
+    String appenderName;
+    while(st.hasMoreTokens()) {
+      appenderName = st.nextToken().trim();
+      if(appenderName == null || appenderName.equals(",")) {
+        continue;
+    }
+      LogLog.debug("Parsing appender named \"" + appenderName +"\".");
+      appender = parseAppender(props, appenderName);
+      if(appender != null) {
+       logger.addAppender(appender);
+      }
+    }
+  }
+
+  Appender parseAppender(Properties props, String appenderName) {
+    Appender appender = registryGet(appenderName);
+    if((appender != null)) {
+      LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
+      return appender;
+    }
+    // Appender was not previously initialized.
+    String prefix = APPENDER_PREFIX + appenderName;
+    String layoutPrefix = prefix + ".layout";
+
+    appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
+                                             org.apache.log4j.Appender.class,
+                                             null);
+    if(appender == null) {
+      LogLog.error(
+              "Could not instantiate appender named \"" + appenderName+"\".");
+      return null;
+    }
+    appender.setName(appenderName);
+
+    if(appender instanceof OptionHandler) {
+      if(appender.requiresLayout()) {
+       Layout layout = (Layout) OptionConverter.instantiateByKey(props,
+                                                                 layoutPrefix,
+                                                                 Layout.class,
+                                                                 null);
+       if(layout != null) {
+         appender.setLayout(layout);
+         LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
+         //configureOptionHandler(layout, layoutPrefix + ".", props);
+          PropertySetter.setProperties(layout, props, layoutPrefix + ".");
+         LogLog.debug("End of parsing for \"" + appenderName +"\".");
+       }
+      }
+      final String errorHandlerPrefix = prefix + ".errorhandler";
+      String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
+      if (errorHandlerClass != null) {
+               ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
+                                         errorHandlerPrefix,
+                                         ErrorHandler.class,
+                                         null);
+               if (eh != null) {
+                         appender.setErrorHandler(eh);
+                         LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
+                         parseErrorHandler(eh, errorHandlerPrefix, props, repository);
+                         final Properties edited = new Properties();
+                         final String[] keys = new String[] { 
+                                         errorHandlerPrefix + "." + ROOT_REF,
+                                         errorHandlerPrefix + "." + LOGGER_REF,
+                                         errorHandlerPrefix + "." + APPENDER_REF_TAG
+                         };
+                         for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
+                                 Map.Entry entry = (Map.Entry) iter.next();
+                                 int i = 0;
+                                 for(; i < keys.length; i++) {
+                                         if(keys[i].equals(entry.getKey())) {
+                            break;
+                        }
+                                 }
+                                 if (i == keys.length) {
+                                         edited.put(entry.getKey(), entry.getValue());
+                                 }
+                         }
+                     PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
+                         LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
+               }
+         
+      }
+      //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
+      PropertySetter.setProperties(appender, props, prefix + ".");
+      LogLog.debug("Parsed \"" + appenderName +"\" options.");
+    }
+    parseAppenderFilters(props, appenderName, appender);
+    registryPut(appender);
+    return appender;
+  }
+  
+  private void parseErrorHandler(
+                 final ErrorHandler eh,
+                 final String errorHandlerPrefix,
+                 final Properties props, 
+                 final LoggerRepository hierarchy) {
+               boolean rootRef = OptionConverter.toBoolean(
+                                         OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
+               if (rootRef) {
+                                 eh.setLogger(hierarchy.getRootLogger());
+           }
+               String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
+               if (loggerName != null) {
+                       Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
+                                       : hierarchy.getLogger(loggerName, loggerFactory);
+                       eh.setLogger(logger);
+               }
+               String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
+               if (appenderName != null) {
+                       Appender backup = parseAppender(props, appenderName);
+                       if (backup != null) {
+                               eh.setBackupAppender(backup);
+                       }
+               }
+  }
+                               
+  
+  void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
+    // extract filters and filter options from props into a hashtable mapping
+    // the property name defining the filter class to a list of pre-parsed
+    // name-value pairs associated to that filter
+    final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
+    int fIdx = filterPrefix.length();
+    Hashtable filters = new Hashtable();
+    Enumeration e = props.keys();
+    String name = "";
+    while (e.hasMoreElements()) {
+      String key = (String) e.nextElement();
+      if (key.startsWith(filterPrefix)) {
+        int dotIdx = key.indexOf('.', fIdx);
+        String filterKey = key;
+        if (dotIdx != -1) {
+          filterKey = key.substring(0, dotIdx);
+          name = key.substring(dotIdx+1);
+        }
+        Vector filterOpts = (Vector) filters.get(filterKey);
+        if (filterOpts == null) {
+          filterOpts = new Vector();
+          filters.put(filterKey, filterOpts);
+        }
+        if (dotIdx != -1) {
+          String value = OptionConverter.findAndSubst(key, props);
+          filterOpts.add(new NameValue(name, value));
+        }
+      }
+    }
+
+    // sort filters by IDs, insantiate filters, set filter options,
+    // add filters to the appender
+    Enumeration g = new SortedKeyEnumeration(filters);
+    while (g.hasMoreElements()) {
+      String key = (String) g.nextElement();
+      String clazz = props.getProperty(key);
+      if (clazz != null) {
+        LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
+        Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
+        if (filter != null) {
+          PropertySetter propSetter = new PropertySetter(filter);
+          Vector v = (Vector)filters.get(key);
+          Enumeration filterProps = v.elements();
+          while (filterProps.hasMoreElements()) {
+            NameValue kv = (NameValue)filterProps.nextElement();
+            propSetter.setProperty(kv.key, kv.value);
+          }
+          propSetter.activate();
+          LogLog.debug("Adding filter of type ["+filter.getClass()
+           +"] to appender named ["+appender.getName()+"].");
+          appender.addFilter(filter);
+        }
+      } else {
+        LogLog.warn("Missing class definition for filter: ["+key+"]");
+      }
+    }
+  }
+
+
+  void  registryPut(Appender appender) {
+    registry.put(appender.getName(), appender);
+  }
+
+  Appender registryGet(String name) {
+    return (Appender) registry.get(name);
+  }
+}
+
+class PropertyWatchdog extends FileWatchdog {
+
+  PropertyWatchdog(String filename) {
+    super(filename);
+  }
+
+  /**
+     Call {@link PropertyConfigurator#configure(String)} with the
+     <code>filename</code> to reconfigure log4j. */
+  public
+  void doOnChange() {
+    new PropertyConfigurator().doConfigure(filename,
+                                          LogManager.getLoggerRepository());
+  }
+}
+
+class NameValue {
+  String key, value;
+  public NameValue(String key, String value) {
+    this.key = key;
+    this.value = value;
+  }
+  public String toString() {
+    return key + "=" + value;
+  }
+}
+
+class SortedKeyEnumeration implements Enumeration {
+
+  private Enumeration e;
+
+  public SortedKeyEnumeration(Hashtable ht) {
+    Enumeration f = ht.keys();
+    Vector keys = new Vector(ht.size());
+    for (int i, last = 0; f.hasMoreElements(); ++last) {
+      String key = (String) f.nextElement();
+      for (i = 0; i < last; ++i) {
+        String s = (String) keys.get(i);
+        if (key.compareTo(s) <= 0) {
+            break;
+        }
+      }
+      keys.add(i, key);
+    }
+    e = keys.elements();
+  }
+
+  public boolean hasMoreElements() {
+    return e.hasMoreElements();
+  }
+
+  public Object nextElement() {
+    return e.nextElement();
+  }
+}
diff --git a/srcjar/org/apache/log4j/ProvisionNode.java b/srcjar/org/apache/log4j/ProvisionNode.java
new file mode 100644 (file)
index 0000000..f112682
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import java.util.Vector;
+
+class ProvisionNode extends Vector {
+  private static final long serialVersionUID = -4479121426311014469L;
+
+  ProvisionNode(Logger logger) {
+    super();
+    this.addElement(logger);
+  }
+}
diff --git a/srcjar/org/apache/log4j/README.txt b/srcjar/org/apache/log4j/README.txt
new file mode 100644 (file)
index 0000000..342f5ff
--- /dev/null
@@ -0,0 +1 @@
+source https://github.com/apache/log4j Latest commit 7be00ee on Jun 4, 2015
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/RollingFileAppender.java b/srcjar/org/apache/log4j/RollingFileAppender.java
new file mode 100644 (file)
index 0000000..9cfacc6
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.io.File;
+import java.io.InterruptedIOException;
+
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.CountingQuietWriter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   RollingFileAppender extends FileAppender to backup the log files when
+   they reach a certain size.
+   
+   The log4j extras companion includes alternatives which should be considered
+   for new deployments and which are discussed in the documentation
+   for org.apache.log4j.rolling.RollingFileAppender.
+   
+
+   @author Heinz Richter
+   @author Ceki G&uuml;lc&uuml;
+
+*/
+public class RollingFileAppender extends FileAppender {
+
+  /**
+     The default maximum file size is 10MB.
+  */
+  protected long maxFileSize = 10*1024*1024;
+
+  /**
+     There is one backup file by default.
+   */
+  protected int  maxBackupIndex  = 1;
+
+  private long nextRollover = 0;
+
+  /**
+     The default constructor simply calls its {@link
+     FileAppender#FileAppender parents constructor}.  */
+  public
+  RollingFileAppender() {
+    super();
+  }
+
+  /**
+    Instantiate a RollingFileAppender and open the file designated by
+    <code>filename</code>. The opened filename will become the ouput
+    destination for this appender.
+
+    <p>If the <code>append</code> parameter is true, the file will be
+    appended to. Otherwise, the file desginated by
+    <code>filename</code> will be truncated before being opened.
+  */
+  public
+  RollingFileAppender(Layout layout, String filename, boolean append)
+                                      throws IOException {
+    super(layout, filename, append);
+  }
+
+  /**
+     Instantiate a FileAppender and open the file designated by
+    <code>filename</code>. The opened filename will become the output
+    destination for this appender.
+
+    <p>The file will be appended to.  */
+  public
+  RollingFileAppender(Layout layout, String filename) throws IOException {
+    super(layout, filename);
+  }
+
+  /**
+     Returns the value of the <b>MaxBackupIndex</b> option.
+   */
+  public
+  int getMaxBackupIndex() {
+    return maxBackupIndex;
+  }
+
+ /**
+    Get the maximum size that the output file is allowed to reach
+    before being rolled over to backup files.
+
+    @since 1.1
+ */
+  public
+  long getMaximumFileSize() {
+    return maxFileSize;
+  }
+
+  /**
+     Implements the usual roll over behaviour.
+
+     <p>If <code>MaxBackupIndex</code> is positive, then files
+     {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
+     are renamed to {<code>File.2</code>, ...,
+     <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
+     renamed <code>File.1</code> and closed. A new <code>File</code> is
+     created to receive further log output.
+
+     <p>If <code>MaxBackupIndex</code> is equal to zero, then the
+     <code>File</code> is truncated with no backup files created.
+
+   */
+  public // synchronization not necessary since doAppend is alreasy synched
+  void rollOver() {
+    File target;
+    File file;
+
+    if (qw != null) {
+        long size = ((CountingQuietWriter) qw).getCount();
+        LogLog.debug("rolling over count=" + size);
+        //   if operation fails, do not roll again until
+        //      maxFileSize more bytes are written
+        nextRollover = size + maxFileSize;
+    }
+    LogLog.debug("maxBackupIndex="+maxBackupIndex);
+
+    boolean renameSucceeded = true;
+    // If maxBackups <= 0, then there is no file renaming to be done.
+    if(maxBackupIndex > 0) {
+      // Delete the oldest file, to keep Windows happy.
+      file = new File(fileName + '.' + maxBackupIndex);
+      if (file.exists()) {
+        renameSucceeded = file.delete();
+    }
+
+      // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
+      for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) {
+       file = new File(fileName + "." + i);
+       if (file.exists()) {
+         target = new File(fileName + '.' + (i + 1));
+         LogLog.debug("Renaming file " + file + " to " + target);
+         renameSucceeded = file.renameTo(target);
+       }
+      }
+
+    if(renameSucceeded) {
+      // Rename fileName to fileName.1
+      target = new File(fileName + "." + 1);
+
+      this.closeFile(); // keep windows happy.
+
+      file = new File(fileName);
+      LogLog.debug("Renaming file " + file + " to " + target);
+      renameSucceeded = file.renameTo(target);
+      //
+      //   if file rename failed, reopen file with append = true
+      //
+      if (!renameSucceeded) {
+          try {
+            this.setFile(fileName, true, bufferedIO, bufferSize);
+          }
+          catch(IOException e) {
+              if (e instanceof InterruptedIOException) {
+                  Thread.currentThread().interrupt();
+              }
+              LogLog.error("setFile("+fileName+", true) call failed.", e);
+          }
+      }
+    }
+    }
+
+    //
+    //   if all renames were successful, then
+    //
+    if (renameSucceeded) {
+    try {
+      // This will also close the file. This is OK since multiple
+      // close operations are safe.
+      this.setFile(fileName, false, bufferedIO, bufferSize);
+      nextRollover = 0;
+    }
+    catch(IOException e) {
+        if (e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+        LogLog.error("setFile("+fileName+", false) call failed.", e);
+    }
+    }
+  }
+
+  public
+  synchronized
+  void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
+                                                                 throws IOException {
+    super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
+    if(append) {
+      File f = new File(fileName);
+      ((CountingQuietWriter) qw).setCount(f.length());
+    }
+  }
+
+
+  /**
+     Set the maximum number of backup files to keep around.
+
+     <p>The <b>MaxBackupIndex</b> option determines how many backup
+     files are kept before the oldest is erased. This option takes
+     a positive integer value. If set to zero, then there will be no
+     backup files and the log file will be truncated when it reaches
+     <code>MaxFileSize</code>.
+   */
+  public
+  void setMaxBackupIndex(int maxBackups) {
+    this.maxBackupIndex = maxBackups;
+  }
+
+  /**
+     Set the maximum size that the output file is allowed to reach
+     before being rolled over to backup files.
+
+     <p>This method is equivalent to {@link #setMaxFileSize} except
+     that it is required for differentiating the setter taking a
+     <code>long</code> argument from the setter taking a
+     <code>String</code> argument by the JavaBeans {@link
+     java.beans.Introspector Introspector}.
+
+     @see #setMaxFileSize(String)
+ */
+  public
+  void setMaximumFileSize(long maxFileSize) {
+    this.maxFileSize = maxFileSize;
+  }
+
+
+  /**
+     Set the maximum size that the output file is allowed to reach
+     before being rolled over to backup files.
+
+     <p>In configuration files, the <b>MaxFileSize</b> option takes an
+     long integer in the range 0 - 2^63. You can specify the value
+     with the suffixes "KB", "MB" or "GB" so that the integer is
+     interpreted being expressed respectively in kilobytes, megabytes
+     or gigabytes. For example, the value "10KB" will be interpreted
+     as 10240.
+   */
+  public
+  void setMaxFileSize(String value) {
+    maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
+  }
+
+  protected
+  void setQWForFiles(Writer writer) {
+     this.qw = new CountingQuietWriter(writer, errorHandler);
+  }
+
+  /**
+     This method differentiates RollingFileAppender from its super
+     class.
+
+     @since 0.9.0
+  */
+  protected
+  void subAppend(LoggingEvent event) {
+    super.subAppend(event);
+    if(fileName != null && qw != null) {
+        long size = ((CountingQuietWriter) qw).getCount();
+        if (size >= maxFileSize && size >= nextRollover) {
+            rollOver();
+        }
+    }
+   }
+}
diff --git a/srcjar/org/apache/log4j/SimpleLayout.java b/srcjar/org/apache/log4j/SimpleLayout.java
new file mode 100644 (file)
index 0000000..5699661
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   SimpleLayout consists of the level of the log statement,
+   followed by " - " and then the log message itself. For example,
+
+   <pre>
+           DEBUG - Hello world
+   </pre>
+
+   <p>
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.7.0
+
+   <p>{@link PatternLayout} offers a much more powerful alternative.
+*/
+public class SimpleLayout extends Layout {
+
+  StringBuffer sbuf = new StringBuffer(128);
+
+  public SimpleLayout() {
+  }
+
+  public
+  void activateOptions() {
+  }
+  
+  /**
+     Returns the log statement in a format consisting of the
+     <code>level</code>, followed by " - " and then the
+     <code>message</code>. For example, <pre> INFO - "A message"
+     </pre>
+
+     <p>The <code>category</code> parameter is ignored.
+     <p>
+     @return A byte array in SimpleLayout format.
+    */
+  public
+  String format(LoggingEvent event) {
+
+    sbuf.setLength(0);
+    sbuf.append(event.getLevel().toString());
+    sbuf.append(" - ");
+    sbuf.append(event.getRenderedMessage());
+    sbuf.append(LINE_SEP);
+    return sbuf.toString();
+  }
+
+/**
+     The SimpleLayout does not handle the throwable contained within
+     {@link LoggingEvent LoggingEvents}. Thus, it returns
+     <code>true</code>.
+
+     @since version 0.8.4 */
+  public
+  boolean ignoresThrowable() {
+    return true;
+  }
+}
diff --git a/srcjar/org/apache/log4j/TTCCLayout.java b/srcjar/org/apache/log4j/TTCCLayout.java
new file mode 100644 (file)
index 0000000..3b0e98f
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors: Christopher Williams
+//               Mathias Bogaert
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.DateLayout;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ TTCC layout format consists of time, thread, category and nested
+ diagnostic context information, hence the name.
+
+ <p>Each of the four fields can be individually enabled or
+ disabled. The time format depends on the <code>DateFormat</code>
+ used.
+
+ <p>Here is an example TTCCLayout output with the
+ {@link org.apache.log4j.helpers.RelativeTimeDateFormat}.
+
+ <pre>
+176 [main] INFO  org.apache.log4j.examples.Sort - Populating an array of 2 elements in reverse order.
+225 [main] INFO  org.apache.log4j.examples.SortAlgo - Entered the sort method.
+262 [main] DEBUG org.apache.log4j.examples.SortAlgo.OUTER i=1 - Outer loop.
+276 [main] DEBUG org.apache.log4j.examples.SortAlgo.SWAP i=1 j=0 - Swapping intArray[0] = 1 and intArray[1] = 0
+290 [main] DEBUG org.apache.log4j.examples.SortAlgo.OUTER i=0 - Outer loop.
+304 [main] INFO  org.apache.log4j.examples.SortAlgo.DUMP - Dump of interger array:
+317 [main] INFO  org.apache.log4j.examples.SortAlgo.DUMP - Element [0] = 0
+331 [main] INFO  org.apache.log4j.examples.SortAlgo.DUMP - Element [1] = 1
+343 [main] INFO  org.apache.log4j.examples.Sort - The next log statement should be an error message.
+346 [main] ERROR org.apache.log4j.examples.SortAlgo.DUMP - Tried to dump an uninitialized array.
+        at org.apache.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
+        at org.apache.log4j.examples.Sort.main(Sort.java:64)
+467 [main] INFO  org.apache.log4j.examples.Sort - Exiting main method.
+</pre>
+
+  <p>The first field is the number of milliseconds elapsed since the
+  start of the program. The second field is the thread outputting the
+  log statement. The third field is the level, the fourth field is
+  the category to which the statement belongs.
+
+  <p>The fifth field (just before the '-') is the nested diagnostic
+  context.  Note the nested diagnostic context may be empty as in the
+  first two statements. The text after the '-' is the message of the
+  statement.
+
+  <p><b>WARNING</b> Do not use the same TTCCLayout instance from
+  within different appenders. The TTCCLayout is not thread safe when
+  used in his way. However, it is perfectly safe to use a TTCCLayout
+  instance from just one appender.
+
+  <p>{@link PatternLayout} offers a much more flexible alternative.
+
+  @author Ceki G&uuml;lc&uuml;
+  @author <A HREF="mailto:heinz.richter@ecmwf.int">Heinz Richter</a>
+
+*/
+public class TTCCLayout extends DateLayout {
+
+  // Internal representation of options
+  private boolean threadPrinting    = true;
+  private boolean categoryPrefixing = true;
+  private boolean contextPrinting   = true;
+
+
+  protected final StringBuffer buf = new StringBuffer(256);
+
+
+  /**
+     Instantiate a TTCCLayout object with {@link
+     org.apache.log4j.helpers.RelativeTimeDateFormat} as the date
+     formatter in the local time zone.
+
+     @since 0.7.5 */
+  public TTCCLayout() {
+    this.setDateFormat(RELATIVE_TIME_DATE_FORMAT, null);
+  }
+
+
+  /**
+     Instantiate a TTCCLayout object using the local time zone. The
+     DateFormat used will depend on the <code>dateFormatType</code>.
+
+     <p>This constructor just calls the {@link
+     DateLayout#setDateFormat} method.
+
+     */
+  public TTCCLayout(String dateFormatType) {
+    this.setDateFormat(dateFormatType);
+  }
+
+
+  /**
+     The <b>ThreadPrinting</b> option specifies whether the name of the
+     current thread is part of log output or not. This is true by default.
+   */
+  public
+  void setThreadPrinting(boolean threadPrinting) {
+    this.threadPrinting = threadPrinting;
+  }
+
+  /**
+     Returns value of the <b>ThreadPrinting</b> option.
+   */
+  public
+  boolean getThreadPrinting() {
+    return threadPrinting;
+  }
+
+  /**
+     The <b>CategoryPrefixing</b> option specifies whether {@link Category}
+     name is part of log output or not. This is true by default.
+   */
+  public
+  void setCategoryPrefixing(boolean categoryPrefixing) {
+    this.categoryPrefixing = categoryPrefixing;
+  }
+
+  /**
+     Returns value of the <b>CategoryPrefixing</b> option.
+   */
+  public
+  boolean getCategoryPrefixing() {
+    return categoryPrefixing;
+  }
+
+  /**
+     The <b>ContextPrinting</b> option specifies log output will include
+     the nested context information belonging to the current thread.
+     This is true by default.
+   */
+  public
+  void setContextPrinting(boolean contextPrinting) {
+    this.contextPrinting = contextPrinting;
+  }
+
+  /**
+     Returns value of the <b>ContextPrinting</b> option.
+   */
+  public
+  boolean getContextPrinting() {
+    return contextPrinting;
+  }
+
+  /**
+   In addition to the level of the statement and message, the
+   returned byte array includes time, thread, category and {@link NDC}
+   information.
+
+   <p>Time, thread, category and diagnostic context are printed
+   depending on options.
+
+    @param event The event to format
+
+  */
+  public
+  String format(LoggingEvent event) {
+
+    // Reset buf
+    buf.setLength(0);
+
+    dateFormat(buf, event);
+
+    if(this.threadPrinting) {
+      buf.append('[');
+      buf.append(event.getThreadName());
+      buf.append("] ");
+    }
+    buf.append(event.getLevel().toString());
+    buf.append(' ');
+
+    if(this.categoryPrefixing) {
+      buf.append(event.getLoggerName());
+      buf.append(' ');
+    }
+
+    if(this.contextPrinting) {
+       String ndc = event.getNDC();
+
+      if(ndc != null) {
+       buf.append(ndc);
+       buf.append(' ');
+      }
+    }
+    buf.append("- ");
+    buf.append(event.getRenderedMessage());
+    buf.append(LINE_SEP);
+    return buf.toString();
+  }
+
+ /**
+     The TTCCLayout does not handle the throwable contained within
+     {@link LoggingEvent LoggingEvents}. Thus, it returns
+     <code>true</code>.
+
+     @since version 0.8.4 */
+  public
+  boolean ignoresThrowable() {
+    return true;
+  }
+}
diff --git a/srcjar/org/apache/log4j/WriterAppender.java b/srcjar/org/apache/log4j/WriterAppender.java
new file mode 100644 (file)
index 0000000..1c93cc2
--- /dev/null
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.QuietWriter;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.LoggingEvent;
+
+// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
+//              Ben Sandee
+
+/**
+   WriterAppender appends log events to a {@link java.io.Writer} or an
+   {@link java.io.OutputStream} depending on the user's choice.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.1 */
+public class WriterAppender extends AppenderSkeleton {
+
+
+  /**
+     Immediate flush means that the underlying writer or output stream
+     will be flushed at the end of each append operation unless shouldFlush()
+     is overridden. Immediate
+     flush is slower but ensures that each append request is actually
+     written. If <code>immediateFlush</code> is set to
+     <code>false</code>, then there is a good chance that the last few
+     logs events are not actually written to persistent media if and
+     when the application crashes.
+
+     <p>The <code>immediateFlush</code> variable is set to
+     <code>true</code> by default.
+
+  */
+  protected boolean immediateFlush = true;
+
+  /**
+     The encoding to use when writing.  <p>The
+     <code>encoding</code> variable is set to <code>null</null> by
+     default which results in the utilization of the system's default
+     encoding.  */
+  protected String encoding;
+
+  /**
+     This is the {@link QuietWriter quietWriter} where we will write
+     to.
+  */
+  protected QuietWriter qw;
+
+
+  /**
+     This default constructor does nothing.  */
+  public
+  WriterAppender() {
+  }
+
+  /**
+     Instantiate a WriterAppender and set the output destination to a
+     new {@link OutputStreamWriter} initialized with <code>os</code>
+     as its {@link OutputStream}.  */
+  public
+  WriterAppender(Layout layout, OutputStream os) {
+    this(layout, new OutputStreamWriter(os));
+  }
+
+  /**
+     Instantiate a WriterAppender and set the output destination to
+     <code>writer</code>.
+
+     <p>The <code>writer</code> must have been previously opened by
+     the user.  */
+  public
+  WriterAppender(Layout layout, Writer writer) {
+    this.layout = layout;
+    this.setWriter(writer);
+  }
+
+  /**
+     If the <b>ImmediateFlush</b> option is set to
+     <code>true</code>, the appender will flush at the end of each
+     write. This is the default behavior. If the option is set to
+     <code>false</code>, then the underlying stream can defer writing
+     to physical medium to a later time.
+
+     <p>Avoiding the flush operation at the end of each append results in
+     a performance gain of 10 to 20 percent. However, there is safety
+     tradeoff involved in skipping flushing. Indeed, when flushing is
+     skipped, then it is likely that the last few log events will not
+     be recorded on disk when the application exits. This is a high
+     price to pay even for a 20% performance gain.
+   */
+  public
+  void setImmediateFlush(boolean value) {
+    immediateFlush = value;
+  }
+
+  /**
+     Returns value of the <b>ImmediateFlush</b> option.
+   */
+  public
+  boolean getImmediateFlush() {
+    return immediateFlush;
+  }
+
+  /**
+     Does nothing.
+  */
+  public
+  void activateOptions() {
+  }
+
+
+  /**
+     This method is called by the {@link AppenderSkeleton#doAppend}
+     method.
+
+     <p>If the output stream exists and is writable then write a log
+     statement to the output stream. Otherwise, write a single warning
+     message to <code>System.err</code>.
+
+     <p>The format of the output will depend on this appender's
+     layout.
+
+  */
+  public
+  void append(LoggingEvent event) {
+
+    // Reminder: the nesting of calls is:
+    //
+    //    doAppend()
+    //      - check threshold
+    //      - filter
+    //      - append();
+    //        - checkEntryConditions();
+    //        - subAppend();
+
+    if(!checkEntryConditions()) {
+      return;
+    }
+    subAppend(event);
+   }
+
+  /**
+     This method determines if there is a sense in attempting to append.
+
+     <p>It checks whether there is a set output target and also if
+     there is a set layout. If these checks fail, then the boolean
+     value <code>false</code> is returned. */
+  protected
+  boolean checkEntryConditions() {
+    if(this.closed) {
+      LogLog.warn("Not allowed to write to a closed appender.");
+      return false;
+    }
+
+    if(this.qw == null) {
+      errorHandler.error("No output stream or file set for the appender named ["+
+                       name+"].");
+      return false;
+    }
+
+    if(this.layout == null) {
+      errorHandler.error("No layout set for the appender named ["+ name+"].");
+      return false;
+    }
+    return true;
+  }
+
+
+  /**
+     Close this appender instance. The underlying stream or writer is
+     also closed.
+
+     <p>Closed appenders cannot be reused.
+
+     @see #setWriter
+     @since 0.8.4 */
+  public
+  synchronized
+  void close() {
+    if(this.closed) {
+        return;
+    }
+    this.closed = true;
+    writeFooter();
+    reset();
+  }
+
+  /**
+   * Close the underlying {@link java.io.Writer}.
+   * */
+  protected void closeWriter() {
+    if(qw != null) {
+      try {
+       qw.close();
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+       // There is do need to invoke an error handler at this late
+       // stage.
+       LogLog.error("Could not close " + qw, e);
+      }
+    }
+  }
+
+  /**
+     Returns an OutputStreamWriter when passed an OutputStream.  The
+     encoding used will depend on the value of the
+     <code>encoding</code> property.  If the encoding value is
+     specified incorrectly the writer will be opened using the default
+     system encoding (an error message will be printed to the loglog.  */
+  protected
+  OutputStreamWriter createWriter(OutputStream os) {
+    OutputStreamWriter retval = null;
+
+    String enc = getEncoding();
+    if(enc != null) {
+      try {
+       retval = new OutputStreamWriter(os, enc);
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             LogLog.warn("Error initializing output writer.");
+             LogLog.warn("Unsupported encoding?");
+      }
+    }
+    if(retval == null) {
+      retval = new OutputStreamWriter(os);
+    }
+    return retval;
+  }
+
+  public String getEncoding() {
+    return encoding;
+  }
+
+  public void setEncoding(String value) {
+    encoding = value;
+  }
+
+
+
+
+  /**
+     Set the {@link ErrorHandler} for this WriterAppender and also the
+     underlying {@link QuietWriter} if any. */
+  public synchronized void setErrorHandler(ErrorHandler eh) {
+    if(eh == null) {
+      LogLog.warn("You have tried to set a null error-handler.");
+    } else {
+      this.errorHandler = eh;
+      if(this.qw != null) {
+       this.qw.setErrorHandler(eh);
+      }
+    }
+  }
+
+  /**
+    <p>Sets the Writer where the log output will go. The
+    specified Writer must be opened by the user and be
+    writable.
+
+    <p>The <code>java.io.Writer</code> will be closed when the
+    appender instance is closed.
+
+
+    <p><b>WARNING:</b> Logging to an unopened Writer will fail.
+    <p>
+    @param writer An already opened Writer.  */
+  public synchronized void setWriter(Writer writer) {
+    reset();
+    this.qw = new QuietWriter(writer, errorHandler);
+    //this.tp = new TracerPrintWriter(qw);
+    writeHeader();
+  }
+
+
+  /**
+     Actual writing occurs here.
+
+     <p>Most subclasses of <code>WriterAppender</code> will need to
+     override this method.
+
+     @since 0.9.0 */
+  protected
+  void subAppend(LoggingEvent event) {
+    this.qw.write(this.layout.format(event));
+
+    if(layout.ignoresThrowable()) {
+      String[] s = event.getThrowableStrRep();
+      if (s != null) {
+       int len = s.length;
+       for(int i = 0; i < len; i++) {
+         this.qw.write(s[i]);
+         this.qw.write(Layout.LINE_SEP);
+       }
+      }
+    }
+
+    if(shouldFlush(event)) {
+      this.qw.flush();
+    }
+  }
+
+
+
+  /**
+     The WriterAppender requires a layout. Hence, this method returns
+     <code>true</code>.
+  */
+  public
+  boolean requiresLayout() {
+    return true;
+  }
+
+  /**
+     Clear internal references to the writer and other variables.
+
+     Subclasses can override this method for an alternate closing
+     behavior.  */
+  protected
+  void reset() {
+    closeWriter();
+    this.qw = null;
+    //this.tp = null;
+  }
+
+
+  /**
+     Write a footer as produced by the embedded layout's {@link
+     Layout#getFooter} method.  */
+  protected
+  void writeFooter() {
+    if(layout != null) {
+      String f = layout.getFooter();
+      if(f != null && this.qw != null) {
+       this.qw.write(f);
+       this.qw.flush();
+      }
+    }
+  }
+
+  /**
+     Write a header as produced by the embedded layout's {@link
+     Layout#getHeader} method.  */
+  protected
+  void writeHeader() {
+    if(layout != null) {
+      String h = layout.getHeader();
+      if(h != null && this.qw != null) {
+        this.qw.write(h);
+    }
+    }
+  }
+  
+  /**
+   * Determines whether the writer should be flushed after
+   * this event is written.
+   * 
+   * @since 1.2.16
+   */
+  protected boolean shouldFlush(final LoggingEvent event) {
+     return immediateFlush;
+  }
+}
diff --git a/srcjar/org/apache/log4j/config/PropertyGetter.java b/srcjar/org/apache/log4j/config/PropertyGetter.java
new file mode 100644 (file)
index 0000000..4805376
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.config;
+
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.LogLog;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+/**
+   Used for inferring configuration information for a log4j's component.
+
+   @author  Anders Kristensen
+ */
+public class PropertyGetter {
+  protected static final Object[] NULL_ARG = new Object[] {};
+  protected Object obj;
+  protected PropertyDescriptor[] props;
+
+  public interface PropertyCallback {
+    void foundProperty(Object obj, String prefix, String name, Object value);
+  }
+
+  /**
+    Create a new PropertyGetter for the specified Object. This is done
+    in prepartion for invoking {@link
+    #getProperties(PropertyGetter.PropertyCallback, String)} one or
+    more times.
+
+    @param obj the object for which to set properties */
+  public
+  PropertyGetter(Object obj) throws IntrospectionException {
+    BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
+    props = bi.getPropertyDescriptors();
+    this.obj = obj;
+  }
+
+  public
+  static
+  void getProperties(Object obj, PropertyCallback callback, String prefix) {
+    try {
+      new PropertyGetter(obj).getProperties(callback, prefix);
+    } catch (IntrospectionException ex) {
+      LogLog.error("Failed to introspect object " + obj, ex);
+    }
+  }
+
+  public
+  void getProperties(PropertyCallback callback, String prefix) {
+    for (int i = 0; i < props.length; i++) {
+      Method getter = props[i].getReadMethod();
+      if (getter == null) {
+        continue;
+    }
+      if (!isHandledType(getter.getReturnType())) {
+       //System.err.println("Ignoring " + props[i].getName() +" " + getter.getReturnType());
+       continue;
+      }
+      String name = props[i].getName();
+      try {
+       Object result = getter.invoke(obj, NULL_ARG);
+       //System.err.println("PROP " + name +": " + result);
+       if (result != null) {
+         callback.foundProperty(obj, prefix, name, result);
+       }
+      } catch (IllegalAccessException ex) {
+           LogLog.warn("Failed to get value of property " + name);
+      } catch (InvocationTargetException ex) {
+        if (ex.getTargetException() instanceof InterruptedException
+                || ex.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+        LogLog.warn("Failed to get value of property " + name);
+      } catch (RuntimeException ex) {
+           LogLog.warn("Failed to get value of property " + name);
+      }
+    }
+  }
+
+  protected
+  boolean isHandledType(Class type) {
+    return String.class.isAssignableFrom(type) ||
+      Integer.TYPE.isAssignableFrom(type) ||
+      Long.TYPE.isAssignableFrom(type)    ||
+      Boolean.TYPE.isAssignableFrom(type) ||
+      Priority.class.isAssignableFrom(type);
+  }
+}
diff --git a/srcjar/org/apache/log4j/config/PropertyPrinter.java b/srcjar/org/apache/log4j/config/PropertyPrinter.java
new file mode 100644 (file)
index 0000000..9a62d53
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.config;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+   Prints the configuration of the log4j default hierarchy
+   (which needs to be auto-initialized) as a propoperties file
+   on a {@link PrintWriter}.
+   
+   @author  Anders Kristensen
+ */
+public class PropertyPrinter implements PropertyGetter.PropertyCallback {
+  protected int numAppenders = 0;
+  protected Hashtable appenderNames = new Hashtable();
+  protected Hashtable layoutNames   = new Hashtable();
+  protected PrintWriter out;
+  protected boolean doCapitalize;
+  
+  public
+  PropertyPrinter(PrintWriter out) {
+    this(out, false);
+  }
+  
+  public
+  PropertyPrinter(PrintWriter out, boolean doCapitalize) {
+    this.out = out;
+    this.doCapitalize = doCapitalize;
+    
+    print(out);
+    out.flush();
+  }
+  
+  protected
+  String genAppName() {
+    return "A" + numAppenders++;
+  }
+  
+  /**
+   * Returns true if the specified appender name is considered to have
+   * been generated, that is, if it is of the form A[0-9]+.
+  */
+  protected
+  boolean isGenAppName(String name) {
+    if (name.length() < 2 || name.charAt(0) != 'A') {
+        return false;
+    }
+    
+    for (int i = 0; i < name.length(); i++) {
+      if (name.charAt(i) < '0' || name.charAt(i) > '9') {
+        return false;
+    }
+    }
+    return true;
+  }
+  
+  /**
+   * Prints the configuration of the default log4j hierarchy as a Java
+   * properties file on the specified Writer.
+   * 
+   * <p>N.B. print() can be invoked only once!
+   */
+  public
+  void print(PrintWriter out) {
+    printOptions(out, Logger.getRootLogger());
+    
+    Enumeration cats = LogManager.getCurrentLoggers();
+    while (cats.hasMoreElements()) {
+      printOptions(out, (Logger) cats.nextElement());
+    }
+  }
+  
+  /**
+   * @since 1.2.15
+   */
+  protected
+  void printOptions(PrintWriter out, Category cat) {
+    Enumeration appenders = cat.getAllAppenders();
+    Level prio = cat.getLevel();
+    String appenderString = (prio == null ? "" : prio.toString());
+    
+    while (appenders.hasMoreElements()) {
+      Appender app = (Appender) appenders.nextElement();
+      String name;
+      
+      if ((name = (String) appenderNames.get(app)) == null) {
+      
+        // first assign name to the appender
+        if ((name = app.getName()) == null || isGenAppName(name)) {
+            name = genAppName();
+        }
+        appenderNames.put(app, name);
+        
+        printOptions(out, app, "log4j.appender."+name);
+        if (app.getLayout() != null) {
+          printOptions(out, app.getLayout(), "log4j.appender."+name+".layout");
+        }
+      }
+      appenderString += ", " + name;
+    }
+    String catKey = (cat == Logger.getRootLogger())
+        ? "log4j.rootLogger"
+        : "log4j.logger." + cat.getName();
+    if (appenderString != "") {
+      out.println(catKey + "=" + appenderString);
+    }
+    if (!cat.getAdditivity() && cat != Logger.getRootLogger()) {
+       out.println("log4j.additivity." + cat.getName() + "=false");    
+    }
+  }
+
+  protected void printOptions(PrintWriter out, Logger cat) {
+      printOptions(out, (Category) cat);
+  }
+  
+  protected
+  void printOptions(PrintWriter out, Object obj, String fullname) {
+    out.println(fullname + "=" + obj.getClass().getName());
+    PropertyGetter.getProperties(obj, this, fullname + ".");
+  }
+  
+  public void foundProperty(Object obj, String prefix, String name, Object value) {
+    // XXX: Properties encode value.toString()
+    if (obj instanceof Appender && "name".equals(name)) {
+      return;
+    }
+    if (doCapitalize) {
+      name = capitalize(name);
+    }
+    out.println(prefix + name + "=" + value.toString());
+  }
+  
+  public static String capitalize(String name) {
+    if (Character.isLowerCase(name.charAt(0))) {
+      if (name.length() == 1 || Character.isLowerCase(name.charAt(1))) {
+        StringBuffer newname = new StringBuffer(name);
+        newname.setCharAt(0, Character.toUpperCase(name.charAt(0)));
+        return newname.toString();
+      }
+    }
+    return name;
+  }
+  
+  // for testing
+  public static void main(String[] args) {
+    new PropertyPrinter(new PrintWriter(System.out));
+  }
+}
diff --git a/srcjar/org/apache/log4j/config/PropertySetter.java b/srcjar/org/apache/log4j/config/PropertySetter.java
new file mode 100644 (file)
index 0000000..dae3c00
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:  Georg Lundesgaard
+
+package org.apache.log4j.config;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.ErrorHandler;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+   General purpose Object property setter. Clients repeatedly invokes
+   {@link #setProperty setProperty(name,value)} in order to invoke setters
+   on the Object specified in the constructor. This class relies on the
+   JavaBeans {@link Introspector} to analyze the given Object Class using
+   reflection.
+   
+   <p>Usage:
+   <pre>
+     PropertySetter ps = new PropertySetter(anObject);
+     ps.set("name", "Joe");
+     ps.set("age", "32");
+     ps.set("isMale", "true");
+   </pre>
+   will cause the invocations anObject.setName("Joe"), anObject.setAge(32),
+   and setMale(true) if such methods exist with those signatures.
+   Otherwise an {@link IntrospectionException} are thrown.
+  
+   @author Anders Kristensen
+   @since 1.1
+ */
+public class PropertySetter {
+  protected Object obj;
+  protected PropertyDescriptor[] props;
+  
+  /**
+    Create a new PropertySetter for the specified Object. This is done
+    in prepartion for invoking {@link #setProperty} one or more times.
+    
+    @param obj  the object for which to set properties
+   */
+  public
+  PropertySetter(Object obj) {
+    this.obj = obj;
+  }
+  
+  /**
+     Uses JavaBeans {@link Introspector} to computer setters of object to be
+     configured.
+   */
+  protected
+  void introspect() {
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
+      props = bi.getPropertyDescriptors();
+    } catch (IntrospectionException ex) {
+      LogLog.error("Failed to introspect "+obj+": " + ex.getMessage());
+      props = new PropertyDescriptor[0];
+    }
+  }
+  
+
+  /**
+     Set the properties of an object passed as a parameter in one
+     go. The <code>properties</code> are parsed relative to a
+     <code>prefix</code>.
+
+     @param obj The object to configure.
+     @param properties A java.util.Properties containing keys and values.
+     @param prefix Only keys having the specified prefix will be set.
+  */
+  public
+  static
+  void setProperties(Object obj, Properties properties, String prefix) {
+    new PropertySetter(obj).setProperties(properties, prefix);
+  }
+  
+
+  /**
+     Set the properites for the object that match the
+     <code>prefix</code> passed as parameter.
+
+     
+   */
+  public
+  void setProperties(Properties properties, String prefix) {
+    int len = prefix.length();
+    
+    for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) {
+      String key = (String) e.nextElement();
+      
+      // handle only properties that start with the desired frefix.
+      if (key.startsWith(prefix)) {
+
+       
+       // ignore key if it contains dots after the prefix
+        if (key.indexOf('.', len + 1) > 0) {
+         //System.err.println("----------Ignoring---["+key
+         //         +"], prefix=["+prefix+"].");
+         continue;
+       }
+        
+       String value = OptionConverter.findAndSubst(key, properties);
+        key = key.substring(len);
+        if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
+          continue;
+        }
+        //
+        //   if the property type is an OptionHandler
+        //     (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
+        PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
+        if (prop != null
+                && OptionHandler.class.isAssignableFrom(prop.getPropertyType())
+                && prop.getWriteMethod() != null) {
+            OptionHandler opt = (OptionHandler)
+                    OptionConverter.instantiateByKey(properties, prefix + key,
+                                  prop.getPropertyType(),
+                                  null);
+            PropertySetter setter = new PropertySetter(opt);
+            setter.setProperties(properties, prefix + key + ".");
+            try {
+                prop.getWriteMethod().invoke(this.obj, new Object[] { opt });
+            } catch(IllegalAccessException ex) {
+                LogLog.warn("Failed to set property [" + key +
+                            "] to value \"" + value + "\". ", ex);
+            } catch(InvocationTargetException ex) {
+                if (ex.getTargetException() instanceof InterruptedException
+                        || ex.getTargetException() instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                LogLog.warn("Failed to set property [" + key +
+                            "] to value \"" + value + "\". ", ex);
+            } catch(RuntimeException ex) {
+                LogLog.warn("Failed to set property [" + key +
+                            "] to value \"" + value + "\". ", ex);
+            }
+            continue;
+        }
+
+        setProperty(key, value);
+      }
+    }
+    activate();
+  }
+  
+  /**
+     Set a property on this PropertySetter's Object. If successful, this
+     method will invoke a setter method on the underlying Object. The
+     setter is the one for the specified property name and the value is
+     determined partly from the setter argument type and partly from the
+     value specified in the call to this method.
+     
+     <p>If the setter expects a String no conversion is necessary.
+     If it expects an int, then an attempt is made to convert 'value'
+     to an int using new Integer(value). If the setter expects a boolean,
+     the conversion is by new Boolean(value).
+     
+     @param name    name of the property
+     @param value   String value of the property
+   */
+  public
+  void setProperty(String name, String value) {
+    if (value == null) {
+        return;
+    }
+    
+    name = Introspector.decapitalize(name);
+    PropertyDescriptor prop = getPropertyDescriptor(name);
+    
+    //LogLog.debug("---------Key: "+name+", type="+prop.getPropertyType());
+
+    if (prop == null) {
+      LogLog.warn("No such property [" + name + "] in "+
+                 obj.getClass().getName()+"." );
+    } else {
+      try {
+        setProperty(prop, name, value);
+      } catch (PropertySetterException ex) {
+        LogLog.warn("Failed to set property [" + name +
+                    "] to value \"" + value + "\". ", ex.rootCause);
+      }
+    }
+  }
+  
+  /** 
+      Set the named property given a {@link PropertyDescriptor}.
+
+      @param prop A PropertyDescriptor describing the characteristics
+      of the property to set.
+      @param name The named of the property to set.
+      @param value The value of the property.      
+   */
+  public
+  void setProperty(PropertyDescriptor prop, String name, String value)
+    throws PropertySetterException {
+    Method setter = prop.getWriteMethod();
+    if (setter == null) {
+      throw new PropertySetterException("No setter for property ["+name+"].");
+    }
+    Class[] paramTypes = setter.getParameterTypes();
+    if (paramTypes.length != 1) {
+      throw new PropertySetterException("#params for setter != 1");
+    }
+    
+    Object arg;
+    try {
+      arg = convertArg(value, paramTypes[0]);
+    } catch (Throwable t) {
+      throw new PropertySetterException("Conversion to type ["+paramTypes[0]+
+                                       "] failed. Reason: "+t);
+    }
+    if (arg == null) {
+      throw new PropertySetterException(
+          "Conversion to type ["+paramTypes[0]+"] failed.");
+    }
+    LogLog.debug("Setting property [" + name + "] to [" +arg+"].");
+    try {
+      setter.invoke(obj, new Object[]  { arg });
+    } catch (IllegalAccessException ex) {
+      throw new PropertySetterException(ex);
+    } catch (InvocationTargetException ex) {
+        if (ex.getTargetException() instanceof InterruptedException
+                || ex.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }        
+        throw new PropertySetterException(ex);
+    } catch (RuntimeException ex) {
+      throw new PropertySetterException(ex);
+    }
+  }
+  
+
+  /**
+     Convert <code>val</code> a String parameter to an object of a
+     given type.
+  */
+  protected
+  Object convertArg(String val, Class type) {
+    if(val == null) {
+        return null;
+    }
+
+    String v = val.trim();
+    if (String.class.isAssignableFrom(type)) {
+      return val;
+    } else if (Integer.TYPE.isAssignableFrom(type)) {
+      return new Integer(v);
+    } else if (Long.TYPE.isAssignableFrom(type)) {
+      return new Long(v);
+    } else if (Boolean.TYPE.isAssignableFrom(type)) {
+      if ("true".equalsIgnoreCase(v)) {
+        return Boolean.TRUE;
+      } else if ("false".equalsIgnoreCase(v)) {
+        return Boolean.FALSE;
+      }
+    } else if (Priority.class.isAssignableFrom(type)) {
+      return OptionConverter.toLevel(v, Level.DEBUG);
+    } else if (ErrorHandler.class.isAssignableFrom(type)) {
+      return OptionConverter.instantiateByClassName(v, 
+         ErrorHandler.class, null);
+    }
+    return null;
+  }
+  
+  
+  protected
+  PropertyDescriptor getPropertyDescriptor(String name) {
+    if (props == null) {
+        introspect();
+    }
+    
+    for (int i = 0; i < props.length; i++) {
+      if (name.equals(props[i].getName())) {
+       return props[i];
+      }
+    }
+    return null;
+  }
+  
+  public
+  void activate() {
+    if (obj instanceof OptionHandler) {
+      ((OptionHandler) obj).activateOptions();
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/config/PropertySetterException.java b/srcjar/org/apache/log4j/config/PropertySetterException.java
new file mode 100644 (file)
index 0000000..c6314cc
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.config;
+
+/**
+ * Thrown when an error is encountered whilst attempting to set a property
+ * using the {@link PropertySetter} utility class.
+ * 
+ * @author Anders Kristensen
+ * @since 1.1
+ */
+public class PropertySetterException extends Exception {
+  private static final long serialVersionUID = -1352613734254235861L;
+  protected Throwable rootCause;
+  
+  public
+  PropertySetterException(String msg) {
+    super(msg);
+  }
+  
+  public
+  PropertySetterException(Throwable rootCause)
+  {
+    super();
+    this.rootCause = rootCause;
+  }
+  
+  /**
+     Returns descriptive text on the cause of this exception.
+   */
+  public
+  String getMessage() {
+    String msg = super.getMessage();
+    if (msg == null && rootCause != null) {
+      msg = rootCause.getMessage();
+    }
+    return msg;
+  }
+}
diff --git a/srcjar/org/apache/log4j/config/package.html b/srcjar/org/apache/log4j/config/package.html
new file mode 100644 (file)
index 0000000..973dce4
--- /dev/null
@@ -0,0 +1,23 @@
+<html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<body>
+Package used in getting/setting component properties.
+</body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java b/srcjar/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java
new file mode 100644 (file)
index 0000000..98472ee
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Date;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.DateFormat;
+
+
+/**
+   Formats a {@link Date} in the format "HH:mm:ss,SSS" for example,
+   "15:49:37,459".
+   
+   @author Ceki G&uuml;lc&uuml;
+   @author Andrew Vajoczki    
+
+   @since 0.7.5
+*/
+public class AbsoluteTimeDateFormat extends DateFormat {
+   private static final long serialVersionUID = -388856345976723342L;
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.AbsoluteTimeDateFormat} in layouts. Current
+     value is <b>ABSOLUTE</b>.  */
+  public final static String ABS_TIME_DATE_FORMAT = "ABSOLUTE";
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.DateTimeDateFormat} in layouts.  Current
+     value is <b>DATE</b>.
+  */
+  public final static String DATE_AND_TIME_DATE_FORMAT = "DATE";
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.ISO8601DateFormat} in layouts. Current
+     value is <b>ISO8601</b>.
+  */
+  public final static String ISO8601_DATE_FORMAT = "ISO8601";
+
+  public
+  AbsoluteTimeDateFormat() {
+    setCalendar(Calendar.getInstance());
+  }
+  
+  public
+  AbsoluteTimeDateFormat(TimeZone timeZone) {
+    setCalendar(Calendar.getInstance(timeZone));
+  }
+
+  private static long   previousTime;
+  private static char[] previousTimeWithoutMillis = new char[9]; // "HH:mm:ss."
+
+  /**
+     Appends to <code>sbuf</code> the time in the format
+     "HH:mm:ss,SSS" for example, "15:49:37,459"
+
+     @param date the date to format
+     @param sbuf the string buffer to write to
+     @param fieldPosition remains untouched
+    */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    long now = date.getTime();
+    int millis = (int)(now % 1000);
+
+    if ((now - millis) != previousTime || previousTimeWithoutMillis[0] == 0) {
+      // We reach this point at most once per second
+      // across all threads instead of each time format()
+      // is called. This saves considerable CPU time.
+
+      calendar.setTime(date);
+
+      int start = sbuf.length();
+      
+      int hour = calendar.get(Calendar.HOUR_OF_DAY);
+      if(hour < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(hour);
+      sbuf.append(':');
+      
+      int mins = calendar.get(Calendar.MINUTE);
+      if(mins < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(mins);
+      sbuf.append(':');
+      
+      int secs = calendar.get(Calendar.SECOND);
+      if(secs < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(secs);
+      sbuf.append(',');      
+
+      // store the time string for next time to avoid recomputation
+      sbuf.getChars(start, sbuf.length(), previousTimeWithoutMillis, 0);
+      
+      previousTime = now - millis;
+    }
+    else {
+      sbuf.append(previousTimeWithoutMillis);
+    }
+    
+
+    
+    if(millis < 100) {
+        sbuf.append('0');
+    }
+    if(millis < 10) {
+        sbuf.append('0');
+    }
+    
+    sbuf.append(millis);
+    return sbuf;
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(String s, ParsePosition pos) {
+    return null;
+  }  
+}
diff --git a/srcjar/org/apache/log4j/helpers/AppenderAttachableImpl.java b/srcjar/org/apache/log4j/helpers/AppenderAttachableImpl.java
new file mode 100644 (file)
index 0000000..4aae5c1
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+
+import org.apache.log4j.Appender;
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+   A straightforward implementation of the {@link AppenderAttachable}
+   interface.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.9.1 */
+public class AppenderAttachableImpl implements AppenderAttachable {
+  
+  /** Array of appenders. */
+  protected Vector  appenderList;
+
+  /**
+     Attach an appender. If the appender is already in the list in
+     won't be added again.
+  */
+  public
+  void addAppender(Appender newAppender) {
+    // Null values for newAppender parameter are strictly forbidden.
+    if(newAppender == null) {
+        return;
+    }
+    
+    if(appenderList == null) {
+      appenderList = new Vector(1);
+    }
+    if(!appenderList.contains(newAppender)) {
+        appenderList.addElement(newAppender);
+    }
+  }
+
+  /**
+     Call the <code>doAppend</code> method on all attached appenders.  */
+  public
+  int appendLoopOnAppenders(LoggingEvent event) {
+    int size = 0;
+    Appender appender;
+
+    if(appenderList != null) {
+      size = appenderList.size();
+      for(int i = 0; i < size; i++) {
+       appender = (Appender) appenderList.elementAt(i);
+       appender.doAppend(event);
+      }
+    }    
+    return size;
+  }
+
+
+  /**
+     Get all attached appenders as an Enumeration. If there are no
+     attached appenders <code>null</code> is returned.
+     
+     @return Enumeration An enumeration of attached appenders.
+   */
+  public
+  Enumeration getAllAppenders() {
+    if(appenderList == null) {
+        return null;
+    } else {
+        return appenderList.elements();
+    }    
+  }
+
+  /**
+     Look for an attached appender named as <code>name</code>.
+
+     <p>Return the appender with that name if in the list. Return null
+     otherwise.  
+     
+   */
+  public
+  Appender getAppender(String name) {
+     if(appenderList == null || name == null) {
+        return null;
+    }
+
+     int size = appenderList.size();
+     Appender appender;
+     for(int i = 0; i < size; i++) {
+       appender = (Appender) appenderList.elementAt(i);
+       if(name.equals(appender.getName())) {
+        return appender;
+    }
+     }
+     return null;    
+  }
+
+
+  /**
+     Returns <code>true</code> if the specified appender is in the
+     list of attached appenders, <code>false</code> otherwise.
+
+     @since 1.2 */
+  public 
+  boolean isAttached(Appender appender) {
+    if(appenderList == null || appender == null) {
+        return false;
+    }
+
+     int size = appenderList.size();
+     Appender a;
+     for(int i = 0; i < size; i++) {
+       a  = (Appender) appenderList.elementAt(i);
+       if(a == appender) {
+        return true;
+    }
+     }
+     return false;    
+  }
+
+
+
+  /**
+   * Remove and close all previously attached appenders.
+   * */
+  public
+  void removeAllAppenders() {
+    if(appenderList != null) {
+      int len = appenderList.size();      
+      for(int i = 0; i < len; i++) {
+       Appender a = (Appender) appenderList.elementAt(i);
+       a.close();
+      }
+      appenderList.removeAllElements();
+      appenderList = null;      
+    }
+  }
+
+
+  /**
+     Remove the appender passed as parameter form the list of attached
+     appenders.  */
+  public
+  void removeAppender(Appender appender) {
+    if(appender == null || appenderList == null) {
+        return;
+    }
+    appenderList.removeElement(appender);    
+  }
+
+
+ /**
+    Remove the appender with the name passed as parameter form the
+    list of appenders.  
+  */
+  public
+  void removeAppender(String name) {
+    if(name == null || appenderList == null) {
+        return;
+    }
+    int size = appenderList.size();
+    for(int i = 0; i < size; i++) {
+      if(name.equals(((Appender)appenderList.elementAt(i)).getName())) {
+        appenderList.removeElementAt(i);
+        break;
+      }
+    }
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/helpers/BoundedFIFO.java b/srcjar/org/apache/log4j/helpers/BoundedFIFO.java
new file mode 100644 (file)
index 0000000..d53a771
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:     Mathias Bogaert
+//                   joelr@viair.com
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   <code>BoundedFIFO</code> serves as the bounded first-in-first-out
+   buffer heavily used by the {@link org.apache.log4j.AsyncAppender}.
+   
+   @author Ceki G&uuml;lc&uuml; 
+   @since version 0.9.1 */
+public class BoundedFIFO {
+  
+  LoggingEvent[] buf;
+  int numElements = 0;
+  int first = 0;
+  int next = 0;
+  int maxSize;
+
+  /**
+     Instantiate a new BoundedFIFO with a maximum size passed as argument.
+   */
+  public
+  BoundedFIFO(int maxSize) {
+   if(maxSize < 1) {
+      throw new IllegalArgumentException("The maxSize argument ("+maxSize+
+                           ") is not a positive integer.");
+    }
+    this.maxSize = maxSize;
+    buf = new LoggingEvent[maxSize];
+  }
+  
+  /**
+     Get the first element in the buffer. Returns <code>null</code> if
+     there are no elements in the buffer.  */
+  public
+  LoggingEvent get() {
+    if(numElements == 0) {
+        return null;
+    }
+    
+    LoggingEvent r = buf[first];
+    buf[first] = null; // help garbage collection
+
+    if(++first == maxSize) {
+       first = 0;
+    }
+    numElements--;    
+    return r;    
+  }
+
+  /**
+     Place a {@link LoggingEvent} in the buffer. If the buffer is full
+     then the event is <b>silently dropped</b>. It is the caller's
+     responsability to make sure that the buffer has free space.  */
+  public 
+  void put(LoggingEvent o) {
+    if(numElements != maxSize) {      
+      buf[next] = o;    
+      if(++next == maxSize) {
+       next = 0;
+      }
+      numElements++;
+    }
+  }
+
+  /**
+     Get the maximum size of the buffer.
+   */
+  public 
+  int getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+     Return <code>true</code> if the buffer is full, that is, whether
+     the number of elements in the buffer equals the buffer size. */
+  public 
+  boolean isFull() {
+    return numElements == maxSize;
+  }
+
+  /**
+     Get the number of elements in the buffer. This number is
+     guaranteed to be in the range 0 to <code>maxSize</code>
+     (inclusive).
+  */
+  public
+  int length() {
+    return numElements;
+  } 
+
+
+  int min(int a, int b) {
+    return a < b ? a : b;
+  }
+
+
+  /**
+     Resize the buffer to a new size. If the new size is smaller than
+     the old size events might be lost.
+     
+     @since 1.1
+   */
+  synchronized
+  public 
+  void resize(int newSize) {
+    if(newSize == maxSize) {
+        return;
+    }
+
+
+   LoggingEvent[] tmp = new LoggingEvent[newSize];
+
+   // we should not copy beyond the buf array
+   int len1 = maxSize - first;
+
+   // we should not copy beyond the tmp array
+   len1 = min(len1, newSize);
+
+   // er.. how much do we actually need to copy?
+   // We should not copy more than the actual number of elements.
+   len1 = min(len1, numElements);
+
+   // Copy from buf starting a first, to tmp, starting at position 0, len1 elements.
+   System.arraycopy(buf, first, tmp, 0, len1);
+   
+   // Are there any uncopied elements and is there still space in the new array?
+   int len2 = 0;
+   if((len1 < numElements) && (len1 < newSize)) {
+     len2 = numElements - len1;
+     len2 = min(len2, newSize - len1);
+     System.arraycopy(buf, 0, tmp, len1, len2);
+   }
+   
+   this.buf = tmp;
+   this.maxSize = newSize;    
+   this.first=0;   
+   this.numElements = len1+len2;
+   this.next = this.numElements;
+   if(this.next == this.maxSize) {
+    this.next = 0;
+}
+  }
+
+  
+  /**
+     Returns <code>true</code> if there is just one element in the
+     buffer. In other words, if there were no elements before the last
+     {@link #put} operation completed.  */
+  public
+  boolean wasEmpty() {
+    return numElements == 1;
+  }
+
+  /**
+      Returns <code>true</code> if the number of elements in the
+      buffer plus 1 equals the maximum buffer size, returns
+      <code>false</code> otherwise. */
+  public
+  boolean wasFull() {
+    return (numElements+1 == maxSize);
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/helpers/CountingQuietWriter.java b/srcjar/org/apache/log4j/helpers/CountingQuietWriter.java
new file mode 100644 (file)
index 0000000..55199e4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.io.Writer;
+import java.io.IOException;
+
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.ErrorCode;
+
+/**
+   Counts the number of bytes written.
+
+   @author Heinz Richter, heinz.richter@frogdot.com
+   @since 0.8.1
+
+   */
+public class CountingQuietWriter extends QuietWriter {
+
+  protected long count;
+
+  public
+  CountingQuietWriter(Writer writer, ErrorHandler eh) {
+    super(writer, eh);
+  }
+
+  public
+  void write(String string) {
+    try {
+      out.write(string);
+      count += string.length();
+    }
+    catch(IOException e) {
+      errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE);
+    }
+  }
+
+  public
+  long getCount() {
+    return count;
+  }
+
+  public
+  void setCount(long count) {
+    this.count = count;
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/helpers/CyclicBuffer.java b/srcjar/org/apache/log4j/helpers/CyclicBuffer.java
new file mode 100644 (file)
index 0000000..5c83013
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+     
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+
+   CyclicBuffer is used by other appenders to hold {@link LoggingEvent
+   LoggingEvents} for immediate or differed display.
+   
+   <p>This buffer gives read access to any element in the buffer not
+   just the first or last element.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0
+
+ */
+public class CyclicBuffer {
+  
+  LoggingEvent[] ea;
+  int first; 
+  int last; 
+  int numElems;
+  int maxSize;
+
+  /**
+     Instantiate a new CyclicBuffer of at most <code>maxSize</code> events.
+
+     The <code>maxSize</code> argument must a positive integer.
+
+     @param maxSize The maximum number of elements in the buffer.
+  */
+  public CyclicBuffer(int maxSize) throws IllegalArgumentException {
+    if(maxSize < 1) {
+      throw new IllegalArgumentException("The maxSize argument ("+maxSize+
+                           ") is not a positive integer.");
+    }
+    this.maxSize = maxSize;
+    ea = new LoggingEvent[maxSize];
+    first = 0;
+    last = 0;
+    numElems = 0;
+  }
+    
+  /**
+     Add an <code>event</code> as the last event in the buffer.
+
+   */
+  public
+  void add(LoggingEvent event) {    
+    ea[last] = event;    
+    if(++last == maxSize) {
+        last = 0;
+    }
+
+    if(numElems < maxSize) {
+        numElems++;
+    } else if(++first == maxSize) {
+        first = 0;
+    }
+  }
+
+
+  /**
+     Get the <i>i</i>th oldest event currently in the buffer. If
+     <em>i</em> is outside the range 0 to the number of elements
+     currently in the buffer, then <code>null</code> is returned.
+
+
+  */
+  public
+  LoggingEvent get(int i) {
+    if(i < 0 || i >= numElems) {
+        return null;
+    }
+
+    return ea[(first + i) % maxSize];
+  }
+
+  public 
+  int getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+     Get the oldest (first) element in the buffer. The oldest element
+     is removed from the buffer.
+  */
+  public
+  LoggingEvent get() {
+    LoggingEvent r = null;
+    if(numElems > 0) {
+      numElems--;
+      r = ea[first];
+      ea[first] = null;
+      if(++first == maxSize) {
+        first = 0;
+    }
+    } 
+    return r;
+  }
+  
+  /**
+     Get the number of elements in the buffer. This number is
+     guaranteed to be in the range 0 to <code>maxSize</code>
+     (inclusive).
+  */
+  public
+  int length() {
+    return numElems;
+  } 
+
+  /**
+     Resize the cyclic buffer to <code>newSize</code>.
+
+     @throws IllegalArgumentException if <code>newSize</code> is negative.
+   */
+  public 
+  void resize(int newSize) {
+    if(newSize < 0) {
+      throw new IllegalArgumentException("Negative array size ["+newSize+
+                                        "] not allowed.");
+    }
+    if(newSize == numElems)
+     {
+        return; // nothing to do
+    }
+    
+    LoggingEvent[] temp = new  LoggingEvent[newSize];
+
+    int loopLen = newSize < numElems ? newSize : numElems;
+    
+    for(int i = 0; i < loopLen; i++) {
+      temp[i] = ea[first];
+      ea[first] = null;
+      if(++first == numElems) {
+        first = 0;
+    }
+    }
+    ea = temp;
+    first = 0;
+    numElems = loopLen;
+    maxSize = newSize;
+    if (loopLen == newSize) {
+      last = 0;
+    } else {
+      last = loopLen;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/DateLayout.java b/srcjar/org/apache/log4j/helpers/DateLayout.java
new file mode 100644 (file)
index 0000000..383ef38
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.text.FieldPosition;
+
+
+/**
+   This abstract layout takes care of all the date related options and
+   formatting work.
+   
+
+   @author Ceki G&uuml;lc&uuml;
+ */
+abstract public class DateLayout extends Layout {
+
+  /**
+     String constant designating no time information. Current value of
+     this constant is <b>NULL</b>.
+     
+  */
+  public final static String NULL_DATE_FORMAT = "NULL";
+
+  /**
+     String constant designating relative time. Current value of
+     this constant is <b>RELATIVE</b>.
+   */
+  public final static String RELATIVE_TIME_DATE_FORMAT = "RELATIVE";
+
+  protected FieldPosition pos = new FieldPosition(0);
+
+  /**
+     @deprecated Options are now handled using the JavaBeans paradigm.
+     This constant is not longer needed and will be removed in the
+     <em>near</em> term.
+  */
+  final static public String DATE_FORMAT_OPTION = "DateFormat";
+  
+  /**
+     @deprecated Options are now handled using the JavaBeans paradigm.
+     This constant is not longer needed and will be removed in the
+     <em>near</em> term.
+  */
+  final static public String TIMEZONE_OPTION = "TimeZone";  
+
+  private String timeZoneID;
+  private String dateFormatOption;  
+
+  protected DateFormat dateFormat;
+  protected Date date = new Date();
+
+  /**
+     @deprecated Use the setter method for the option directly instead
+     of the generic <code>setOption</code> method. 
+  */
+  public
+  String[] getOptionStrings() {
+    return new String[] {DATE_FORMAT_OPTION, TIMEZONE_OPTION};
+  }
+
+  /**
+     @deprecated Use the setter method for the option directly instead
+     of the generic <code>setOption</code> method. 
+  */
+  public
+  void setOption(String option, String value) {
+    if(option.equalsIgnoreCase(DATE_FORMAT_OPTION)) {
+      dateFormatOption = value.toUpperCase();
+    } else if(option.equalsIgnoreCase(TIMEZONE_OPTION)) {
+      timeZoneID = value;
+    }
+  }
+  
+
+  /**
+    The value of the <b>DateFormat</b> option should be either an
+    argument to the constructor of {@link SimpleDateFormat} or one of
+    the srings "NULL", "RELATIVE", "ABSOLUTE", "DATE" or "ISO8601.
+   */
+  public
+  void setDateFormat(String dateFormat) {
+    if (dateFormat != null) {
+        dateFormatOption = dateFormat;
+    }
+    setDateFormat(dateFormatOption, TimeZone.getDefault());
+  }
+
+  /**
+     Returns value of the <b>DateFormat</b> option.
+   */
+  public
+  String getDateFormat() {
+    return dateFormatOption;
+  }
+  
+  /**
+    The <b>TimeZoneID</b> option is a time zone ID string in the format
+    expected by the {@link TimeZone#getTimeZone} method.
+   */
+  public
+  void setTimeZone(String timeZone) {
+    this.timeZoneID = timeZone;
+  }
+  
+  /**
+     Returns value of the <b>TimeZone</b> option.
+   */
+  public
+  String getTimeZone() {
+    return timeZoneID;
+  }
+  
+  public
+  void activateOptions() {
+    setDateFormat(dateFormatOption);
+    if(timeZoneID != null && dateFormat != null) {
+      dateFormat.setTimeZone(TimeZone.getTimeZone(timeZoneID));
+    }
+  }
+
+  public
+  void dateFormat(StringBuffer buf, LoggingEvent event) {
+    if(dateFormat != null) {
+      date.setTime(event.timeStamp);
+      dateFormat.format(date, buf, this.pos);
+      buf.append(' ');
+    }
+  }
+
+  /**
+     Sets the {@link DateFormat} used to format time and date in the
+     zone determined by <code>timeZone</code>.
+   */
+  public
+  void setDateFormat(DateFormat dateFormat, TimeZone timeZone) {
+    this.dateFormat = dateFormat;    
+    this.dateFormat.setTimeZone(timeZone);
+  }
+  
+  /**
+     Sets the DateFormat used to format date and time in the time zone
+     determined by <code>timeZone</code> parameter. The {@link DateFormat} used
+     will depend on the <code>dateFormatType</code>.
+
+     <p>The recognized types are {@link #NULL_DATE_FORMAT}, {@link
+     #RELATIVE_TIME_DATE_FORMAT} {@link
+     AbsoluteTimeDateFormat#ABS_TIME_DATE_FORMAT}, {@link
+     AbsoluteTimeDateFormat#DATE_AND_TIME_DATE_FORMAT} and {@link
+     AbsoluteTimeDateFormat#ISO8601_DATE_FORMAT}. If the
+     <code>dateFormatType</code> is not one of the above, then the
+     argument is assumed to be a date pattern for {@link
+     SimpleDateFormat}.
+  */
+  public
+  void setDateFormat(String dateFormatType, TimeZone timeZone) {
+    if(dateFormatType == null) {
+      this.dateFormat = null;
+      return;
+    } 
+
+    if(dateFormatType.equalsIgnoreCase(NULL_DATE_FORMAT)) {
+      this.dateFormat = null;
+    } else if (dateFormatType.equalsIgnoreCase(RELATIVE_TIME_DATE_FORMAT)) {
+      this.dateFormat =  new RelativeTimeDateFormat();
+    } else if(dateFormatType.equalsIgnoreCase(
+                             AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) {
+      this.dateFormat =  new AbsoluteTimeDateFormat(timeZone);
+    } else if(dateFormatType.equalsIgnoreCase(
+                        AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) {
+      this.dateFormat =  new DateTimeDateFormat(timeZone);
+    } else if(dateFormatType.equalsIgnoreCase(
+                              AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) {
+      this.dateFormat =  new ISO8601DateFormat(timeZone);
+    } else {
+      this.dateFormat = new SimpleDateFormat(dateFormatType);
+      this.dateFormat.setTimeZone(timeZone);
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/DateTimeDateFormat.java b/srcjar/org/apache/log4j/helpers/DateTimeDateFormat.java
new file mode 100644 (file)
index 0000000..e8759b8
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.Date;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.DateFormatSymbols;
+
+/**
+   Formats a {@link Date} in the format "dd MMM yyyy HH:mm:ss,SSS" for example,
+   "06 Nov 1994 15:49:37,459".
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.7.5
+*/
+public class DateTimeDateFormat extends AbsoluteTimeDateFormat {
+  private static final long serialVersionUID = 5547637772208514971L;
+
+  String[] shortMonths;
+
+  public
+  DateTimeDateFormat() {
+    super();
+    shortMonths = new DateFormatSymbols().getShortMonths();
+  }
+
+  public
+  DateTimeDateFormat(TimeZone timeZone) {
+    this();
+    setCalendar(Calendar.getInstance(timeZone));
+  }
+
+  /**
+     Appends to <code>sbuf</code> the date in the format "dd MMM yyyy
+     HH:mm:ss,SSS" for example, "06 Nov 1994 08:49:37,459".
+
+     @param sbuf the string buffer to write to
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    calendar.setTime(date);
+
+    int day = calendar.get(Calendar.DAY_OF_MONTH);
+    if(day < 10) {
+        sbuf.append('0');
+    }
+    sbuf.append(day);
+    sbuf.append(' ');
+    sbuf.append(shortMonths[calendar.get(Calendar.MONTH)]);
+    sbuf.append(' ');
+
+    int year =  calendar.get(Calendar.YEAR);
+    sbuf.append(year);
+    sbuf.append(' ');
+
+    return super.format(date, sbuf, fieldPosition);
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/FileWatchdog.java b/srcjar/org/apache/log4j/helpers/FileWatchdog.java
new file mode 100644 (file)
index 0000000..b78a4af
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:  Mathias Bogaert
+
+package org.apache.log4j.helpers;
+
+import java.io.File;
+
+/**
+   Check every now and then that a certain file has not changed. If it
+   has, then call the {@link #doOnChange} method.
+
+
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.9.1 */
+public abstract class FileWatchdog extends Thread {
+
+  /**
+     The default delay between every file modification check, set to 60
+     seconds.  */
+  static final public long DEFAULT_DELAY = 60000; 
+  /**
+     The name of the file to observe  for changes.
+   */
+  protected String filename;
+  
+  /**
+     The delay to observe between every check. By default set {@link
+     #DEFAULT_DELAY}. */
+  protected long delay = DEFAULT_DELAY; 
+  
+  File file;
+  long lastModif = 0; 
+  boolean warnedAlready = false;
+  boolean interrupted = false;
+
+  protected
+  FileWatchdog(String filename) {
+    super("FileWatchdog");
+    this.filename = filename;
+    file = new File(filename);
+    setDaemon(true);
+    checkAndConfigure();
+  }
+
+  /**
+     Set the delay to observe between each check of the file changes.
+   */
+  public
+  void setDelay(long delay) {
+    this.delay = delay;
+  }
+
+  abstract 
+  protected 
+  void doOnChange();
+
+  protected
+  void checkAndConfigure() {
+    boolean fileExists;
+    try {
+      fileExists = file.exists();
+    } catch(SecurityException  e) {
+      LogLog.warn("Was not allowed to read check file existance, file:["+
+                 filename+"].");
+      interrupted = true; // there is no point in continuing
+      return;
+    }
+
+    if(fileExists) {
+      long l = file.lastModified(); // this can also throw a SecurityException
+      if(l > lastModif) {           // however, if we reached this point this
+       lastModif = l;              // is very unlikely.
+       doOnChange();
+       warnedAlready = false;
+      }
+    } else {
+      if(!warnedAlready) {
+       LogLog.debug("["+filename+"] does not exist.");
+       warnedAlready = true;
+      }
+    }
+  }
+
+  public
+  void run() {    
+    while(!interrupted) {
+      try {
+           Thread.sleep(delay);
+      } catch(InterruptedException e) {
+       // no interruption expected
+      }
+      checkAndConfigure();
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/FormattingInfo.java b/srcjar/org/apache/log4j/helpers/FormattingInfo.java
new file mode 100644 (file)
index 0000000..e158243
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+
+/**
+   FormattingInfo instances contain the information obtained when parsing
+   formatting modifiers in conversion modifiers.
+
+   @author <a href=mailto:jim_cakalic@na.biomerieux.com>Jim Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.8.2   
+ */
+public class FormattingInfo {
+  int min = -1;
+  int max = 0x7FFFFFFF;
+  boolean leftAlign = false;
+
+  void reset() {
+    min = -1;
+    max = 0x7FFFFFFF;
+    leftAlign = false;      
+  }
+
+  void dump() {
+    LogLog.debug("min="+min+", max="+max+", leftAlign="+leftAlign);
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/ISO8601DateFormat.java b/srcjar/org/apache/log4j/helpers/ISO8601DateFormat.java
new file mode 100644 (file)
index 0000000..9079a2f
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.Date;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+
+// Contributors: Arndt Schoenewald <arndt@ibm23093i821.mc.schoenewald.de>
+
+/**
+   Formats a {@link Date} in the format "yyyy-MM-dd HH:mm:ss,SSS" for example
+   "1999-11-27 15:49:37,459".
+
+   <p>Refer to the <a
+   href=http://www.cl.cam.ac.uk/~mgk25/iso-time.html>summary of the
+   International Standard Date and Time Notation</a> for more
+   information on this format.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Andrew Vajoczki
+
+   @since 0.7.5
+*/
+public class ISO8601DateFormat extends AbsoluteTimeDateFormat {
+  private static final long serialVersionUID = -759840745298755296L;
+
+  public
+  ISO8601DateFormat() {
+  }
+
+  public
+  ISO8601DateFormat(TimeZone timeZone) {
+    super(timeZone);
+  }
+
+  static private long   lastTime;
+  static private char[] lastTimeString = new char[20];
+
+  /**
+     Appends a date in the format "YYYY-mm-dd HH:mm:ss,SSS"
+     to <code>sbuf</code>. For example: "1999-11-27 15:49:37,459".
+
+     @param sbuf the <code>StringBuffer</code> to write to
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    long now = date.getTime();
+    int millis = (int)(now % 1000);
+
+    if ((now - millis) != lastTime || lastTimeString[0] == 0) {
+      // We reach this point at most once per second
+      // across all threads instead of each time format()
+      // is called. This saves considerable CPU time.
+
+      calendar.setTime(date);
+
+      int start = sbuf.length();
+
+      int year =  calendar.get(Calendar.YEAR);
+      sbuf.append(year);
+
+      String month;
+      switch(calendar.get(Calendar.MONTH)) {
+      case Calendar.JANUARY: month = "-01-"; break;
+      case Calendar.FEBRUARY: month = "-02-";  break;
+      case Calendar.MARCH: month = "-03-"; break;
+      case Calendar.APRIL: month = "-04-";  break;
+      case Calendar.MAY: month = "-05-"; break;
+      case Calendar.JUNE: month = "-06-";  break;
+      case Calendar.JULY: month = "-07-"; break;
+      case Calendar.AUGUST: month = "-08-";  break;
+      case Calendar.SEPTEMBER: month = "-09-"; break;
+      case Calendar.OCTOBER: month = "-10-"; break;
+      case Calendar.NOVEMBER: month = "-11-";  break;
+      case Calendar.DECEMBER: month = "-12-";  break;
+      default: month = "-NA-"; break;
+      }
+      sbuf.append(month);
+
+      int day = calendar.get(Calendar.DAY_OF_MONTH);
+      if(day < 10) {
+        sbuf.append('0');
+    }
+      sbuf.append(day);
+
+      sbuf.append(' ');
+
+      int hour = calendar.get(Calendar.HOUR_OF_DAY);
+      if(hour < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(hour);
+      sbuf.append(':');
+
+      int mins = calendar.get(Calendar.MINUTE);
+      if(mins < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(mins);
+      sbuf.append(':');
+
+      int secs = calendar.get(Calendar.SECOND);
+      if(secs < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(secs);
+
+      sbuf.append(',');
+
+      // store the time string for next time to avoid recomputation
+      sbuf.getChars(start, sbuf.length(), lastTimeString, 0);
+      lastTime = now - millis;
+    }
+    else {
+      sbuf.append(lastTimeString);
+    }
+
+
+    if (millis < 100) {
+        sbuf.append('0');
+    }
+    if (millis < 10) {
+        sbuf.append('0');
+    }
+
+    sbuf.append(millis);
+    return sbuf;
+  }
+
+  /**
+    This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/helpers/Loader.java b/srcjar/org/apache/log4j/helpers/Loader.java
new file mode 100644 (file)
index 0000000..d789f5a
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.net.URL;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import java.io.InterruptedIOException;
+
+
+/**
+   Load resources (or images) from various sources.
+  @author Ceki G&uuml;lc&uuml;
+ */
+
+public class Loader  { 
+
+  static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
+
+  // We conservatively assume that we are running under Java 1.x
+  static private boolean java1 = true;
+  
+  static private boolean ignoreTCL = false;
+  
+  static {
+    String prop = OptionConverter.getSystemProperty("java.version", null);
+    
+    if(prop != null) {
+      int i = prop.indexOf('.');
+      if(i != -1) {    
+       if(prop.charAt(i+1) != '1') {
+        java1 = false;
+    }
+      } 
+    }
+    String ignoreTCLProp = OptionConverter.getSystemProperty("log4j.ignoreTCL", null);
+    if(ignoreTCLProp != null) {
+      ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true);      
+    }   
+  }
+  
+  /**
+   *  Get a resource by delegating to getResource(String).
+   *  @param resource resource name
+   *  @param clazz class, ignored.
+   *  @return URL to resource or null.
+   *  @deprecated as of 1.2.
+   */
+  public static URL getResource(String resource, Class clazz) {
+      return getResource(resource);
+  }
+
+  /**
+     This method will search for <code>resource</code> in different
+     places. The search order is as follows:
+
+     <ol>
+
+     <p><li>Search for <code>resource</code> using the thread context
+     class loader under Java2. If that fails, search for
+     <code>resource</code> using the class loader that loaded this
+     class (<code>Loader</code>). Under JDK 1.1, only the the class
+     loader that loaded this class (<code>Loader</code>) is used.
+
+     <p><li>Try one last time with
+     <code>ClassLoader.getSystemResource(resource)</code>, that is is
+     using the system class loader in JDK 1.2 and virtual machine's
+     built-in class loader in JDK 1.1.
+
+     </ol>
+  */
+  static public URL getResource(String resource) {
+    ClassLoader classLoader = null;
+    URL url = null;
+    
+    try {
+       if(!java1 && !ignoreTCL) {
+         classLoader = getTCL();
+         if(classLoader != null) {
+           LogLog.debug("Trying to find ["+resource+"] using context classloader "
+                        +classLoader+".");
+           url = classLoader.getResource(resource);      
+           if(url != null) {
+             return url;
+           }
+         }
+       }
+       
+       // We could not find resource. Ler us now try with the
+       // classloader that loaded this class.
+       classLoader = Loader.class.getClassLoader(); 
+       if(classLoader != null) {
+         LogLog.debug("Trying to find ["+resource+"] using "+classLoader
+                      +" class loader.");
+         url = classLoader.getResource(resource);
+         if(url != null) {
+           return url;
+         }
+       }
+    } catch(IllegalAccessException t) {
+        LogLog.warn(TSTR, t);
+    } catch(InvocationTargetException t) {
+        if (t.getTargetException() instanceof InterruptedException
+                || t.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+        LogLog.warn(TSTR, t);
+    } catch(Throwable t) {
+      //
+      //  can't be InterruptedException or InterruptedIOException
+      //    since not declared, must be error or RuntimeError.
+      LogLog.warn(TSTR, t);
+    }
+    
+    // Last ditch attempt: get the resource from the class path. It
+    // may be the case that clazz was loaded by the Extentsion class
+    // loader which the parent of the system class loader. Hence the
+    // code below.
+    LogLog.debug("Trying to find ["+resource+
+                  "] using ClassLoader.getSystemResource().");
+    return ClassLoader.getSystemResource(resource);
+  } 
+  
+  /**
+     Are we running under JDK 1.x?        
+  */
+  public
+  static
+  boolean isJava1() {
+    return java1;
+  }
+  
+  /**
+    * Get the Thread Context Loader which is a JDK 1.2 feature. If we
+    * are running under JDK 1.1 or anything else goes wrong the method
+    * returns <code>null<code>.
+    *
+    *  */
+  private static ClassLoader getTCL() throws IllegalAccessException, 
+    InvocationTargetException {
+
+    // Are we running on a JDK 1.2 or later system?
+    Method method = null;
+    try {
+      method = Thread.class.getMethod("getContextClassLoader", null);
+    } catch (NoSuchMethodException e) {
+      // We are running on JDK 1.1
+      return null;
+    }
+    
+    return (ClassLoader) method.invoke(Thread.currentThread(), null);
+  }
+
+
+  
+  /**
+   * If running under JDK 1.2 load the specified class using the
+   *  <code>Thread</code> <code>contextClassLoader</code> if that
+   *  fails try Class.forname. Under JDK 1.1 only Class.forName is
+   *  used.
+   *
+   */
+  static public Class loadClass (String clazz) throws ClassNotFoundException {
+    // Just call Class.forName(clazz) if we are running under JDK 1.1
+    // or if we are instructed to ignore the TCL.
+    if(java1 || ignoreTCL) {
+      return Class.forName(clazz);
+    } else {
+      try {
+           return getTCL().loadClass(clazz);
+      }
+      // we reached here because tcl was null or because of a
+      // security exception, or because clazz could not be loaded...
+      // In any case we now try one more time
+      catch(InvocationTargetException e) {
+          if (e.getTargetException() instanceof InterruptedException
+                  || e.getTargetException() instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+      } catch(Throwable t) {
+      }
+    }
+    return Class.forName(clazz);
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/LogLog.java b/srcjar/org/apache/log4j/helpers/LogLog.java
new file mode 100644 (file)
index 0000000..29869ed
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+/**
+   This class used to output log statements from within the log4j package.
+
+   <p>Log4j components cannot make log4j logging calls. However, it is
+   sometimes useful for the user to learn about what log4j is
+   doing. You can enable log4j internal logging by defining the
+   <b>log4j.configDebug</b> variable.
+
+   <p>All log4j internal debug calls go to <code>System.out</code>
+   where as internal error messages are sent to
+   <code>System.err</code>. All internal messages are prepended with
+   the string "log4j: ".
+   
+   @since 0.8.2
+   @author Ceki G&uuml;lc&uuml;
+*/
+public class LogLog {
+
+  /**
+     Defining this value makes log4j print log4j-internal debug
+     statements to <code>System.out</code>.
+     
+    <p> The value of this string is <b>log4j.debug</b>.
+    
+    <p>Note that the search for all option names is case sensitive.  */
+  public static final String DEBUG_KEY="log4j.debug";
+
+  /**
+     Defining this value makes log4j components print log4j-internal
+     debug statements to <code>System.out</code>.
+     
+    <p> The value of this string is <b>log4j.configDebug</b>.
+    
+    <p>Note that the search for all option names is case sensitive.  
+
+    @deprecated Use {@link #DEBUG_KEY} instead.
+  */
+  public static final String CONFIG_DEBUG_KEY="log4j.configDebug";
+
+  protected static boolean debugEnabled = false;  
+
+  /**
+     In quietMode not even errors generate any output.
+   */
+  private static boolean quietMode = false;
+
+  private static final String PREFIX = "log4j: ";
+  private static final String ERR_PREFIX = "log4j:ERROR ";
+  private static final String WARN_PREFIX = "log4j:WARN ";
+
+  static {
+    String key = OptionConverter.getSystemProperty(DEBUG_KEY, null);
+
+    if(key == null) {
+      key = OptionConverter.getSystemProperty(CONFIG_DEBUG_KEY, null);
+    }
+
+    if(key != null) { 
+      debugEnabled = OptionConverter.toBoolean(key, true);
+    }
+  }
+
+  /**
+     Allows to enable/disable log4j internal logging.
+   */
+  static
+  public
+  void setInternalDebugging(boolean enabled) {
+    debugEnabled = enabled;
+  }
+
+  /**
+     This method is used to output log4j internal debug
+     statements. Output goes to <code>System.out</code>.
+  */
+  public
+  static
+  void debug(String msg) {
+    if(debugEnabled && !quietMode) {
+      System.out.println(PREFIX+msg);
+    }
+  }
+
+  /**
+     This method is used to output log4j internal debug
+     statements. Output goes to <code>System.out</code>.
+  */
+  public
+  static
+  void debug(String msg, Throwable t) {
+    if(debugEnabled && !quietMode) {
+      System.out.println(PREFIX+msg);
+      if(t != null) {
+        t.printStackTrace(System.out);
+    }
+    }
+  }
+  
+
+  /**
+     This method is used to output log4j internal error
+     statements. There is no way to disable error statements.
+     Output goes to <code>System.err</code>.
+  */
+  public
+  static
+  void error(String msg) {
+    if(quietMode) {
+        return;
+    }
+    System.err.println(ERR_PREFIX+msg);
+  }  
+
+  /**
+     This method is used to output log4j internal error
+     statements. There is no way to disable error statements.
+     Output goes to <code>System.err</code>.  
+  */
+  public
+  static
+  void error(String msg, Throwable t) {
+    if(quietMode) {
+        return;
+    }
+
+    System.err.println(ERR_PREFIX+msg);
+    if(t != null) {
+      t.printStackTrace();
+    }
+  }  
+
+  /**
+     In quite mode no LogLog generates strictly no output, not even
+     for errors. 
+
+     @param quietMode A true for not
+  */
+  public
+  static
+  void setQuietMode(boolean quietMode) {
+    LogLog.quietMode = quietMode;
+  }
+
+  /**
+     This method is used to output log4j internal warning
+     statements. There is no way to disable warning statements.
+     Output goes to <code>System.err</code>.  */
+  public
+  static
+  void warn(String msg) {
+    if(quietMode) {
+        return;
+    }
+
+    System.err.println(WARN_PREFIX+msg);
+  }  
+
+  /**
+     This method is used to output log4j internal warnings. There is
+     no way to disable warning statements.  Output goes to
+     <code>System.err</code>.  */
+  public
+  static
+  void warn(String msg, Throwable t) {
+    if(quietMode) {
+        return;
+    }
+
+    System.err.println(WARN_PREFIX+msg);
+    if(t != null) {
+      t.printStackTrace();
+    }
+  }  
+}
diff --git a/srcjar/org/apache/log4j/helpers/MDCKeySetExtractor.java b/srcjar/org/apache/log4j/helpers/MDCKeySetExtractor.java
new file mode 100644 (file)
index 0000000..2d2a539
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.pattern.LogEvent;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+
+
+public final class MDCKeySetExtractor {
+    private final Method getKeySetMethod;
+    public static final MDCKeySetExtractor INSTANCE =
+            new MDCKeySetExtractor();
+
+
+    private MDCKeySetExtractor() {
+        //
+        //  log4j 1.2.15 and later will have method to get names
+        //     of all keys in MDC
+        //
+      Method getMethod = null;
+
+        try {
+           getMethod = LoggingEvent.class.getMethod(
+                      "getPropertyKeySet", null);
+        } catch(Exception ex) {
+            getMethod = null;
+        }
+      getKeySetMethod = getMethod;
+
+    }
+
+    public Set getPropertyKeySet(final LoggingEvent event) throws Exception {
+        //
+        //  MDC keys are not visible prior to log4j 1.2.15
+        //
+        Set keySet = null;
+        if (getKeySetMethod != null) {
+              keySet = (Set) getKeySetMethod.invoke(event, null);
+        } else {
+            //
+            //  for 1.2.14 and earlier could serialize and
+            //    extract MDC content
+              ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+              ObjectOutputStream os = new ObjectOutputStream(outBytes);
+              os.writeObject(event);
+              os.close();
+
+              byte[] raw = outBytes.toByteArray();
+              //
+              //   bytes 6 and 7 should be the length of the original classname
+              //     should be the same as our substitute class name
+              final String subClassName = LogEvent.class.getName();
+              if (raw[6] == 0 || raw[7] == subClassName.length()) {
+                  //
+                  //  manipulate stream to use our class name
+                  //
+                  for (int i = 0; i < subClassName.length(); i++) {
+                      raw[8 + i] = (byte) subClassName.charAt(i);
+                  }
+                  ByteArrayInputStream inBytes = new ByteArrayInputStream(raw);
+                  ObjectInputStream is = new ObjectInputStream(inBytes);
+                  Object cracked = is.readObject();
+                  if (cracked instanceof LogEvent) {
+                      keySet = ((LogEvent) cracked).getPropertyKeySet();
+                  }
+                  is.close();
+              }
+        }
+        return keySet;        
+    }
+}
diff --git a/srcjar/org/apache/log4j/helpers/NullEnumeration.java b/srcjar/org/apache/log4j/helpers/NullEnumeration.java
new file mode 100644 (file)
index 0000000..0f4310d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+   
+  An always-empty Enumerator.
+
+  @author Anders Kristensen
+  @since version 1.0
+ */
+public class NullEnumeration implements Enumeration {
+  private static final NullEnumeration instance = new NullEnumeration();
+  
+  private
+  NullEnumeration() {
+  }
+  
+  public static NullEnumeration getInstance() {
+    return instance;
+  }
+  
+  public
+  boolean hasMoreElements() {
+    return false;
+  }
+  
+  public
+  Object nextElement() {
+    throw new NoSuchElementException();
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/OnlyOnceErrorHandler.java b/srcjar/org/apache/log4j/helpers/OnlyOnceErrorHandler.java
new file mode 100644 (file)
index 0000000..950778d
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import  org.apache.log4j.spi.ErrorHandler;
+import  org.apache.log4j.spi.LoggingEvent;
+import  org.apache.log4j.Logger;
+import  org.apache.log4j.Appender;
+
+import java.io.InterruptedIOException;
+
+/**
+
+   The <code>OnlyOnceErrorHandler</code> implements log4j's default
+   error handling policy which consists of emitting a message for the
+   first error in an appender and ignoring all following errors.
+
+   <p>The error message is printed on <code>System.err</code>. 
+
+   <p>This policy aims at protecting an otherwise working application
+   from being flooded with error messages when logging fails.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0 */
+public class OnlyOnceErrorHandler implements ErrorHandler {
+
+
+  final String WARN_PREFIX = "log4j warning: ";
+  final String ERROR_PREFIX = "log4j error: ";
+
+  boolean firstTime = true;
+
+
+  /**
+     Does not do anything.
+   */
+  public 
+  void setLogger(Logger logger) {
+  }
+
+
+  /**
+     No options to activate.
+  */
+  public 
+  void activateOptions() {
+  }
+
+
+  /**
+     Prints the message and the stack trace of the exception on
+     <code>System.err</code>.  */
+  public
+  void error(String message, Exception e, int errorCode) { 
+    error(message, e, errorCode, null);
+  }
+
+  /**
+     Prints the message and the stack trace of the exception on
+     <code>System.err</code>.
+   */
+  public
+  void error(String message, Exception e, int errorCode, LoggingEvent event) {
+    if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+        Thread.currentThread().interrupt();
+    }
+    if(firstTime) {
+      LogLog.error(message, e);
+      firstTime = false;
+    }
+  }
+
+
+  /**
+     Print a the error message passed as parameter on
+     <code>System.err</code>.  
+  */
+  public 
+  void error(String message) {
+    if(firstTime) {
+      LogLog.error(message);
+      firstTime = false;
+    }
+  }
+  
+  /**
+     Does not do anything.
+   */
+  public
+  void setAppender(Appender appender) {
+  }
+
+  /**
+     Does not do anything.
+   */
+  public
+  void setBackupAppender(Appender appender) {
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/OptionConverter.java b/srcjar/org/apache/log4j/helpers/OptionConverter.java
new file mode 100644 (file)
index 0000000..e16b195
--- /dev/null
@@ -0,0 +1,543 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.LoggerRepository;
+
+// Contributors:   Avy Sharell (sharell@online.fr)
+//                 Matthieu Verbert (mve@zurich.ibm.com)
+//                 Colin Sampaleanu
+
+/**
+   A convenience class to convert property values to specific types.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Simon Kitching;
+   @author Anders Kristensen
+*/
+public class OptionConverter {
+
+  static String DELIM_START = "${";
+  static char   DELIM_STOP  = '}';
+  static int DELIM_START_LEN = 2;
+  static int DELIM_STOP_LEN  = 1;
+
+  /** OptionConverter is a static class. */
+  private OptionConverter() {}
+
+  public
+  static
+  String[] concatanateArrays(String[] l, String[] r) {
+    int len = l.length + r.length;
+    String[] a = new String[len];
+
+    System.arraycopy(l, 0, a, 0, l.length);
+    System.arraycopy(r, 0, a, l.length, r.length);
+
+    return a;
+  }
+
+  public
+  static
+  String convertSpecialChars(String s) {
+    char c;
+    int len = s.length();
+    StringBuffer sbuf = new StringBuffer(len);
+
+    int i = 0;
+    while(i < len) {
+      c = s.charAt(i++);
+      if (c == '\\') {
+       c =  s.charAt(i++);
+       if(c == 'n') {
+        c = '\n';
+    } else if(c == 'r') {
+        c = '\r';
+    } else if(c == 't') {
+        c = '\t';
+    } else if(c == 'f') {
+        c = '\f';
+    } else if(c == '\b') {
+        c = '\b';
+    } else if(c == '\"') {
+        c = '\"';
+    } else if(c == '\'') {
+        c = '\'';
+    } else if(c == '\\') {
+        c = '\\';
+    }
+      }
+      sbuf.append(c);
+    }
+    return sbuf.toString();
+  }
+
+
+  /**
+     Very similar to <code>System.getProperty</code> except
+     that the {@link SecurityException} is hidden.
+
+     @param key The key to search for.
+     @param def The default value to return.
+     @return the string value of the system property, or the default
+     value if there is no property with that key.
+
+     @since 1.1 */
+  public
+  static
+  String getSystemProperty(String key, String def) {
+    try {
+      return System.getProperty(key, def);
+    } catch(Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
+      LogLog.debug("Was not allowed to read system property \""+key+"\".");
+      return def;
+    }
+  }
+
+
+  public
+  static
+  Object instantiateByKey(Properties props, String key, Class superClass,
+                               Object defaultValue) {
+
+    // Get the value of the property in string form
+    String className = findAndSubst(key, props);
+    if(className == null) {
+      LogLog.error("Could not find value for key " + key);
+      return defaultValue;
+    }
+    // Trim className to avoid trailing spaces that cause problems.
+    return OptionConverter.instantiateByClassName(className.trim(), superClass,
+                                                 defaultValue);
+  }
+
+  /**
+     If <code>value</code> is "true", then <code>true</code> is
+     returned. If <code>value</code> is "false", then
+     <code>true</code> is returned. Otherwise, <code>default</code> is
+     returned.
+
+     <p>Case of value is unimportant.  */
+  public
+  static
+  boolean toBoolean(String value, boolean dEfault) {
+    if(value == null) {
+        return dEfault;
+    }
+    String trimmedVal = value.trim();
+    if("true".equalsIgnoreCase(trimmedVal)) {
+        return true;
+    }
+    if("false".equalsIgnoreCase(trimmedVal)) {
+        return false;
+    }
+    return dEfault;
+  }
+
+  public
+  static
+  int toInt(String value, int dEfault) {
+    if(value != null) {
+      String s = value.trim();
+      try {
+       return Integer.valueOf(s).intValue();
+      }
+      catch (NumberFormatException e) {
+        LogLog.error("[" + s + "] is not in proper int form.");
+       e.printStackTrace();
+      }
+    }
+    return dEfault;
+  }
+
+  /**
+     Converts a standard or custom priority level to a Level
+     object.  <p> If <code>value</code> is of form
+     "level#classname", then the specified class' toLevel method
+     is called to process the specified level string; if no '#'
+     character is present, then the default {@link org.apache.log4j.Level}
+     class is used to process the level value.
+
+     <p>As a special case, if the <code>value</code> parameter is
+     equal to the string "NULL", then the value <code>null</code> will
+     be returned.
+
+     <p> If any error occurs while converting the value to a level,
+     the <code>defaultValue</code> parameter, which may be
+     <code>null</code>, is returned.
+
+     <p> Case of <code>value</code> is insignificant for the level level, but is
+     significant for the class name part, if present.
+
+     @since 1.1 */
+  public
+  static
+  Level toLevel(String value, Level defaultValue) {
+    if(value == null) {
+        return defaultValue;
+    }
+      
+    value = value.trim();
+
+    int hashIndex = value.indexOf('#');
+    if (hashIndex == -1) {
+      if("NULL".equalsIgnoreCase(value)) {
+       return null;
+      } else {
+       // no class name specified : use standard Level class
+       return Level.toLevel(value, defaultValue);
+      }
+    }
+
+    Level result = defaultValue;
+
+    String clazz = value.substring(hashIndex+1);
+    String levelName = value.substring(0, hashIndex);
+
+    // This is degenerate case but you never know.
+    if("NULL".equalsIgnoreCase(levelName)) {
+       return null;
+    }
+
+    LogLog.debug("toLevel" + ":class=[" + clazz + "]"
+                + ":pri=[" + levelName + "]");
+
+    try {
+      Class customLevel = Loader.loadClass(clazz);
+
+      // get a ref to the specified class' static method
+      // toLevel(String, org.apache.log4j.Level)
+      Class[] paramTypes = new Class[] { String.class,
+                                        org.apache.log4j.Level.class
+                                       };
+      java.lang.reflect.Method toLevelMethod =
+                      customLevel.getMethod("toLevel", paramTypes);
+
+      // now call the toLevel method, passing level string + default
+      Object[] params = new Object[] {levelName, defaultValue};
+      Object o = toLevelMethod.invoke(null, params);
+
+      result = (Level) o;
+    } catch(ClassNotFoundException e) {
+      LogLog.warn("custom level class [" + clazz + "] not found.");
+    } catch(NoSuchMethodException e) {
+      LogLog.warn("custom level class [" + clazz + "]"
+        + " does not have a class function toLevel(String, Level)", e);
+    } catch(java.lang.reflect.InvocationTargetException e) {
+        if (e.getTargetException() instanceof InterruptedException
+                || e.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+      LogLog.warn("custom level class [" + clazz + "]"
+                  + " could not be instantiated", e);
+    } catch(ClassCastException e) {
+      LogLog.warn("class [" + clazz
+        + "] is not a subclass of org.apache.log4j.Level", e);
+    } catch(IllegalAccessException e) {
+      LogLog.warn("class ["+clazz+
+                  "] cannot be instantiated due to access restrictions", e);
+    } catch(RuntimeException e) {
+      LogLog.warn("class ["+clazz+"], level ["+levelName+
+                  "] conversion failed.", e);
+    }
+    return result;
+   }
+
+  public
+  static
+  long toFileSize(String value, long dEfault) {
+    if(value == null) {
+        return dEfault;
+    }
+
+    String s = value.trim().toUpperCase();
+    long multiplier = 1;
+    int index;
+
+    if((index = s.indexOf("KB")) != -1) {
+      multiplier = 1024;
+      s = s.substring(0, index);
+    }
+    else if((index = s.indexOf("MB")) != -1) {
+      multiplier = 1024*1024;
+      s = s.substring(0, index);
+    }
+    else if((index = s.indexOf("GB")) != -1) {
+      multiplier = 1024*1024*1024;
+      s = s.substring(0, index);
+    }
+    if(s != null) {
+      try {
+       return Long.valueOf(s).longValue() * multiplier;
+      }
+      catch (NumberFormatException e) {
+       LogLog.error("[" + s + "] is not in proper int form.");
+       LogLog.error("[" + value + "] not in expected format.", e);
+      }
+    }
+    return dEfault;
+  }
+
+  /**
+     Find the value corresponding to <code>key</code> in
+     <code>props</code>. Then perform variable substitution on the
+     found value.
+
+ */
+  public
+  static
+  String findAndSubst(String key, Properties props) {
+    String value = props.getProperty(key);
+    if(value == null) {
+        return null;
+    }
+
+    try {
+      return substVars(value, props);
+    } catch(IllegalArgumentException e) {
+      LogLog.error("Bad option value ["+value+"].", e);
+      return value;
+    }
+  }
+
+  /**
+     Instantiate an object given a class name. Check that the
+     <code>className</code> is a subclass of
+     <code>superClass</code>. If that test fails or the object could
+     not be instantiated, then <code>defaultValue</code> is returned.
+
+     @param className The fully qualified class name of the object to instantiate.
+     @param superClass The class to which the new object should belong.
+     @param defaultValue The object to return in case of non-fulfillment
+   */
+  public
+  static
+  Object instantiateByClassName(String className, Class superClass,
+                               Object defaultValue) {
+    if(className != null) {
+      try {
+       Class classObj = Loader.loadClass(className);
+       if(!superClass.isAssignableFrom(classObj)) {
+         LogLog.error("A \""+className+"\" object is not assignable to a \""+
+                      superClass.getName() + "\" variable.");
+         LogLog.error("The class \""+ superClass.getName()+"\" was loaded by ");
+         LogLog.error("["+superClass.getClassLoader()+"] whereas object of type ");
+         LogLog.error("\"" +classObj.getName()+"\" was loaded by ["
+                      +classObj.getClassLoader()+"].");
+         return defaultValue;
+       }
+       return classObj.newInstance();
+      } catch (ClassNotFoundException e) {
+           LogLog.error("Could not instantiate class [" + className + "].", e);
+      } catch (IllegalAccessException e) {
+           LogLog.error("Could not instantiate class [" + className + "].", e);
+      } catch (InstantiationException e) {
+        LogLog.error("Could not instantiate class [" + className + "].", e);
+      } catch (RuntimeException e) {
+           LogLog.error("Could not instantiate class [" + className + "].", e);
+      }
+    }
+    return defaultValue;
+  }
+
+
+  /**
+     Perform variable substitution in string <code>val</code> from the
+     values of keys found in the system propeties.
+
+     <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
+
+     <p>For example, if the System properties contains "key=value", then
+     the call
+     <pre>
+     String s = OptionConverter.substituteVars("Value of key is ${key}.");
+     </pre>
+
+     will set the variable <code>s</code> to "Value of key is value.".
+
+     <p>If no value could be found for the specified key, then the
+     <code>props</code> parameter is searched, if the value could not
+     be found there, then substitution defaults to the empty string.
+
+     <p>For example, if system propeties contains no value for the key
+     "inexistentKey", then the call
+
+     <pre>
+     String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
+     </pre>
+     will set <code>s</code> to "Value of inexistentKey is []"
+
+     <p>An {@link java.lang.IllegalArgumentException} is thrown if
+     <code>val</code> contains a start delimeter "${" which is not
+     balanced by a stop delimeter "}". </p>
+
+     <p><b>Author</b> Avy Sharell</a></p>
+
+     @param val The string on which variable substitution is performed.
+     @throws IllegalArgumentException if <code>val</code> is malformed.
+
+  */
+  public static
+  String substVars(String val, Properties props) throws
+                        IllegalArgumentException {
+
+    StringBuffer sbuf = new StringBuffer();
+
+    int i = 0;
+    int j, k;
+
+    while(true) {
+      j=val.indexOf(DELIM_START, i);
+      if(j == -1) {
+       // no more variables
+       if(i==0) { // this is a simple string
+         return val;
+       } else { // add the tail string which contails no variables and return the result.
+         sbuf.append(val.substring(i, val.length()));
+         return sbuf.toString();
+       }
+      } else {
+       sbuf.append(val.substring(i, j));
+       k = val.indexOf(DELIM_STOP, j);
+       if(k == -1) {
+         throw new IllegalArgumentException('"'+val+
+                     "\" has no closing brace. Opening brace at position " + j
+                                            + '.');
+       } else {
+         j += DELIM_START_LEN;
+         String key = val.substring(j, k);
+         // first try in System properties
+         String replacement = getSystemProperty(key, null);
+         // then try props parameter
+         if(replacement == null && props != null) {
+           replacement =  props.getProperty(key);
+         }
+
+         if(replacement != null) {
+           // Do variable substitution on the replacement string
+           // such that we can solve "Hello ${x2}" as "Hello p1" 
+            // the where the properties are
+           // x1=p1
+            // x2=${x1}
+           String recursiveReplacement = substVars(replacement, props);
+           sbuf.append(recursiveReplacement);
+         }
+         i = k + DELIM_STOP_LEN;
+       }
+      }
+    }
+  }
+
+    /**
+     * Configure log4j given an {@link InputStream}.
+     * 
+     * <p>
+     * The InputStream will be interpreted by a new instance of a log4j configurator.
+     * </p>
+     * <p>
+     * All configurations steps are taken on the <code>hierarchy</code> passed as a parameter.
+     * </p>
+     * 
+     * @param inputStream
+     *            The configuration input stream.
+     * @param clazz
+     *            The class name, of the log4j configurator which will parse the <code>inputStream</code>. This must be a
+     *            subclass of {@link Configurator}, or null. If this value is null then a default configurator of
+     *            {@link PropertyConfigurator} is used.
+     * @param hierarchy
+     *            The {@link org.apache.log4j.Hierarchy} to act on.
+     * @since 1.2.17
+     */
+
+static
+public
+void selectAndConfigure(InputStream inputStream, String clazz, LoggerRepository hierarchy) {
+Configurator configurator = null;
+
+if(clazz != null) {
+  LogLog.debug("Preferred configurator class: " + clazz);
+  configurator = (Configurator) instantiateByClassName(clazz,
+                           Configurator.class,
+                           null);
+  if(configurator == null) {
+   LogLog.error("Could not instantiate configurator ["+clazz+"].");
+   return;
+  }
+} else {
+  configurator = new PropertyConfigurator();
+}
+
+configurator.doConfigure(inputStream, hierarchy);
+}
+
+
+  /**
+     Configure log4j given a URL.
+
+     <p>The url must point to a file or resource which will be interpreted by
+     a new instance of a log4j configurator.
+
+     <p>All configurations steps are taken on the
+     <code>hierarchy</code> passed as a parameter.
+
+     <p>
+     @param url The location of the configuration file or resource.
+     @param clazz The classname, of the log4j configurator which will parse
+     the file or resource at <code>url</code>. This must be a subclass of
+     {@link Configurator}, or null. If this value is null then a default
+     configurator of {@link PropertyConfigurator} is used, unless the
+     filename pointed to by <code>url</code> ends in '.xml', in which case
+     {@link org.apache.log4j.xml.DOMConfigurator} is used.
+     @param hierarchy The {@link org.apache.log4j.Hierarchy} to act on.
+
+     @since 1.1.4 */
+
+  static
+  public
+  void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
+   Configurator configurator = null;
+   String filename = url.getFile();
+
+   if(clazz == null && filename != null && filename.endsWith(".xml")) {
+     clazz = "org.apache.log4j.xml.DOMConfigurator";
+   }
+
+   if(clazz != null) {
+     LogLog.debug("Preferred configurator class: " + clazz);
+     configurator = (Configurator) instantiateByClassName(clazz,
+                                                         Configurator.class,
+                                                         null);
+     if(configurator == null) {
+         LogLog.error("Could not instantiate configurator ["+clazz+"].");
+         return;
+     }
+   } else {
+     configurator = new PropertyConfigurator();
+   }
+
+   configurator.doConfigure(url, hierarchy);
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/PatternConverter.java b/srcjar/org/apache/log4j/helpers/PatternConverter.java
new file mode 100644 (file)
index 0000000..f18e264
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+
+   <p>PatternConverter is an abtract class that provides the
+   formatting functionality that derived classes need.
+
+   <p>Conversion specifiers in a conversion patterns are parsed to
+   individual PatternConverters. Each of which is responsible for
+   converting a logging event in a converter specific manner.
+
+   @author <a href="mailto:cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.8.2
+ */
+public abstract class PatternConverter {
+  public PatternConverter next;
+  int min = -1;
+  int max = 0x7FFFFFFF;
+  boolean leftAlign = false;
+
+  protected
+  PatternConverter() {  }
+  
+  protected
+  PatternConverter(FormattingInfo fi) {
+    min = fi.min;
+    max = fi.max;
+    leftAlign = fi.leftAlign;
+  }
+
+  /**
+     Derived pattern converters must override this method in order to
+     convert conversion specifiers in the correct way.
+  */
+  abstract
+  protected
+  String convert(LoggingEvent event);
+
+  /**
+     A template method for formatting in a converter specific way.
+   */
+  public
+  void format(StringBuffer sbuf, LoggingEvent e) {
+    String s = convert(e);
+
+    if(s == null) {
+      if(0 < min) {
+        spacePad(sbuf, min);
+    }
+      return;
+    }
+
+    int len = s.length();
+
+    if(len > max) {
+        sbuf.append(s.substring(len-max));
+    } else if(len < min) {
+      if(leftAlign) {  
+       sbuf.append(s);
+       spacePad(sbuf, min-len);
+      }
+      else {
+       spacePad(sbuf, min-len);
+       sbuf.append(s);
+      }
+    } else {
+        sbuf.append(s);
+    }
+  }    
+
+  static String[] SPACES = {" ", "  ", "    ", "        ", //1,2,4,8 spaces
+                           "                ", // 16 spaces
+                           "                                " }; // 32 spaces
+
+  /**
+     Fast space padding method.
+  */
+  public
+  void spacePad(StringBuffer sbuf, int length) {
+    while(length >= 32) {
+      sbuf.append(SPACES[5]);
+      length -= 32;
+    }
+    
+    for(int i = 4; i >= 0; i--) {      
+      if((length & (1<<i)) != 0) {
+       sbuf.append(SPACES[i]);
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/PatternParser.java b/srcjar/org/apache/log4j/helpers/PatternParser.java
new file mode 100644 (file)
index 0000000..0cd997a
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LocationInfo;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.Arrays;
+
+// Contributors:   Nelson Minar <(nelson@monkey.org>
+//                 Igor E. Poteryaev <jah@mail.ru>
+//                 Reinhard Deschler <reinhard.deschler@web.de>
+
+/**
+   Most of the work of the {@link org.apache.log4j.PatternLayout} class
+   is delegated to the PatternParser class.
+
+   <p>It is this class that parses conversion patterns and creates
+   a chained list of {@link OptionConverter OptionConverters}.
+
+   @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+   @author Anders Kristensen
+
+   @since 0.8.2
+*/
+public class PatternParser {
+
+  private static final char ESCAPE_CHAR = '%';
+
+  private static final int LITERAL_STATE = 0;
+  private static final int CONVERTER_STATE = 1;
+  private static final int DOT_STATE = 3;
+  private static final int MIN_STATE = 4;
+  private static final int MAX_STATE = 5;
+
+  static final int FULL_LOCATION_CONVERTER = 1000;
+  static final int METHOD_LOCATION_CONVERTER = 1001;
+  static final int CLASS_LOCATION_CONVERTER = 1002;
+  static final int LINE_LOCATION_CONVERTER = 1003;
+  static final int FILE_LOCATION_CONVERTER = 1004;
+
+  static final int RELATIVE_TIME_CONVERTER = 2000;
+  static final int THREAD_CONVERTER = 2001;
+  static final int LEVEL_CONVERTER = 2002;
+  static final int NDC_CONVERTER = 2003;
+  static final int MESSAGE_CONVERTER = 2004;
+
+  int state;
+  protected StringBuffer currentLiteral = new StringBuffer(32);
+  protected int patternLength;
+  protected int i;
+  PatternConverter head;
+  PatternConverter tail;
+  protected FormattingInfo formattingInfo = new FormattingInfo();
+  protected String pattern;
+
+  public
+  PatternParser(String pattern) {
+    this.pattern = pattern;
+    patternLength =  pattern.length();
+    state = LITERAL_STATE;
+  }
+
+  private
+  void  addToList(PatternConverter pc) {
+    if(head == null) {
+      head = tail = pc;
+    } else {
+      tail.next = pc;
+      tail = pc;
+    }
+  }
+
+  protected
+  String extractOption() {
+    if((i < patternLength) && (pattern.charAt(i) == '{')) {
+      int end = pattern.indexOf('}', i);
+      if (end > i) {
+       String r = pattern.substring(i + 1, end);
+       i = end+1;
+       return r;
+      }
+    }
+    return null;
+  }
+
+
+  /**
+     The option is expected to be in decimal and positive. In case of
+     error, zero is returned.  */
+  protected
+  int extractPrecisionOption() {
+    String opt = extractOption();
+    int r = 0;
+    if(opt != null) {
+      try {
+       r = Integer.parseInt(opt);
+       if(r <= 0) {
+           LogLog.error(
+               "Precision option (" + opt + ") isn't a positive integer.");
+           r = 0;
+       }
+      }
+      catch (NumberFormatException e) {
+       LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
+      }
+    }
+    return r;
+  }
+
+  public
+  PatternConverter parse() {
+    char c;
+    i = 0;
+    while(i < patternLength) {
+      c = pattern.charAt(i++);
+      switch(state) {
+      case LITERAL_STATE:
+        // In literal state, the last char is always a literal.
+        if(i == patternLength) {
+          currentLiteral.append(c);
+          continue;
+        }
+        if(c == ESCAPE_CHAR) {
+          // peek at the next char.
+          switch(pattern.charAt(i)) {
+          case ESCAPE_CHAR:
+            currentLiteral.append(c);
+            i++; // move pointer
+            break;
+          case 'n':
+            currentLiteral.append(Layout.LINE_SEP);
+            i++; // move pointer
+            break;
+          default:
+            if(currentLiteral.length() != 0) {
+              addToList(new LiteralPatternConverter(
+                                                  currentLiteral.toString()));
+              //LogLog.debug("Parsed LITERAL converter: \""
+              //           +currentLiteral+"\".");
+            }
+            currentLiteral.setLength(0);
+            currentLiteral.append(c); // append %
+            state = CONVERTER_STATE;
+            formattingInfo.reset();
+          }
+        }
+        else {
+          currentLiteral.append(c);
+        }
+        break;
+      case CONVERTER_STATE:
+       currentLiteral.append(c);
+       switch(c) {
+       case '-':
+         formattingInfo.leftAlign = true;
+         break;
+       case '.':
+         state = DOT_STATE;
+         break;
+       default:
+         if(c >= '0' && c <= '9') {
+           formattingInfo.min = c - '0';
+           state = MIN_STATE;
+         } else {
+        finalizeConverter(c);
+    }
+       } // switch
+       break;
+      case MIN_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9') {
+        formattingInfo.min = formattingInfo.min*10 + (c - '0');
+    } else if(c == '.') {
+        state = DOT_STATE;
+    } else {
+         finalizeConverter(c);
+       }
+       break;
+      case DOT_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9') {
+         formattingInfo.max = c - '0';
+          state = MAX_STATE;
+       }
+       else {
+         LogLog.error("Error occured in position "+i
+                    +".\n Was expecting digit, instead got char \""+c+"\".");
+         state = LITERAL_STATE;
+       }
+       break;
+      case MAX_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9') {
+        formattingInfo.max = formattingInfo.max*10 + (c - '0');
+    } else {
+         finalizeConverter(c);
+         state = LITERAL_STATE;
+       }
+       break;
+      } // switch
+    } // while
+    if(currentLiteral.length() != 0) {
+      addToList(new LiteralPatternConverter(currentLiteral.toString()));
+      //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
+    }
+    return head;
+  }
+
+  protected
+  void finalizeConverter(char c) {
+    PatternConverter pc = null;
+    switch(c) {
+    case 'c':
+      pc = new CategoryPatternConverter(formattingInfo,
+                                       extractPrecisionOption());
+      //LogLog.debug("CATEGORY converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'C':
+      pc = new ClassNamePatternConverter(formattingInfo,
+                                        extractPrecisionOption());
+      //LogLog.debug("CLASS_NAME converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'd':
+      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
+      DateFormat df;
+      String dOpt = extractOption();
+      if(dOpt != null) {
+        dateFormatStr = dOpt;
+    }
+
+      if(dateFormatStr.equalsIgnoreCase(
+                                    AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) {
+        df = new  ISO8601DateFormat();
+    } else if(dateFormatStr.equalsIgnoreCase(
+                                   AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) {
+        df = new AbsoluteTimeDateFormat();
+    } else if(dateFormatStr.equalsIgnoreCase(
+                              AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) {
+        df = new DateTimeDateFormat();
+    } else {
+       try {
+         df = new SimpleDateFormat(dateFormatStr);
+       }
+       catch (IllegalArgumentException e) {
+         LogLog.error("Could not instantiate SimpleDateFormat with " +
+                      dateFormatStr, e);
+         df = (DateFormat) OptionConverter.instantiateByClassName(
+                                  "org.apache.log4j.helpers.ISO8601DateFormat",
+                                  DateFormat.class, null);
+       }
+      }
+      pc = new DatePatternConverter(formattingInfo, df);
+      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'F':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       FILE_LOCATION_CONVERTER);
+      //LogLog.debug("File name converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'l':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       FULL_LOCATION_CONVERTER);
+      //LogLog.debug("Location converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'L':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       LINE_LOCATION_CONVERTER);
+      //LogLog.debug("LINE NUMBER converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'm':
+      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
+      //LogLog.debug("MESSAGE converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'M':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       METHOD_LOCATION_CONVERTER);
+      //LogLog.debug("METHOD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'p':
+      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
+      //LogLog.debug("LEVEL converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'r':
+      pc = new BasicPatternConverter(formattingInfo,
+                                        RELATIVE_TIME_CONVERTER);
+      //LogLog.debug("RELATIVE time converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 't':
+      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
+      //LogLog.debug("THREAD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+      /*case 'u':
+      if(i < patternLength) {
+       char cNext = pattern.charAt(i);
+       if(cNext >= '0' && cNext <= '9') {
+         pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
+         LogLog.debug("USER converter ["+cNext+"].");
+         formattingInfo.dump();
+         currentLiteral.setLength(0);
+         i++;
+       }
+       else
+         LogLog.error("Unexpected char" +cNext+" at position "+i);
+      }
+      break;*/
+    case 'x':
+      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
+      //LogLog.debug("NDC converter.");
+      currentLiteral.setLength(0);
+      break;
+    case 'X':
+      String xOpt = extractOption();
+      pc = new MDCPatternConverter(formattingInfo, xOpt);
+      currentLiteral.setLength(0);
+      break;
+    default:
+      LogLog.error("Unexpected char [" +c+"] at position "+i
+                  +" in conversion patterrn.");
+      pc = new LiteralPatternConverter(currentLiteral.toString());
+      currentLiteral.setLength(0);
+    }
+
+    addConverter(pc);
+  }
+
+  protected
+  void addConverter(PatternConverter pc) {
+    currentLiteral.setLength(0);
+    // Add the pattern converter to the list.
+    addToList(pc);
+    // Next pattern is assumed to be a literal.
+    state = LITERAL_STATE;
+    // Reset formatting info
+    formattingInfo.reset();
+  }
+
+  // ---------------------------------------------------------------------
+  //                      PatternConverters
+  // ---------------------------------------------------------------------
+
+  private static class BasicPatternConverter extends PatternConverter {
+    int type;
+
+    BasicPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      switch(type) {
+      case RELATIVE_TIME_CONVERTER:
+       return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
+      case THREAD_CONVERTER:
+       return event.getThreadName();
+      case LEVEL_CONVERTER:
+       return event.getLevel().toString();
+      case NDC_CONVERTER:
+       return event.getNDC();
+      case MESSAGE_CONVERTER: {
+       return event.getRenderedMessage();
+      }
+      default: return null;
+      }
+    }
+  }
+
+  private static class LiteralPatternConverter extends PatternConverter {
+    private String literal;
+
+    LiteralPatternConverter(String value) {
+      literal = value;
+    }
+
+    public
+    final
+    void format(StringBuffer sbuf, LoggingEvent event) {
+      sbuf.append(literal);
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      return literal;
+    }
+  }
+
+  private static class DatePatternConverter extends PatternConverter {
+    private DateFormat df;
+    private Date date;
+
+    DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
+      super(formattingInfo);
+      date = new Date();
+      this.df = df;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      date.setTime(event.timeStamp);
+      String converted = null;
+      try {
+        converted = df.format(date);
+      }
+      catch (Exception ex) {
+        LogLog.error("Error occured while converting date.", ex);
+      }
+      return converted;
+    }
+  }
+
+  private static class MDCPatternConverter extends PatternConverter {
+    private String key;
+
+    MDCPatternConverter(FormattingInfo formattingInfo, String key) {
+      super(formattingInfo);
+      this.key = key;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      if (key == null) {
+          StringBuffer buf = new StringBuffer("{");
+          Map properties = event.getProperties();
+          if (properties.size() > 0) {
+            Object[] keys = properties.keySet().toArray();
+            Arrays.sort(keys);
+            for (int i = 0; i < keys.length; i++) {
+                buf.append('{');
+                buf.append(keys[i]);
+                buf.append(',');
+                buf.append(properties.get(keys[i]));
+                buf.append('}');
+            }
+          }
+          buf.append('}');
+          return buf.toString();
+      } else {
+        Object val = event.getMDC(key);
+        if(val == null) {
+               return null;
+        } else {
+               return val.toString();
+        }
+      }
+    }
+  }
+
+
+  private class LocationPatternConverter extends PatternConverter {
+    int type;
+
+    LocationPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      LocationInfo locationInfo = event.getLocationInformation();
+      switch(type) {
+      case FULL_LOCATION_CONVERTER:
+       return locationInfo.fullInfo;
+      case METHOD_LOCATION_CONVERTER:
+       return locationInfo.getMethodName();
+      case LINE_LOCATION_CONVERTER:
+       return locationInfo.getLineNumber();
+      case FILE_LOCATION_CONVERTER:
+       return locationInfo.getFileName();
+      default: return null;
+      }
+    }
+  }
+
+  private static abstract class NamedPatternConverter extends PatternConverter {
+    int precision;
+
+    NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo);
+      this.precision =  precision;
+    }
+
+    abstract
+    String getFullyQualifiedName(LoggingEvent event);
+
+    public
+    String convert(LoggingEvent event) {
+      String n = getFullyQualifiedName(event);
+      if(precision <= 0) {
+        return n;
+    } else {
+       int len = n.length();
+
+       // We substract 1 from 'len' when assigning to 'end' to avoid out of
+       // bounds exception in return r.substring(end+1, len). This can happen if
+       // precision is 1 and the category name ends with a dot.
+       int end = len -1 ;
+       for(int i = precision; i > 0; i--) {
+         end = n.lastIndexOf('.', end-1);
+         if(end == -1) {
+        return n;
+    }
+       }
+       return n.substring(end+1, len);
+      }
+    }
+  }
+
+  private class ClassNamePatternConverter extends NamedPatternConverter {
+
+    ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(LoggingEvent event) {
+      return event.getLocationInformation().getClassName();
+    }
+  }
+
+  private class CategoryPatternConverter extends NamedPatternConverter {
+
+    CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(LoggingEvent event) {
+      return event.getLoggerName();
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/helpers/QuietWriter.java b/srcjar/org/apache/log4j/helpers/QuietWriter.java
new file mode 100644 (file)
index 0000000..778f091
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.io.Writer;
+import java.io.FilterWriter;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.ErrorCode;
+
+
+/**
+   QuietWriter does not throw exceptions when things go
+   wrong. Instead, it delegates error handling to its {@link ErrorHandler}. 
+
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.7.3
+*/
+public class QuietWriter extends FilterWriter {
+
+  protected ErrorHandler errorHandler;
+
+  public
+  QuietWriter(Writer writer, ErrorHandler errorHandler) {
+    super(writer);
+    setErrorHandler(errorHandler);
+  }
+
+  public
+  void write(String string) {
+    if (string != null) {
+       try {
+               out.write(string);
+       } catch(Exception e) {
+               errorHandler.error("Failed to write ["+string+"].", e, 
+                               ErrorCode.WRITE_FAILURE);
+           }
+    }
+  }
+
+  public
+  void flush() {
+    try {
+      out.flush();
+    } catch(Exception e) {
+      errorHandler.error("Failed to flush writer,", e, 
+                        ErrorCode.FLUSH_FAILURE);
+    }  
+  }
+
+
+  public
+  void setErrorHandler(ErrorHandler eh) {
+    if(eh == null) {
+      // This is a programming error on the part of the enclosing appender.
+      throw new IllegalArgumentException("Attempted to set null ErrorHandler.");
+    } else { 
+      this.errorHandler = eh;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/RelativeTimeDateFormat.java b/srcjar/org/apache/log4j/helpers/RelativeTimeDateFormat.java
new file mode 100644 (file)
index 0000000..ab81a34
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Date;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.DateFormat;
+
+/**
+   Formats a {@link Date} by printing the number of milliseconds
+   elapsed since construction of the format.  This is the fastest
+   printing DateFormat in the package.
+   
+   @author Ceki G&uuml;lc&uuml;
+   
+   @since 0.7.5
+*/
+public class RelativeTimeDateFormat extends DateFormat {
+  private static final long serialVersionUID = 7055751607085611984L;
+
+
+  protected final long startTime;
+
+  public
+  RelativeTimeDateFormat() {
+    this.startTime = System.currentTimeMillis();
+  }
+  
+  /**
+     Appends to <code>sbuf</code> the number of milliseconds elapsed
+     since the start of the application. 
+     
+     @since 0.7.5
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+    //System.err.println(":"+ date.getTime() + " - " + startTime);
+    return sbuf.append((date.getTime() - startTime));
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }  
+}
diff --git a/srcjar/org/apache/log4j/helpers/SyslogQuietWriter.java b/srcjar/org/apache/log4j/helpers/SyslogQuietWriter.java
new file mode 100644 (file)
index 0000000..62e933e
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+
+
+import java.io.Writer;
+import org.apache.log4j.spi.ErrorHandler;
+
+/**
+   SyslogQuietWriter extends QuietWriter by prepending the syslog
+   level code before each printed String.
+
+   @since 0.7.3
+*/
+public class SyslogQuietWriter extends QuietWriter {
+
+  int syslogFacility;
+  int level;
+
+  public
+  SyslogQuietWriter(Writer writer, int syslogFacility, ErrorHandler eh) {
+    super(writer, eh);
+    this.syslogFacility = syslogFacility;
+  }
+
+  public
+  void setLevel(int level) {
+    this.level = level;
+  }
+
+  public
+  void setSyslogFacility(int syslogFacility) {
+    this.syslogFacility = syslogFacility;
+  }
+
+  public
+  void write(String string) {
+    super.write("<"+(syslogFacility | level)+">" + string);
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/SyslogWriter.java b/srcjar/org/apache/log4j/helpers/SyslogWriter.java
new file mode 100644 (file)
index 0000000..d6ce4bf
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+
+import java.io.Writer;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.DatagramPacket;
+import java.net.UnknownHostException;
+import java.net.SocketException;
+import java.io.IOException;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+/**
+   SyslogWriter is a wrapper around the java.net.DatagramSocket class
+   so that it behaves like a java.io.Writer.
+
+   @since 0.7.3
+*/
+public class SyslogWriter extends Writer {
+
+  final int SYSLOG_PORT = 514;
+  /**
+   *  Host string from last constructed SyslogWriter.
+   *  @deprecated
+   */
+  static String syslogHost;
+  
+  private InetAddress address;
+  private final int port;
+  private DatagramSocket ds;
+
+  /**
+   *  Constructs a new instance of SyslogWriter.
+   *  @param syslogHost host name, may not be null.  A port
+   *  may be specified by following the name or IPv4 literal address with
+   *  a colon and a decimal port number.  To specify a port with an IPv6
+   *  address, enclose the IPv6 address in square brackets before appending
+   *  the colon and decimal port number.
+   */
+  public
+  SyslogWriter(final String syslogHost) {
+    SyslogWriter.syslogHost = syslogHost;
+    if (syslogHost == null) {
+        throw new NullPointerException("syslogHost");
+    }
+    
+    String host = syslogHost;
+    int urlPort = -1;
+    
+    //
+    //  If not an unbracketed IPv6 address then
+    //      parse as a URL
+    //
+    if (host.indexOf("[") != -1 || host.indexOf(':') == host.lastIndexOf(':')) {
+        try {
+            URL url = new URL("http://" + host);
+            if (url.getHost() != null) {
+                host = url.getHost();
+                //   if host is a IPv6 literal, strip off the brackets
+                if(host.startsWith("[") && host.charAt(host.length() - 1) == ']') {
+                    host = host.substring(1, host.length() - 1);
+                }
+                urlPort = url.getPort();
+            }
+        } catch(MalformedURLException e) {
+               LogLog.error("Malformed URL: will attempt to interpret as InetAddress.", e);
+        }
+    }
+    
+    if (urlPort == -1) {
+        urlPort = SYSLOG_PORT;
+    }
+    port = urlPort;
+
+    try {      
+      this.address = InetAddress.getByName(host);
+    }
+    catch (UnknownHostException e) {
+      LogLog.error("Could not find " + host +
+                        ". All logging will FAIL.", e);
+    }
+
+    try {
+      this.ds = new DatagramSocket();
+    }
+    catch (SocketException e) {
+      e.printStackTrace();
+      LogLog.error("Could not instantiate DatagramSocket to " + host +
+                        ". All logging will FAIL.", e);
+    }
+    
+  }
+
+
+  public
+  void write(char[] buf, int off, int len) throws IOException {
+    this.write(new String(buf, off, len));
+  }
+  
+  public
+  void write(final String string) throws IOException {
+
+    if(this.ds != null && this.address != null) {
+        byte[] bytes = string.getBytes();
+        //
+        //  syslog packets must be less than 1024 bytes
+        //
+        int bytesLength = bytes.length;
+        if (bytesLength >= 1024) {
+            bytesLength = 1024;
+        }
+        DatagramPacket packet = new DatagramPacket(bytes, bytesLength,
+                               address, port);
+        ds.send(packet);
+    }
+    
+  }
+
+  public
+  void flush() {}
+
+  public void close() {
+      if (ds != null) {
+          ds.close();
+      }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/ThreadLocalMap.java b/srcjar/org/apache/log4j/helpers/ThreadLocalMap.java
new file mode 100644 (file)
index 0000000..da60c86
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.Hashtable;
+
+/**
+   <code>ThreadLocalMap</code> extends {@link InheritableThreadLocal}
+   to bequeath a copy of the hashtable of the MDC of the parent
+   thread.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2
+*/
+final public class ThreadLocalMap extends InheritableThreadLocal {
+
+  public
+  final
+  Object childValue(Object parentValue) {
+    Hashtable ht = (Hashtable) parentValue;
+    if(ht != null) {
+      return ht.clone();
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/Transform.java b/srcjar/org/apache/log4j/helpers/Transform.java
new file mode 100644 (file)
index 0000000..7626e71
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+/**
+   Utility class for transforming strings.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Michael A. McAngus 
+ */
+public class Transform {
+
+   private static final String CDATA_START  = "<![CDATA[";
+   private static final String CDATA_END    = "]]>";
+   private static final String CDATA_PSEUDO_END = "]]&gt;";
+   private static final String CDATA_EMBEDED_END = CDATA_END + CDATA_PSEUDO_END + CDATA_START;
+   private static final int CDATA_END_LEN = CDATA_END.length();
+
+  /**
+   * This method takes a string which may contain HTML tags (ie,
+   * &lt;b&gt;, &lt;table&gt;, etc) and replaces any
+   * '<',  '>' , '&' or '"'
+   * characters with respective predefined entity references.
+   *
+   * @param input The text to be converted.
+   * @return The input string with the special characters replaced.
+   * */
+  static public String escapeTags(final String input) {
+    //Check if the string is null, zero length or devoid of special characters
+    // if so, return what was sent in.
+
+    if(input == null
+       || input.length() == 0
+       || (input.indexOf('"') == -1 &&
+           input.indexOf('&') == -1 &&
+           input.indexOf('<') == -1 &&
+           input.indexOf('>') == -1)) {
+      return input;
+    }
+
+    //Use a StringBuffer in lieu of String concatenation -- it is
+    //much more efficient this way.
+
+    StringBuffer buf = new StringBuffer(input.length() + 6);
+    char ch = ' ';
+
+    int len = input.length();
+    for(int i=0; i < len; i++) {
+      ch = input.charAt(i);
+      if (ch > '>') {
+          buf.append(ch);
+      } else if(ch == '<') {
+             buf.append("&lt;");
+      } else if(ch == '>') {
+             buf.append("&gt;");
+      } else if(ch == '&') {
+             buf.append("&amp;");
+      } else if(ch == '"') {
+             buf.append("&quot;");
+      } else {
+             buf.append(ch);
+      }
+    }
+    return buf.toString();
+  }
+
+  /**
+  * Ensures that embeded CDEnd strings (]]>) are handled properly
+  * within message, NDC and throwable tag text.
+  *
+  * @param buf StringBuffer holding the XML data to this point.  The
+  * initial CDStart (<![CDATA[) and final CDEnd (]]>) of the CDATA
+  * section are the responsibility of the calling method.
+  * @param str The String that is inserted into an existing CDATA Section within buf.  
+  * */
+  static public void appendEscapingCDATA(final StringBuffer buf,
+                                         final String str) {
+      if (str != null) {
+          int end = str.indexOf(CDATA_END);
+          if (end < 0) {
+              buf.append(str);
+          } else {
+              int start = 0;
+              while (end > -1) {
+                  buf.append(str.substring(start, end));
+                  buf.append(CDATA_EMBEDED_END);
+                  start = end + CDATA_END_LEN;
+                  if (start < str.length()) {
+                      end = str.indexOf(CDATA_END, start);
+                  } else {
+                      return;
+                  }
+              }
+              buf.append(str.substring(start));
+          }
+      }
+  }
+}
diff --git a/srcjar/org/apache/log4j/helpers/UtilLoggingLevel.java b/srcjar/org/apache/log4j/helpers/UtilLoggingLevel.java
new file mode 100644 (file)
index 0000000..b15fbca
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Level;
+
+/**
+ * An extension of the Level class that provides support for java.util.logging
+ * Levels.
+ *
+ *
+ * @author Scott Deboy (sdeboy@apache.org)
+ */
+
+public class UtilLoggingLevel extends Level {
+
+    /**
+     * Serialization version id.
+     */
+    private static final long serialVersionUID = 909301162611820211L;
+
+    /**
+     * Numerical value for SEVERE.
+     */
+    public static final int SEVERE_INT = 22000;
+    /**
+     * Numerical value for WARNING.
+     */
+    public static final int WARNING_INT = 21000;
+
+    //INFO level defined in parent as 20000..no need to redefine here
+    
+    /**
+     * Numerical value for CONFIG.
+     */
+    public static final int CONFIG_INT = 14000;
+    /**
+     * Numerical value for FINE.
+     */
+    public static final int FINE_INT = 13000;
+    /**
+     * Numerical value for FINER.
+     */
+    public static final int FINER_INT = 12000;
+    /**
+     * Numerical value for FINEST.
+     */
+    public static final int FINEST_INT = 11000;
+    /**
+     * Numerical value for UNKNOWN.
+     */
+    public static final int UNKNOWN_INT = 10000;
+
+    /**
+     * SEVERE.
+     */
+    public static final UtilLoggingLevel SEVERE =
+            new UtilLoggingLevel(SEVERE_INT, "SEVERE", 0);
+    /**
+     * WARNING.
+     */
+    public static final UtilLoggingLevel WARNING =
+            new UtilLoggingLevel(WARNING_INT, "WARNING", 4);
+    /**
+     * INFO.
+     */
+    //note: we've aligned the int values of the java.util.logging INFO level with log4j's level
+    public static final UtilLoggingLevel INFO =
+            new UtilLoggingLevel(INFO_INT, "INFO", 5);
+    /**
+     * CONFIG.
+     */
+    public static final UtilLoggingLevel CONFIG =
+            new UtilLoggingLevel(CONFIG_INT, "CONFIG", 6);
+    /**
+     * FINE.
+     */
+    public static final UtilLoggingLevel FINE =
+            new UtilLoggingLevel(FINE_INT, "FINE", 7);
+    /**
+     * FINER.
+     */
+    public static final UtilLoggingLevel FINER =
+            new UtilLoggingLevel(FINER_INT, "FINER", 8);
+    /**
+     * FINEST.
+     */
+    public static final UtilLoggingLevel FINEST =
+            new UtilLoggingLevel(FINEST_INT, "FINEST", 9);
+
+    /**
+     * Create new instance.
+     * @param level numeric value for level.
+     * @param levelStr symbolic name for level.
+     * @param syslogEquivalent Equivalent syslog severity.
+     */
+    protected UtilLoggingLevel(final int level,
+                               final String levelStr,
+                               final int syslogEquivalent) {
+        super(level, levelStr, syslogEquivalent);
+    }
+
+    /**
+     * Convert an integer passed as argument to a level. If the
+     * conversion fails, then this method returns the specified default.
+     * @param val numeric value.
+     * @param defaultLevel level to be returned if no level matches
+     * numeric value.
+     * @return matching level or default level.
+     */
+    public static UtilLoggingLevel toLevel(final int val,
+                               final UtilLoggingLevel defaultLevel) {
+        switch (val) {
+            case SEVERE_INT:
+                return SEVERE;
+
+            case WARNING_INT:
+                return WARNING;
+
+            case INFO_INT:
+                return INFO;
+
+            case CONFIG_INT:
+                return CONFIG;
+
+            case FINE_INT:
+                return FINE;
+
+            case FINER_INT:
+                return FINER;
+
+            case FINEST_INT:
+                return FINEST;
+
+            default:
+                return defaultLevel;
+        }
+    }
+
+    /**
+     * Gets level matching numeric value.
+     * @param val numeric value.
+     * @return  matching level or UtilLoggerLevel.FINEST if no match.
+     */
+    public static Level toLevel(final int val) {
+        return toLevel(val, FINEST);
+    }
+
+    /**
+     * Gets list of supported levels.
+     * @return  list of supported levels.
+     */
+    public static List getAllPossibleLevels() {
+        ArrayList list = new ArrayList();
+        list.add(FINE);
+        list.add(FINER);
+        list.add(FINEST);
+        list.add(INFO);
+        list.add(CONFIG);
+        list.add(WARNING);
+        list.add(SEVERE);
+        return list;
+    }
+
+    /**
+     * Get level with specified symbolic name.
+     * @param s symbolic name.
+     * @return matching level or Level.DEBUG if no match.
+     */
+    public static Level toLevel(final String s) {
+        return toLevel(s, Level.DEBUG);
+    }
+
+
+    /**
+     * Get level with specified symbolic name.
+     * @param sArg symbolic name.
+     * @param defaultLevel level to return if no match.
+     * @return matching level or defaultLevel if no match.
+     */
+    public static Level toLevel(final String sArg,
+                                final Level defaultLevel) {
+        if (sArg == null) {
+            return defaultLevel;
+        }
+
+        String s = sArg.toUpperCase();
+
+        if (s.equals("SEVERE")) {
+            return SEVERE;
+        }
+
+        //if(s.equals("FINE")) return Level.FINE;
+        if (s.equals("WARNING")) {
+            return WARNING;
+        }
+
+        if (s.equals("INFO")) {
+            return INFO;
+        }
+
+        if (s.equals("CONFI")) {
+            return CONFIG;
+        }
+
+        if (s.equals("FINE")) {
+            return FINE;
+        }
+
+        if (s.equals("FINER")) {
+            return FINER;
+        }
+
+        if (s.equals("FINEST")) {
+            return FINEST;
+        }
+        return defaultLevel;
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/helpers/package.html b/srcjar/org/apache/log4j/helpers/package.html
new file mode 100644 (file)
index 0000000..ab75bf3
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>This package is used internally.
+
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Sat Jul  3 15:12:58 MDT 1999
+<!-- hhmts end -->
+</body> </html>
diff --git a/srcjar/org/apache/log4j/jdbc/JDBCAppender.java b/srcjar/org/apache/log4j/jdbc/JDBCAppender.java
new file mode 100644 (file)
index 0000000..25369df
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+  The JDBCAppender provides for sending log events to a database.
+  
+ <p><b><font color="#FF2222">WARNING: This version of JDBCAppender
+ is very likely to be completely replaced in the future. Moreoever,
+ it does not log exceptions</font></b>.
+
+  <p>Each append call adds to an <code>ArrayList</code> buffer.  When
+  the buffer is filled each log event is placed in a sql statement
+  (configurable) and executed.
+
+  <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
+  configurable options in the standard log4j ways.
+
+  <p>The <code>setSql(String sql)</code> sets the SQL statement to be
+  used for logging -- this statement is sent to a
+  <code>PatternLayout</code> (either created automaticly by the
+  appender or added by the user).  Therefore by default all the
+  conversion patterns in <code>PatternLayout</code> can be used
+  inside of the statement.  (see the test cases for examples)
+
+  <p>Overriding the {@link #getLogStatement} method allows more
+  explicit control of the statement used for logging.
+
+  <p>For use as a base class:
+
+    <ul>
+
+    <li>Override <code>getConnection()</code> to pass any connection
+    you want.  Typically this is used to enable application wide
+    connection pooling.
+
+     <li>Override <code>closeConnection(Connection con)</code> -- if
+     you override getConnection make sure to implement
+     <code>closeConnection</code> to handle the connection you
+     generated.  Typically this would return the connection to the
+     pool it came from.
+
+     <li>Override <code>getLogStatement(LoggingEvent event)</code> to
+     produce specialized or dynamic statements. The default uses the
+     sql option value.
+
+    </ul>
+
+    @author Kevin Steppe (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
+
+*/
+public class JDBCAppender extends org.apache.log4j.AppenderSkeleton {
+
+  /**
+   * URL of the DB for default connection handling
+   */
+  protected String databaseURL = "jdbc:odbc:myDB";
+
+  /**
+   * User to connect as for default connection handling
+   */
+  protected String databaseUser = "me";
+
+  /**
+   * User to use for default connection handling
+   */
+  protected String databasePassword = "mypassword";
+
+  /**
+   * Connection used by default.  The connection is opened the first time it
+   * is needed and then held open until the appender is closed (usually at
+   * garbage collection).  This behavior is best modified by creating a
+   * sub-class and overriding the <code>getConnection</code> and
+   * <code>closeConnection</code> methods.
+   */
+  protected Connection connection = null;
+
+  /**
+   * Stores the string given to the pattern layout for conversion into a SQL
+   * statement, eg: insert into LogTable (Thread, Class, Message) values
+   * ("%t", "%c", "%m").
+   *
+   * Be careful of quotes in your messages!
+   *
+   * Also see PatternLayout.
+   */
+  protected String sqlStatement = "";
+
+  /**
+   * size of LoggingEvent buffer before writting to the database.
+   * Default is 1.
+   */
+  protected int bufferSize = 1;
+
+  /**
+   * ArrayList holding the buffer of Logging Events.
+   */
+  protected ArrayList buffer;
+
+  /**
+   * Helper object for clearing out the buffer
+   */
+  protected ArrayList removes;
+  
+  private boolean locationInfo = false;
+
+  public JDBCAppender() {
+    super();
+    buffer = new ArrayList(bufferSize);
+    removes = new ArrayList(bufferSize);
+  }
+
+  /**
+   * Gets whether the location of the logging request call
+   * should be captured.
+   *
+   * @since 1.2.16
+   * @return the current value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+  
+  /**
+   * The <b>LocationInfo</b> option takes a boolean value. By default, it is
+   * set to false which means there will be no effort to extract the location
+   * information related to the event. As a result, the event that will be
+   * ultimately logged will likely to contain the wrong location information
+   * (if present in the log format).
+   * <p/>
+   * <p/>
+   * Location information extraction is comparatively very slow and should be
+   * avoided unless performance is not a concern.
+   * </p>
+   * @since 1.2.16
+   * @param flag true if location information should be extracted.
+   */
+  public void setLocationInfo(final boolean flag) {
+    locationInfo = flag;
+  }
+  
+
+  /**
+   * Adds the event to the buffer.  When full the buffer is flushed.
+   */
+  public void append(LoggingEvent event) {
+    event.getNDC();
+    event.getThreadName();
+    // Get a copy of this thread's MDC.
+    event.getMDCCopy();
+    if (locationInfo) {
+      event.getLocationInformation();
+    }
+    event.getRenderedMessage();
+    event.getThrowableStrRep();
+    buffer.add(event);
+
+    if (buffer.size() >= bufferSize) {
+        flushBuffer();
+    }
+  }
+
+  /**
+   * By default getLogStatement sends the event to the required Layout object.
+   * The layout will format the given pattern into a workable SQL string.
+   *
+   * Overriding this provides direct access to the LoggingEvent
+   * when constructing the logging statement.
+   *
+   */
+  protected String getLogStatement(LoggingEvent event) {
+    return getLayout().format(event);
+  }
+
+  /**
+   *
+   * Override this to provide an alertnate method of getting
+   * connections (such as caching).  One method to fix this is to open
+   * connections at the start of flushBuffer() and close them at the
+   * end.  I use a connection pool outside of JDBCAppender which is
+   * accessed in an override of this method.
+   * */
+  protected void execute(String sql) throws SQLException {
+
+    Connection con = null;
+    Statement stmt = null;
+
+    try {
+        con = getConnection();
+
+        stmt = con.createStatement();
+        stmt.executeUpdate(sql);
+    } finally {
+        if(stmt != null) {
+            stmt.close();
+        }
+        closeConnection(con);
+    }
+
+    //System.out.println("Execute: " + sql);
+  }
+
+
+  /**
+   * Override this to return the connection to a pool, or to clean up the
+   * resource.
+   *
+   * The default behavior holds a single connection open until the appender
+   * is closed (typically when garbage collected).
+   */
+  protected void closeConnection(Connection con) {
+  }
+
+  /**
+   * Override this to link with your connection pooling system.
+   *
+   * By default this creates a single connection which is held open
+   * until the object is garbage collected.
+   */
+  protected Connection getConnection() throws SQLException {
+      if (!DriverManager.getDrivers().hasMoreElements()) {
+        setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
+    }
+
+      if (connection == null) {
+        connection = DriverManager.getConnection(databaseURL, databaseUser,
+                                       databasePassword);
+      }
+
+      return connection;
+  }
+
+  /**
+   * Closes the appender, flushing the buffer first then closing the default
+   * connection if it is open.
+   */
+  public void close()
+  {
+    flushBuffer();
+
+    try {
+      if (connection != null && !connection.isClosed()) {
+        connection.close();
+    }
+    } catch (SQLException e) {
+        errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
+    }
+    this.closed = true;
+  }
+
+  /**
+   * loops through the buffer of LoggingEvents, gets a
+   * sql string from getLogStatement() and sends it to execute().
+   * Errors are sent to the errorHandler.
+   *
+   * If a statement fails the LoggingEvent stays in the buffer!
+   */
+  public void flushBuffer() {
+    //Do the actual logging
+    removes.ensureCapacity(buffer.size());
+    for (Iterator i = buffer.iterator(); i.hasNext();) {
+      LoggingEvent logEvent = (LoggingEvent)i.next();
+      try {
+           String sql = getLogStatement(logEvent);
+           execute(sql);
+      }
+      catch (SQLException e) {
+           errorHandler.error("Failed to excute sql", e,
+                          ErrorCode.FLUSH_FAILURE);
+      } finally {
+        removes.add(logEvent);
+      }
+    }
+    
+    // remove from the buffer any events that were reported
+    buffer.removeAll(removes);
+    
+    // clear the buffer of reported events
+    removes.clear();
+  }
+
+
+  /** closes the appender before disposal */
+  public void finalize() {
+    close();
+  }
+
+
+  /**
+   * JDBCAppender requires a layout.
+   * */
+  public boolean requiresLayout() {
+    return true;
+  }
+
+
+  /**
+   *
+   */
+  public void setSql(String sql) {
+    sqlStatement = sql;
+    if (getLayout() == null) {
+        this.setLayout(new PatternLayout(sql));
+    }
+    else {
+        ((PatternLayout)getLayout()).setConversionPattern(sql);
+    }
+  }
+
+
+  /**
+   * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
+   */
+  public String getSql() {
+    return sqlStatement;
+  }
+
+
+  public void setUser(String user) {
+    databaseUser = user;
+  }
+
+
+  public void setURL(String url) {
+    databaseURL = url;
+  }
+
+
+  public void setPassword(String password) {
+    databasePassword = password;
+  }
+
+
+  public void setBufferSize(int newBufferSize) {
+    bufferSize = newBufferSize;
+    buffer.ensureCapacity(bufferSize);
+    removes.ensureCapacity(bufferSize);
+  }
+
+
+  public String getUser() {
+    return databaseUser;
+  }
+
+
+  public String getURL() {
+    return databaseURL;
+  }
+
+
+  public String getPassword() {
+    return databasePassword;
+  }
+
+
+  public int getBufferSize() {
+    return bufferSize;
+  }
+
+
+  /**
+   * Ensures that the given driver class has been loaded for sql connection
+   * creation.
+   */
+  public void setDriver(String driverClass) {
+    try {
+      Class.forName(driverClass);
+    } catch (Exception e) {
+      errorHandler.error("Failed to load driver", e,
+                        ErrorCode.GENERIC_FAILURE);
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/jdbc/package.html b/srcjar/org/apache/log4j/jdbc/package.html
new file mode 100644 (file)
index 0000000..e57e3a5
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+
+  <body>
+    The JDBCAppender provides for sending log events to a database. 
+  </body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/jmx/AbstractDynamicMBean.java b/srcjar/org/apache/log4j/jmx/AbstractDynamicMBean.java
new file mode 100644 (file)
index 0000000..d9d010a
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.Appender;
+
+public abstract class AbstractDynamicMBean implements DynamicMBean,
+                                                      MBeanRegistration {
+
+  String dClassName;
+  MBeanServer server;
+  private final Vector mbeanList = new Vector();
+
+    /**
+     * Get MBean name.
+     * @param appender appender, may not be null.
+     * @return name.
+     * @since 1.2.16
+     */
+  static protected String getAppenderName(final Appender appender){
+      String name = appender.getName();
+      if (name == null || name.trim().length() == 0) {
+          // try to get some form of a name, because null is not allowed (exception), and empty string certainly isn't useful in JMX..
+          name = appender.toString();
+      }
+      return name;
+  }
+      
+
+  /**
+   * Enables the to get the values of several attributes of the Dynamic MBean.
+   */
+  public
+  AttributeList getAttributes(String[] attributeNames) {
+
+    // Check attributeNames is not null to avoid NullPointerException later on
+    if (attributeNames == null) {
+      throw new RuntimeOperationsException(
+                          new IllegalArgumentException("attributeNames[] cannot be null"),
+                          "Cannot invoke a getter of " + dClassName);
+    }
+
+    AttributeList resultList = new AttributeList();
+
+    // if attributeNames is empty, return an empty result list
+    if (attributeNames.length == 0) {
+        return resultList;
+    }
+
+    // build the result attribute list
+    for (int i=0 ; i<attributeNames.length ; i++){
+      try {
+       Object value = getAttribute(attributeNames[i]);
+       resultList.add(new Attribute(attributeNames[i],value));
+      } catch (JMException e) {
+            e.printStackTrace();
+      } catch (RuntimeException e) {
+            e.printStackTrace();
+      }
+    }
+    return(resultList);
+  }
+
+  /**
+   * Sets the values of several attributes of the Dynamic MBean, and returns the
+   * list of attributes that have been set.
+   */
+  public AttributeList setAttributes(AttributeList attributes) {
+
+    // Check attributes is not null to avoid NullPointerException later on
+    if (attributes == null) {
+      throw new RuntimeOperationsException(
+                    new IllegalArgumentException("AttributeList attributes cannot be null"),
+                   "Cannot invoke a setter of " + dClassName);
+    }
+    AttributeList resultList = new AttributeList();
+
+    // if attributeNames is empty, nothing more to do
+    if (attributes.isEmpty()) {
+        return resultList;
+    }
+
+    // for each attribute, try to set it and add to the result list if successfull
+    for (Iterator i = attributes.iterator(); i.hasNext();) {
+      Attribute attr = (Attribute) i.next();
+      try {
+       setAttribute(attr);
+       String name = attr.getName();
+       Object value = getAttribute(name);
+       resultList.add(new Attribute(name,value));
+      } catch(JMException e) {
+           e.printStackTrace();
+      } catch(RuntimeException e) {
+           e.printStackTrace();
+      }
+    }
+    return(resultList);
+  }
+
+  protected
+  abstract
+  Logger getLogger();
+
+  public
+  void postDeregister() {
+    getLogger().debug("postDeregister is called.");
+  }
+
+  public
+  void postRegister(java.lang.Boolean registrationDone) {
+  }
+
+
+
+  public
+  ObjectName preRegister(MBeanServer server, ObjectName name) {
+    getLogger().debug("preRegister called. Server="+server+ ", name="+name);
+    this.server = server;
+    return name;
+  }
+  /**
+   * Registers MBean instance in the attached server. Must <em>NOT</em>
+   * be called before registration of this instance.
+   */
+  protected
+  void registerMBean(Object mbean, ObjectName objectName)
+  throws InstanceAlreadyExistsException, MBeanRegistrationException,
+                   NotCompliantMBeanException {
+    server.registerMBean(mbean, objectName);
+    mbeanList.add(objectName);
+  }
+
+  /**
+   * Performs cleanup for deregistering this MBean. Default implementation
+   * unregisters MBean instances which are registered using 
+   * {@link #registerMBean(Object mbean, ObjectName objectName)}.
+   */
+   public
+   void preDeregister() {
+     getLogger().debug("preDeregister called.");
+     
+    Enumeration iterator = mbeanList.elements();
+    while (iterator.hasMoreElements()) {
+      ObjectName name = (ObjectName) iterator.nextElement();
+      try {
+        server.unregisterMBean(name);
+      } catch (InstanceNotFoundException e) {
+   getLogger().warn("Missing MBean " + name.getCanonicalName());
+      } catch (MBeanRegistrationException e) {
+   getLogger().warn("Failed unregistering " + name.getCanonicalName());
+      }
+    }
+   }
+
+
+}
diff --git a/srcjar/org/apache/log4j/jmx/Agent.java b/srcjar/org/apache/log4j/jmx/Agent.java
new file mode 100644 (file)
index 0000000..9bf3c1a
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import org.apache.log4j.Logger;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import java.lang.reflect.InvocationTargetException;
+import java.io.InterruptedIOException;
+
+
+/**
+ * Manages an instance of com.sun.jdmk.comm.HtmlAdapterServer which
+ * was provided for demonstration purposes in the
+ * Java Management Extensions Reference Implementation 1.2.1.
+ * This class is provided to maintain compatibility with earlier
+ * versions of log4j and use in new code is discouraged.
+ *
+ * @deprecated
+ */
+public class Agent {
+
+    /**
+     * Diagnostic logger.
+     * @deprecated
+     */
+  static Logger log = Logger.getLogger(Agent.class);
+
+    /**
+     * Create new instance.
+     * @deprecated
+     */
+  public Agent() {
+  }
+
+    /**
+     * Creates a new instance of com.sun.jdmk.comm.HtmlAdapterServer
+     * using reflection.
+     *
+     * @since 1.2.16
+     * @return new instance.
+     */
+  private static Object createServer() {
+      Object newInstance = null;
+      try {
+        newInstance = Class.forName(
+                "com.sun.jdmk.comm.HtmlAdapterServer").newInstance();
+      } catch (ClassNotFoundException ex) {
+          throw new RuntimeException(ex.toString());
+      } catch (InstantiationException ex) {
+          throw new RuntimeException(ex.toString());
+      } catch (IllegalAccessException ex) {
+          throw new RuntimeException(ex.toString());
+      }
+      return newInstance;
+  }
+
+    /**
+     * Invokes HtmlAdapterServer.start() using reflection.
+     *
+     * @since 1.2.16
+     * @param server instance of com.sun.jdmk.comm.HtmlAdapterServer.
+     */
+  private static void startServer(final Object server) {
+      try {
+          server.getClass().getMethod("start", new Class[0]).
+                  invoke(server, new Object[0]);
+      } catch(InvocationTargetException ex) {
+          Throwable cause = ex.getTargetException();
+          if (cause instanceof RuntimeException) {
+              throw (RuntimeException) cause;
+          } else if (cause != null) {
+              if (cause instanceof InterruptedException
+                      || cause instanceof InterruptedIOException) {
+                  Thread.currentThread().interrupt();
+              }
+              throw new RuntimeException(cause.toString());
+          } else {
+              throw new RuntimeException();
+          }
+      } catch(NoSuchMethodException ex) {
+          throw new RuntimeException(ex.toString());
+      } catch(IllegalAccessException ex) {
+        throw new RuntimeException(ex.toString());
+    }
+  }
+
+
+    /**
+     * Starts instance of HtmlAdapterServer.
+     * @deprecated
+      */
+  public void start() {
+
+    MBeanServer server = MBeanServerFactory.createMBeanServer();
+    Object html = createServer();
+
+    try {
+      log.info("Registering HtmlAdaptorServer instance.");
+      server.registerMBean(html, new ObjectName("Adaptor:name=html,port=8082"));
+      log.info("Registering HierarchyDynamicMBean instance.");
+      HierarchyDynamicMBean hdm = new HierarchyDynamicMBean();
+      server.registerMBean(hdm, new ObjectName("log4j:hiearchy=default"));
+    } catch(JMException e) {
+      log.error("Problem while registering MBeans instances.", e);
+      return;
+    } catch(RuntimeException e) {
+      log.error("Problem while registering MBeans instances.", e);
+      return;
+    }
+    startServer(html);
+  }
+}
diff --git a/srcjar/org/apache/log4j/jmx/AppenderDynamicMBean.java b/srcjar/org/apache/log4j/jmx/AppenderDynamicMBean.java
new file mode 100644 (file)
index 0000000..56d30e6
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.OptionHandler;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.io.InterruptedIOException;
+
+public class AppenderDynamicMBean extends AbstractDynamicMBean {
+
+  private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+  private Vector dAttributes = new Vector();
+  private String dClassName = this.getClass().getName();
+
+  private Hashtable dynamicProps = new Hashtable(5);
+  private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[2];
+  private String dDescription =
+     "This MBean acts as a management facade for log4j appenders.";
+
+  // This category instance is for logging.
+  private static Logger cat = Logger.getLogger(AppenderDynamicMBean.class);
+
+  // We wrap this appender instance.
+  private Appender appender;
+
+  public  AppenderDynamicMBean(Appender appender) throws IntrospectionException {
+    this.appender = appender;
+    buildDynamicMBeanInfo();
+  }
+
+  private
+  void buildDynamicMBeanInfo() throws IntrospectionException {
+    Constructor[] constructors = this.getClass().getConstructors();
+    dConstructors[0] = new MBeanConstructorInfo(
+             "AppenderDynamicMBean(): Constructs a AppenderDynamicMBean instance",
+            constructors[0]);
+
+
+    BeanInfo bi = Introspector.getBeanInfo(appender.getClass());
+    PropertyDescriptor[] pd = bi.getPropertyDescriptors();
+
+    int size = pd.length;
+
+    for(int i = 0; i < size; i++) {
+      String name = pd[i].getName();
+      Method readMethod =  pd[i].getReadMethod();
+      Method writeMethod =  pd[i].getWriteMethod();
+      if(readMethod != null) {
+       Class returnClass = readMethod.getReturnType();
+       if(isSupportedType(returnClass)) {
+         String returnClassName;
+         if(returnClass.isAssignableFrom(Priority.class)) {
+           returnClassName = "java.lang.String";
+         } else {
+           returnClassName = returnClass.getName();
+         }
+
+         dAttributes.add(new MBeanAttributeInfo(name,
+                                                returnClassName,
+                                                "Dynamic",
+                                                true,
+                                                writeMethod != null,
+                                                false));
+         dynamicProps.put(name, new MethodUnion(readMethod, writeMethod));
+       }
+      }
+    }
+
+    MBeanParameterInfo[] params = new MBeanParameterInfo[0];
+
+    dOperations[0] = new MBeanOperationInfo("activateOptions",
+                                           "activateOptions(): add an appender",
+                                           params,
+                                           "void",
+                                           MBeanOperationInfo.ACTION);
+
+    params = new MBeanParameterInfo[1];
+    params[0] = new MBeanParameterInfo("layout class", "java.lang.String",
+                                      "layout class");
+
+    dOperations[1] = new MBeanOperationInfo("setLayout",
+                                           "setLayout(): add a layout",
+                                           params,
+                                           "void",
+                                           MBeanOperationInfo.ACTION);
+  }
+
+  private
+  boolean isSupportedType(Class clazz) {
+    if(clazz.isPrimitive()) {
+      return true;
+    }
+
+    if(clazz == String.class) {
+      return true;
+    }
+
+
+    if(clazz.isAssignableFrom(Priority.class)) {
+      return true;
+    }
+
+    return false;
+
+
+  }
+
+
+
+  public
+  MBeanInfo getMBeanInfo() {
+    cat.debug("getMBeanInfo called.");
+
+    MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+    dAttributes.toArray(attribs);
+
+    return new MBeanInfo(dClassName,
+                        dDescription,
+                        attribs,
+                        dConstructors,
+                        dOperations,
+                        new MBeanNotificationInfo[0]);
+  }
+
+  public
+  Object invoke(String operationName, Object params[], String signature[])
+    throws MBeanException,
+    ReflectionException {
+
+    if(operationName.equals("activateOptions") &&
+                     appender instanceof OptionHandler) {
+      OptionHandler oh = (OptionHandler) appender;
+      oh.activateOptions();
+      return "Options activated.";
+    } else if (operationName.equals("setLayout")) {
+      Layout layout = (Layout) OptionConverter.instantiateByClassName((String)
+                                                                     params[0],
+                                                                     Layout.class,
+                                                                     null);
+      appender.setLayout(layout);
+      registerLayoutMBean(layout);
+    }
+    return null;
+  }
+
+  void registerLayoutMBean(Layout layout) {
+    if(layout == null) {
+        return;
+    }
+
+    String name = getAppenderName(appender)+",layout="+layout.getClass().getName();
+    cat.debug("Adding LayoutMBean:"+name);
+    ObjectName objectName = null;
+    try {
+      LayoutDynamicMBean appenderMBean = new LayoutDynamicMBean(layout);
+      objectName = new ObjectName("log4j:appender="+name);
+      if (!server.isRegistered(objectName)) {
+        registerMBean(appenderMBean, objectName);
+        dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName",
+                "The " + name + " layout.", true, true, false));
+      }
+
+    } catch(JMException e) {
+      cat.error("Could not add DynamicLayoutMBean for ["+name+"].", e);
+    } catch(java.beans.IntrospectionException e) {
+      cat.error("Could not add DynamicLayoutMBean for ["+name+"].", e);
+    } catch(RuntimeException e) {
+      cat.error("Could not add DynamicLayoutMBean for ["+name+"].", e);
+    }
+  }
+
+  protected
+  Logger getLogger() {
+    return cat;
+  }
+
+
+  public
+  Object getAttribute(String attributeName) throws AttributeNotFoundException,
+                                                   MBeanException,
+                                                   ReflectionException {
+
+       // Check attributeName is not null to avoid NullPointerException later on
+    if (attributeName == null) {
+      throw new RuntimeOperationsException(new IllegalArgumentException(
+                       "Attribute name cannot be null"),
+       "Cannot invoke a getter of " + dClassName + " with null attribute name");
+    }
+
+    cat.debug("getAttribute called with ["+attributeName+"].");
+    if(attributeName.startsWith("appender="+appender.getName()+",layout")) {
+      try {
+           return new ObjectName("log4j:"+attributeName );
+      } catch(MalformedObjectNameException e) {
+           cat.error("attributeName", e);
+      } catch(RuntimeException e) {
+           cat.error("attributeName", e);
+      }
+    }
+
+    MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName);
+
+    //cat.debug("----name="+attributeName+", b="+b);
+
+    if(mu != null && mu.readMethod != null) {
+      try {
+       return mu.readMethod.invoke(appender, null);
+      } catch(IllegalAccessException e) {
+           return null;
+      } catch(InvocationTargetException e) {
+          if (e.getTargetException() instanceof InterruptedException
+                  || e.getTargetException() instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+           return null;
+      } catch(RuntimeException e) {
+           return null;
+      }
+    }
+
+
+
+    // If attributeName has not been recognized throw an AttributeNotFoundException
+    throw(new AttributeNotFoundException("Cannot find " + attributeName +
+                                        " attribute in " + dClassName));
+
+  }
+
+
+  public
+  void setAttribute(Attribute attribute) throws AttributeNotFoundException,
+                                                InvalidAttributeValueException,
+                                                MBeanException,
+                                                ReflectionException {
+
+    // Check attribute is not null to avoid NullPointerException later on
+    if (attribute == null) {
+      throw new RuntimeOperationsException(
+                  new IllegalArgumentException("Attribute cannot be null"),
+                 "Cannot invoke a setter of " + dClassName +
+                 " with null attribute");
+    }
+    String name = attribute.getName();
+    Object value = attribute.getValue();
+
+    if (name == null) {
+      throw new RuntimeOperationsException(
+                    new IllegalArgumentException("Attribute name cannot be null"),
+                   "Cannot invoke the setter of "+dClassName+
+                   " with null attribute name");
+    }
+
+
+
+    MethodUnion mu = (MethodUnion) dynamicProps.get(name);
+
+    if(mu != null && mu.writeMethod != null) {
+      Object[] o = new Object[1];
+
+      Class[] params = mu.writeMethod.getParameterTypes();
+      if(params[0] == org.apache.log4j.Priority.class) {
+       value = OptionConverter.toLevel((String) value,
+                                       (Level) getAttribute(name));
+      }
+      o[0] = value;
+
+      try {
+       mu.writeMethod.invoke(appender,  o);
+
+      } catch(InvocationTargetException e) {
+        if (e.getTargetException() instanceof InterruptedException
+                || e.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+        cat.error("FIXME", e);
+      } catch(IllegalAccessException e) {
+           cat.error("FIXME", e);
+      } catch(RuntimeException e) {
+           cat.error("FIXME", e);
+      }
+    } else if(name.endsWith(".layout")) {
+
+    } else {
+      throw(new AttributeNotFoundException("Attribute " + name +
+                                          " not found in " +
+                                          this.getClass().getName()));
+    }
+  }
+
+  public
+  ObjectName preRegister(MBeanServer server, ObjectName name) {
+    cat.debug("preRegister called. Server="+server+ ", name="+name);
+    this.server = server;
+    registerLayoutMBean(appender.getLayout());
+
+    return name;
+  }
+
+
+}
+
diff --git a/srcjar/org/apache/log4j/jmx/HierarchyDynamicMBean.java b/srcjar/org/apache/log4j/jmx/HierarchyDynamicMBean.java
new file mode 100644 (file)
index 0000000..5449c99
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.spi.LoggerRepository;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationFilterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import java.lang.reflect.Constructor;
+import java.util.Vector;
+
+public class HierarchyDynamicMBean extends AbstractDynamicMBean
+                                   implements HierarchyEventListener,
+                                              NotificationBroadcaster {
+
+  static final String ADD_APPENDER = "addAppender.";
+  static final String THRESHOLD = "threshold";
+
+  private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+  private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+
+  private Vector vAttributes = new Vector();
+  private String dClassName = this.getClass().getName();
+  private String dDescription =
+     "This MBean acts as a management facade for org.apache.log4j.Hierarchy.";
+
+  private NotificationBroadcasterSupport nbs = new NotificationBroadcasterSupport();
+
+
+  private LoggerRepository hierarchy;
+
+  private static Logger log = Logger.getLogger(HierarchyDynamicMBean.class);
+
+  public HierarchyDynamicMBean() {
+    hierarchy = LogManager.getLoggerRepository();
+    buildDynamicMBeanInfo();
+  }
+
+  private
+  void buildDynamicMBeanInfo() {
+    Constructor[] constructors = this.getClass().getConstructors();
+    dConstructors[0] = new MBeanConstructorInfo(
+         "HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance",
+        constructors[0]);
+
+    vAttributes.add(new MBeanAttributeInfo(THRESHOLD,
+                                          "java.lang.String",
+                                          "The \"threshold\" state of the hiearchy.",
+                                          true,
+                                          true,
+                                          false));
+
+    MBeanParameterInfo[] params = new MBeanParameterInfo[1];
+    params[0] = new MBeanParameterInfo("name", "java.lang.String",
+                                      "Create a logger MBean" );
+    dOperations[0] = new MBeanOperationInfo("addLoggerMBean",
+                                   "addLoggerMBean(): add a loggerMBean",
+                                   params ,
+                                   "javax.management.ObjectName",
+                                   MBeanOperationInfo.ACTION);
+  }
+
+
+  public
+  ObjectName addLoggerMBean(String name) {
+    Logger cat = LogManager.exists(name);
+
+    if(cat != null) {
+      return addLoggerMBean(cat);
+    } else {
+      return null;
+    }
+  }
+
+  ObjectName addLoggerMBean(Logger logger) {
+    String name = logger.getName();
+    ObjectName objectName = null;
+    try {
+      LoggerDynamicMBean loggerMBean = new LoggerDynamicMBean(logger);
+      objectName = new ObjectName("log4j", "logger", name);
+      
+      if (!server.isRegistered(objectName)) {
+        registerMBean(loggerMBean, objectName);
+        NotificationFilterSupport nfs = new NotificationFilterSupport();
+        nfs.enableType(ADD_APPENDER + logger.getName());
+        log.debug("---Adding logger [" + name + "] as listener.");
+        nbs.addNotificationListener(loggerMBean, nfs, null);
+        vAttributes.add(new MBeanAttributeInfo("logger=" + name, "javax.management.ObjectName",
+                "The " + name + " logger.", true, true, // this makes the object
+                // clickable
+                false));
+        
+      }
+
+    } catch(JMException e) {
+      log.error("Could not add loggerMBean for ["+name+"].", e);
+    } catch(RuntimeException e) {
+      log.error("Could not add loggerMBean for ["+name+"].", e);
+    }
+    return objectName;
+  }
+
+  public
+  void addNotificationListener(NotificationListener listener,
+                              NotificationFilter filter,
+                              java.lang.Object handback) {
+    nbs.addNotificationListener(listener, filter, handback);
+  }
+
+  protected
+  Logger getLogger() {
+    return log;
+  }
+
+  public
+  MBeanInfo getMBeanInfo() {
+    //cat.debug("getMBeanInfo called.");
+
+    MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[vAttributes.size()];
+    vAttributes.toArray(attribs);
+
+    return new MBeanInfo(dClassName,
+                        dDescription,
+                        attribs,
+                        dConstructors,
+                        dOperations,
+                        new MBeanNotificationInfo[0]);
+  }
+
+  public
+  MBeanNotificationInfo[] getNotificationInfo(){
+    return nbs.getNotificationInfo();
+  }
+
+  public
+  Object invoke(String operationName,
+               Object params[],
+               String signature[]) throws MBeanException,
+                                           ReflectionException {
+
+    if (operationName == null) {
+      throw new RuntimeOperationsException(
+        new IllegalArgumentException("Operation name cannot be null"),
+       "Cannot invoke a null operation in " + dClassName);
+    }
+    // Check for a recognized operation name and call the corresponding operation
+
+    if(operationName.equals("addLoggerMBean")) {
+      return addLoggerMBean((String)params[0]);
+    } else {
+      throw new ReflectionException(
+           new NoSuchMethodException(operationName),
+           "Cannot find the operation " + operationName + " in " + dClassName);
+    }
+
+  }
+
+
+  public
+  Object getAttribute(String attributeName) throws AttributeNotFoundException,
+                                                    MBeanException,
+                                                    ReflectionException {
+
+    // Check attributeName is not null to avoid NullPointerException later on
+    if (attributeName == null) {
+      throw new RuntimeOperationsException(new IllegalArgumentException(
+                       "Attribute name cannot be null"),
+       "Cannot invoke a getter of " + dClassName + " with null attribute name");
+    }
+
+    log.debug("Called getAttribute with ["+attributeName+"].");
+
+    // Check for a recognized attributeName and call the corresponding getter
+    if (attributeName.equals(THRESHOLD)) {
+      return hierarchy.getThreshold();
+    } else if(attributeName.startsWith("logger")) {
+      int k = attributeName.indexOf("%3D");
+      String val = attributeName;
+      if(k > 0) {
+       val = attributeName.substring(0, k)+'='+ attributeName.substring(k+3);
+      }
+      try {
+       return new ObjectName("log4j:"+val);
+      } catch(JMException e) {
+           log.error("Could not create ObjectName" + val);
+      } catch(RuntimeException e) {
+           log.error("Could not create ObjectName" + val);
+      }
+    }
+
+
+
+    // If attributeName has not been recognized throw an AttributeNotFoundException
+    throw(new AttributeNotFoundException("Cannot find " + attributeName +
+                                        " attribute in " + dClassName));
+
+  }
+
+
+  public
+  void addAppenderEvent(Category logger, Appender appender) {
+    log.debug("addAppenderEvent called: logger="+logger.getName()+
+             ", appender="+appender.getName());
+    Notification n = new Notification(ADD_APPENDER+logger.getName(), this, 0);
+    n.setUserData(appender);
+    log.debug("sending notification.");
+    nbs.sendNotification(n);
+  }
+
+ public
+  void removeAppenderEvent(Category cat, Appender appender) {
+    log.debug("removeAppenderCalled: logger="+cat.getName()+
+             ", appender="+appender.getName());
+  }
+
+  public
+  void postRegister(java.lang.Boolean registrationDone) {
+    log.debug("postRegister is called.");
+    hierarchy.addHierarchyEventListener(this);
+    Logger root = hierarchy.getRootLogger();
+    addLoggerMBean(root);
+  }
+
+  public
+  void removeNotificationListener(NotificationListener listener)
+                                         throws ListenerNotFoundException {
+    nbs.removeNotificationListener(listener);
+  }
+
+  public
+  void setAttribute(Attribute attribute) throws AttributeNotFoundException,
+                                                InvalidAttributeValueException,
+                                                MBeanException,
+                                                ReflectionException {
+
+    // Check attribute is not null to avoid NullPointerException later on
+    if (attribute == null) {
+      throw new RuntimeOperationsException(
+                  new IllegalArgumentException("Attribute cannot be null"),
+         "Cannot invoke a setter of "+dClassName+" with null attribute");
+    }
+    String name = attribute.getName();
+    Object value = attribute.getValue();
+
+    if (name == null) {
+      throw new RuntimeOperationsException(
+               new IllegalArgumentException("Attribute name cannot be null"),
+              "Cannot invoke the setter of "+dClassName+
+              " with null attribute name");
+    }
+
+    if(name.equals(THRESHOLD)) {
+      Level l = OptionConverter.toLevel((String) value,
+                                          hierarchy.getThreshold());
+      hierarchy.setThreshold(l);
+    }
+
+
+  }
+}
diff --git a/srcjar/org/apache/log4j/jmx/LayoutDynamicMBean.java b/srcjar/org/apache/log4j/jmx/LayoutDynamicMBean.java
new file mode 100644 (file)
index 0000000..1d7a3cf
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import java.lang.reflect.Constructor;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.OptionHandler;
+
+import java.util.Vector;
+import java.util.Hashtable;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanInfo;
+import javax.management.Attribute;
+
+import javax.management.MBeanException;
+import javax.management.AttributeNotFoundException;
+import javax.management.RuntimeOperationsException;
+import javax.management.ReflectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+
+import java.beans.Introspector;
+import java.beans.BeanInfo;
+import java.beans.PropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.io.InterruptedIOException;
+
+public class LayoutDynamicMBean extends AbstractDynamicMBean {
+
+  private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+  private Vector dAttributes = new Vector();
+  private String dClassName = this.getClass().getName();
+
+  private Hashtable dynamicProps = new Hashtable(5);
+  private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+  private String dDescription =
+     "This MBean acts as a management facade for log4j layouts.";
+
+  // This category instance is for logging.
+  private static Logger cat = Logger.getLogger(LayoutDynamicMBean.class);
+
+  // We wrap this layout instance.
+  private Layout layout;
+
+  public  LayoutDynamicMBean(Layout layout) throws IntrospectionException {
+    this.layout = layout;
+    buildDynamicMBeanInfo();
+  }
+
+  private
+  void buildDynamicMBeanInfo() throws IntrospectionException {
+    Constructor[] constructors = this.getClass().getConstructors();
+    dConstructors[0] = new MBeanConstructorInfo(
+             "LayoutDynamicMBean(): Constructs a LayoutDynamicMBean instance",
+            constructors[0]);
+
+
+    BeanInfo bi = Introspector.getBeanInfo(layout.getClass());
+    PropertyDescriptor[] pd = bi.getPropertyDescriptors();
+
+    int size = pd.length;
+
+    for(int i = 0; i < size; i++) {
+      String name = pd[i].getName();
+      Method readMethod =  pd[i].getReadMethod();
+      Method writeMethod =  pd[i].getWriteMethod();
+      if(readMethod != null) {
+       Class returnClass = readMethod.getReturnType();
+       if(isSupportedType(returnClass)) {
+         String returnClassName;
+         if(returnClass.isAssignableFrom(Level.class)) {
+           returnClassName = "java.lang.String";
+         } else {
+           returnClassName = returnClass.getName();
+         }
+
+         dAttributes.add(new MBeanAttributeInfo(name,
+                                                returnClassName,
+                                                "Dynamic",
+                                                true,
+                                                writeMethod != null,
+                                                false));
+         dynamicProps.put(name, new MethodUnion(readMethod, writeMethod));
+       }
+      }
+    }
+
+    MBeanParameterInfo[] params = new MBeanParameterInfo[0];
+
+    dOperations[0] = new MBeanOperationInfo("activateOptions",
+                                           "activateOptions(): add an layout",
+                                           params,
+                                           "void",
+                                           MBeanOperationInfo.ACTION);
+  }
+
+  private
+  boolean isSupportedType(Class clazz) {
+    if(clazz.isPrimitive()) {
+      return true;
+    }
+
+    if(clazz == String.class) {
+      return true;
+    }
+    if(clazz.isAssignableFrom(Level.class)) {
+      return true;
+    }
+
+    return false;
+  }
+
+
+
+  public
+  MBeanInfo getMBeanInfo() {
+    cat.debug("getMBeanInfo called.");
+
+    MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+    dAttributes.toArray(attribs);
+
+    return new MBeanInfo(dClassName,
+                        dDescription,
+                        attribs,
+                        dConstructors,
+                        dOperations,
+                        new MBeanNotificationInfo[0]);
+  }
+
+  public
+  Object invoke(String operationName, Object params[], String signature[])
+    throws MBeanException,
+    ReflectionException {
+
+    if(operationName.equals("activateOptions") &&
+                     layout instanceof OptionHandler) {
+      OptionHandler oh = layout;
+      oh.activateOptions();
+      return "Options activated.";
+    }
+    return null;
+  }
+
+  protected
+  Logger  getLogger() {
+    return cat;
+  }
+
+
+  public
+  Object getAttribute(String attributeName) throws AttributeNotFoundException,
+                                                   MBeanException,
+                                                   ReflectionException {
+
+       // Check attributeName is not null to avoid NullPointerException later on
+    if (attributeName == null) {
+      throw new RuntimeOperationsException(new IllegalArgumentException(
+                       "Attribute name cannot be null"),
+       "Cannot invoke a getter of " + dClassName + " with null attribute name");
+    }
+
+
+    MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName);
+
+    cat.debug("----name="+attributeName+", mu="+mu);
+
+    if(mu != null && mu.readMethod != null) {
+      try {
+       return mu.readMethod.invoke(layout, null);
+      } catch(InvocationTargetException e) {
+          if (e.getTargetException() instanceof InterruptedException
+                  || e.getTargetException() instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+           return null;
+      } catch(IllegalAccessException e) {
+           return null;
+      } catch(RuntimeException e) {
+           return null;
+      }
+    }
+
+
+
+    // If attributeName has not been recognized throw an AttributeNotFoundException
+    throw(new AttributeNotFoundException("Cannot find " + attributeName +
+                                        " attribute in " + dClassName));
+
+  }
+
+
+  public
+  void setAttribute(Attribute attribute) throws AttributeNotFoundException,
+                                                InvalidAttributeValueException,
+                                                MBeanException,
+                                                ReflectionException {
+
+    // Check attribute is not null to avoid NullPointerException later on
+    if (attribute == null) {
+      throw new RuntimeOperationsException(
+                  new IllegalArgumentException("Attribute cannot be null"),
+                 "Cannot invoke a setter of " + dClassName +
+                 " with null attribute");
+    }
+    String name = attribute.getName();
+    Object value = attribute.getValue();
+
+    if (name == null) {
+      throw new RuntimeOperationsException(
+                    new IllegalArgumentException("Attribute name cannot be null"),
+                   "Cannot invoke the setter of "+dClassName+
+                   " with null attribute name");
+    }
+
+
+
+    MethodUnion mu = (MethodUnion) dynamicProps.get(name);
+
+    if(mu != null && mu.writeMethod != null) {
+      Object[] o = new Object[1];
+
+      Class[] params = mu.writeMethod.getParameterTypes();
+      if(params[0] == org.apache.log4j.Priority.class) {
+       value = OptionConverter.toLevel((String) value,
+                                       (Level) getAttribute(name));
+      }
+      o[0] = value;
+
+      try {
+       mu.writeMethod.invoke(layout,  o);
+
+      } catch(InvocationTargetException e) {
+          if (e.getTargetException() instanceof InterruptedException
+                  || e.getTargetException() instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+           cat.error("FIXME", e);
+      } catch(IllegalAccessException e) {
+           cat.error("FIXME", e);
+      } catch(RuntimeException e) {
+           cat.error("FIXME", e);
+      }
+    } else {
+      throw(new AttributeNotFoundException("Attribute " + name +
+                                          " not found in " +
+                                          this.getClass().getName()));
+    }
+  }
+}
+
+
diff --git a/srcjar/org/apache/log4j/jmx/LoggerDynamicMBean.java b/srcjar/org/apache/log4j/jmx/LoggerDynamicMBean.java
new file mode 100644 (file)
index 0000000..b226319
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.OptionConverter;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import java.lang.reflect.Constructor;
+import java.util.Enumeration;
+import java.util.Vector;
+
+public class LoggerDynamicMBean extends AbstractDynamicMBean
+                                  implements NotificationListener {
+
+  private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+  private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+
+  private Vector dAttributes = new Vector();
+  private String dClassName = this.getClass().getName();
+
+  private String dDescription =
+     "This MBean acts as a management facade for a org.apache.log4j.Logger instance.";
+
+  // This Logger instance is for logging.
+  private static Logger cat = Logger.getLogger(LoggerDynamicMBean.class);
+
+  // We wrap this Logger instance.
+  private Logger logger;
+
+  public LoggerDynamicMBean(Logger logger) {
+    this.logger = logger;
+    buildDynamicMBeanInfo();
+  }
+
+  public
+  void handleNotification(Notification notification, Object handback) {
+    cat.debug("Received notification: "+notification.getType());
+    registerAppenderMBean((Appender) notification.getUserData() );
+
+
+  }
+
+  private
+  void buildDynamicMBeanInfo() {
+    Constructor[] constructors = this.getClass().getConstructors();
+    dConstructors[0] = new MBeanConstructorInfo(
+             "HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance",
+            constructors[0]);
+
+    dAttributes.add(new MBeanAttributeInfo("name",
+                                          "java.lang.String",
+                                          "The name of this Logger.",
+                                          true,
+                                          false,
+                                          false));
+
+    dAttributes.add(new MBeanAttributeInfo("priority",
+                                          "java.lang.String",
+                                          "The priority of this logger.",
+                                          true,
+                                          true,
+                                          false));
+
+
+
+
+
+    MBeanParameterInfo[] params = new MBeanParameterInfo[2];
+    params[0] = new MBeanParameterInfo("class name", "java.lang.String",
+                                      "add an appender to this logger");
+    params[1] = new MBeanParameterInfo("appender name", "java.lang.String",
+                                      "name of the appender");
+
+    dOperations[0] = new MBeanOperationInfo("addAppender",
+                                           "addAppender(): add an appender",
+                                           params,
+                                           "void",
+                                           MBeanOperationInfo.ACTION);
+  }
+
+  protected
+  Logger getLogger() {
+    return logger;
+  }
+
+
+  public
+  MBeanInfo getMBeanInfo() {
+    //cat.debug("getMBeanInfo called.");
+
+    MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+    dAttributes.toArray(attribs);
+
+    MBeanInfo mb = new MBeanInfo(dClassName,
+                        dDescription,
+                        attribs,
+                        dConstructors,
+                        dOperations,
+                        new MBeanNotificationInfo[0]);
+    //cat.debug("getMBeanInfo exit.");
+    return mb;
+  }
+
+  public
+  Object invoke(String operationName, Object params[], String signature[])
+    throws MBeanException,
+    ReflectionException {
+
+    if(operationName.equals("addAppender")) {
+      addAppender((String) params[0], (String) params[1]);
+      return "Hello world.";
+    }
+
+    return null;
+  }
+
+
+  public
+  Object getAttribute(String attributeName) throws AttributeNotFoundException,
+                                                   MBeanException,
+                                                   ReflectionException {
+
+       // Check attributeName is not null to avoid NullPointerException later on
+    if (attributeName == null) {
+      throw new RuntimeOperationsException(new IllegalArgumentException(
+                       "Attribute name cannot be null"),
+       "Cannot invoke a getter of " + dClassName + " with null attribute name");
+    }
+
+    // Check for a recognized attributeName and call the corresponding getter
+    if (attributeName.equals("name")) {
+      return logger.getName();
+    }  else if(attributeName.equals("priority")) {
+      Level l = logger.getLevel();
+      if(l == null) {
+       return null;
+      } else {
+       return l.toString();
+      }
+    } else if(attributeName.startsWith("appender=")) {
+      try {
+       return new ObjectName("log4j:"+attributeName );
+      } catch(MalformedObjectNameException e) {
+           cat.error("Could not create ObjectName" + attributeName);
+      } catch(RuntimeException e) {
+           cat.error("Could not create ObjectName" + attributeName);
+      }
+    }
+
+
+    // If attributeName has not been recognized throw an AttributeNotFoundException
+    throw(new AttributeNotFoundException("Cannot find " + attributeName +
+                                        " attribute in " + dClassName));
+
+  }
+
+
+  void addAppender(String appenderClass, String appenderName) {
+    cat.debug("addAppender called with "+appenderClass+", "+appenderName);
+    Appender appender = (Appender)
+       OptionConverter.instantiateByClassName(appenderClass,
+                                             org.apache.log4j.Appender.class,
+                                             null);
+    appender.setName(appenderName);
+    logger.addAppender(appender);
+
+    //appenderMBeanRegistration();
+
+  }
+
+
+  public
+  void setAttribute(Attribute attribute) throws AttributeNotFoundException,
+                                                InvalidAttributeValueException,
+                                                MBeanException,
+                                                ReflectionException {
+
+    // Check attribute is not null to avoid NullPointerException later on
+    if (attribute == null) {
+      throw new RuntimeOperationsException(
+                  new IllegalArgumentException("Attribute cannot be null"),
+                 "Cannot invoke a setter of " + dClassName +
+                 " with null attribute");
+    }
+    String name = attribute.getName();
+    Object value = attribute.getValue();
+
+    if (name == null) {
+      throw new RuntimeOperationsException(
+                    new IllegalArgumentException("Attribute name cannot be null"),
+                   "Cannot invoke the setter of "+dClassName+
+                   " with null attribute name");
+    }
+
+
+    if(name.equals("priority")) {
+      if (value instanceof String) {
+       String s = (String) value;
+       Level p = logger.getLevel();
+       if(s.equalsIgnoreCase("NULL")) {
+         p = null;
+       } else {
+         p = OptionConverter.toLevel(s, p);
+       }
+       logger.setLevel(p);
+      }
+    } else {
+      throw(new AttributeNotFoundException("Attribute " + name +
+                                          " not found in " +
+                                          this.getClass().getName()));
+    }
+  }
+
+  void appenderMBeanRegistration() {
+    Enumeration enumeration = logger.getAllAppenders();
+    while(enumeration.hasMoreElements()) {
+      Appender appender = (Appender) enumeration.nextElement();
+      registerAppenderMBean(appender);
+    }
+  }
+
+  void registerAppenderMBean(Appender appender) {
+    String name = getAppenderName(appender);
+    cat.debug("Adding AppenderMBean for appender named "+name);
+    ObjectName objectName = null;
+    try {
+      AppenderDynamicMBean appenderMBean = new AppenderDynamicMBean(appender);
+      objectName = new ObjectName("log4j", "appender", name);
+      if (!server.isRegistered(objectName)) {
+        registerMBean(appenderMBean, objectName);
+        dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName",
+                "The " + name + " appender.", true, true, false));
+      }
+
+    } catch(JMException e) {
+      cat.error("Could not add appenderMBean for ["+name+"].", e);
+    } catch(java.beans.IntrospectionException e) {
+      cat.error("Could not add appenderMBean for ["+name+"].", e);
+    } catch(RuntimeException e) {
+      cat.error("Could not add appenderMBean for ["+name+"].", e);
+    }
+  }
+
+  public
+  void postRegister(java.lang.Boolean registrationDone) {
+    appenderMBeanRegistration();
+  }
+}
diff --git a/srcjar/org/apache/log4j/jmx/MethodUnion.java b/srcjar/org/apache/log4j/jmx/MethodUnion.java
new file mode 100644 (file)
index 0000000..e9f2fb2
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jmx;
+
+import java.lang.reflect.Method;
+
+class MethodUnion {
+
+  Method readMethod;
+  Method writeMethod;
+
+  MethodUnion( Method readMethod, Method writeMethod) {
+    this.readMethod = readMethod;
+    this.writeMethod = writeMethod;
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/jmx/package.html b/srcjar/org/apache/log4j/jmx/package.html
new file mode 100644 (file)
index 0000000..6d1583a
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+
+  <body>
+    This package lets you manage log4j settings using JMX. It is
+    unfortunately not of production quality.
+  </body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/net/JMSAppender.java b/srcjar/org/apache/log4j/net/JMSAppender.java
new file mode 100644 (file)
index 0000000..7e1e62e
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+
+import javax.jms.JMSException;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.util.Properties;
+
+/**
+ * A simple appender that publishes events to a JMS Topic. The events
+ * are serialized and transmitted as JMS message type {@link
+ * ObjectMessage}.
+
+ * <p>JMS {@link Topic topics} and {@link TopicConnectionFactory topic
+ * connection factories} are administered objects that are retrieved
+ * using JNDI messaging which in turn requires the retrieval of a JNDI
+ * {@link Context}.
+
+ * <p>There are two common methods for retrieving a JNDI {@link
+ * Context}. If a file resource named <em>jndi.properties</em> is
+ * available to the JNDI API, it will use the information found
+ * therein to retrieve an initial JNDI context. To obtain an initial
+ * context, your code will simply call:
+
+   <pre>
+   InitialContext jndiContext = new InitialContext();
+   </pre>
+  
+ * <p>Calling the no-argument <code>InitialContext()</code> method
+ * will also work from within Enterprise Java Beans (EJBs) because it
+ * is part of the EJB contract for application servers to provide each
+ * bean an environment naming context (ENC).
+    
+ * <p>In the second approach, several predetermined properties are set
+ * and these properties are passed to the <code>InitialContext</code>
+ * constructor to connect to the naming service provider. For example,
+ * to connect to JBoss naming service one would write:
+
+<pre>
+   Properties env = new Properties( );
+   env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
+   env.put(Context.PROVIDER_URL, "jnp://hostname:1099");
+   env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
+   InitialContext jndiContext = new InitialContext(env);
+</pre>
+
+   * where <em>hostname</em> is the host where the JBoss application
+   * server is running.
+   *
+   * <p>To connect to the the naming service of Weblogic application
+   * server one would write:
+
+<pre>
+   Properties env = new Properties( );
+   env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
+   env.put(Context.PROVIDER_URL, "t3://localhost:7001");
+   InitialContext jndiContext = new InitialContext(env);
+</pre>
+
+  * <p>Other JMS providers will obviously require different values.
+  * 
+  * The initial JNDI context can be obtained by calling the
+  * no-argument <code>InitialContext()</code> method in EJBs. Only
+  * clients running in a separate JVM need to be concerned about the
+  * <em>jndi.properties</em> file and calling {@link
+  * InitialContext#InitialContext()} or alternatively correctly
+  * setting the different properties before calling {@link
+  * InitialContext#InitialContext(java.util.Hashtable)} method.
+
+
+   @author Ceki G&uuml;lc&uuml; */
+public class JMSAppender extends AppenderSkeleton {
+
+  String securityPrincipalName;
+  String securityCredentials;
+  String initialContextFactoryName;
+  String urlPkgPrefixes;
+  String providerURL;
+  String topicBindingName;
+  String tcfBindingName;
+  String userName;
+  String password;
+  boolean locationInfo;
+
+  TopicConnection  topicConnection;
+  TopicSession topicSession;
+  TopicPublisher  topicPublisher;
+
+  public
+  JMSAppender() {
+  }
+
+  /**
+     The <b>TopicConnectionFactoryBindingName</b> option takes a
+     string value. Its value will be used to lookup the appropriate
+     <code>TopicConnectionFactory</code> from the JNDI context.
+   */
+  public
+  void setTopicConnectionFactoryBindingName(String tcfBindingName) {
+    this.tcfBindingName = tcfBindingName;
+  }
+
+  /**
+     Returns the value of the <b>TopicConnectionFactoryBindingName</b> option.
+   */
+  public
+  String getTopicConnectionFactoryBindingName() {
+    return tcfBindingName;
+  }
+
+  /**
+     The <b>TopicBindingName</b> option takes a
+     string value. Its value will be used to lookup the appropriate
+     <code>Topic</code> from the JNDI context.
+   */
+  public
+  void setTopicBindingName(String topicBindingName) {
+    this.topicBindingName = topicBindingName;
+  }
+
+  /**
+     Returns the value of the <b>TopicBindingName</b> option.
+   */
+  public
+  String getTopicBindingName() {
+    return topicBindingName;
+  }
+
+
+  /**
+     Returns value of the <b>LocationInfo</b> property which
+     determines whether location (stack) info is sent to the remote
+     subscriber. */
+  public
+  boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+   *  Options are activated and become effective only after calling
+   *  this method.*/
+  public void activateOptions() {
+    TopicConnectionFactory  topicConnectionFactory;
+
+    try {
+      Context jndi;
+
+      LogLog.debug("Getting initial context.");
+      if(initialContextFactoryName != null) {
+       Properties env = new Properties( );
+       env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
+       if(providerURL != null) {
+         env.put(Context.PROVIDER_URL, providerURL);
+       } else {
+         LogLog.warn("You have set InitialContextFactoryName option but not the "
+                    +"ProviderURL. This is likely to cause problems.");
+       }
+       if(urlPkgPrefixes != null) {
+         env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
+       }
+       
+       if(securityPrincipalName != null) {
+         env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
+         if(securityCredentials != null) {
+           env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
+         } else {
+           LogLog.warn("You have set SecurityPrincipalName option but not the "
+                       +"SecurityCredentials. This is likely to cause problems.");
+         }
+       }       
+       jndi = new InitialContext(env);
+      } else {
+       jndi = new InitialContext();
+      }
+
+      LogLog.debug("Looking up ["+tcfBindingName+"]");
+      topicConnectionFactory = (TopicConnectionFactory) lookup(jndi, tcfBindingName);
+      LogLog.debug("About to create TopicConnection.");
+      if(userName != null) {
+       topicConnection = topicConnectionFactory.createTopicConnection(userName, 
+                                                                      password); 
+      } else {
+       topicConnection = topicConnectionFactory.createTopicConnection();
+      }
+
+      LogLog.debug("Creating TopicSession, non-transactional, "
+                  +"in AUTO_ACKNOWLEDGE mode.");
+      topicSession = topicConnection.createTopicSession(false,
+                                                       Session.AUTO_ACKNOWLEDGE);
+
+      LogLog.debug("Looking up topic name ["+topicBindingName+"].");
+      Topic topic = (Topic) lookup(jndi, topicBindingName);
+
+      LogLog.debug("Creating TopicPublisher.");
+      topicPublisher = topicSession.createPublisher(topic);
+      
+      LogLog.debug("Starting TopicConnection.");
+      topicConnection.start();
+
+      jndi.close();
+    } catch(JMSException e) {
+      errorHandler.error("Error while activating options for appender named ["+name+
+                        "].", e, ErrorCode.GENERIC_FAILURE);
+    } catch(NamingException e) {
+      errorHandler.error("Error while activating options for appender named ["+name+
+                        "].", e, ErrorCode.GENERIC_FAILURE);
+    } catch(RuntimeException e) {
+      errorHandler.error("Error while activating options for appender named ["+name+
+                        "].", e, ErrorCode.GENERIC_FAILURE);
+    }
+  }
+
+  protected Object lookup(Context ctx, String name) throws NamingException {
+    try {
+      return ctx.lookup(name);
+    } catch(NameNotFoundException e) {
+      LogLog.error("Could not find name ["+name+"].");
+      throw e;
+    }
+  }
+
+  protected boolean checkEntryConditions() {
+    String fail = null;
+
+    if(this.topicConnection == null) {
+      fail = "No TopicConnection";
+    } else if(this.topicSession == null) {
+      fail = "No TopicSession";
+    } else if(this.topicPublisher == null) {
+      fail = "No TopicPublisher";
+    }
+
+    if(fail != null) {
+      errorHandler.error(fail +" for JMSAppender named ["+name+"].");
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  /**
+     Close this JMSAppender. Closing releases all resources used by the
+     appender. A closed appender cannot be re-opened. */
+  public synchronized void close() {
+    // The synchronized modifier avoids concurrent append and close operations
+
+    if(this.closed) {
+        return;
+    }
+
+    LogLog.debug("Closing appender ["+name+"].");
+    this.closed = true;
+
+    try {
+      if(topicSession != null) {
+        topicSession.close();
+    }
+      if(topicConnection != null) {
+        topicConnection.close();
+    }
+    } catch(JMSException e) {
+      LogLog.error("Error while closing JMSAppender ["+name+"].", e);
+    } catch(RuntimeException e) {
+      LogLog.error("Error while closing JMSAppender ["+name+"].", e);
+    }
+    // Help garbage collection
+    topicPublisher = null;
+    topicSession = null;
+    topicConnection = null;
+  }
+
+  /**
+     This method called by {@link AppenderSkeleton#doAppend} method to
+     do most of the real appending work.  */
+  public void append(LoggingEvent event) {
+    if(!checkEntryConditions()) {
+      return;
+    }
+
+    try {
+      ObjectMessage msg = topicSession.createObjectMessage();
+      if(locationInfo) {
+       event.getLocationInformation();
+      }
+      msg.setObject(event);
+      topicPublisher.publish(msg);
+    } catch(JMSException e) {
+      errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
+                        ErrorCode.GENERIC_FAILURE);
+    } catch(RuntimeException e) {
+      errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
+                        ErrorCode.GENERIC_FAILURE);
+    }
+  }
+
+  /**
+   * Returns the value of the <b>InitialContextFactoryName</b> option.
+   * See {@link #setInitialContextFactoryName} for more details on the
+   * meaning of this option.
+   * */
+  public String getInitialContextFactoryName() {
+    return initialContextFactoryName;    
+  }
+  
+  /**
+   * Setting the <b>InitialContextFactoryName</b> method will cause
+   * this <code>JMSAppender</code> instance to use the {@link
+   * InitialContext#InitialContext(Hashtable)} method instead of the
+   * no-argument constructor. If you set this option, you should also
+   * at least set the <b>ProviderURL</b> option.
+   * 
+   * @see #setProviderURL(String)
+   * */
+  public void setInitialContextFactoryName(String initialContextFactoryName) {
+    this.initialContextFactoryName = initialContextFactoryName;
+  }
+
+  public String getProviderURL() {
+    return providerURL;    
+  }
+
+  public void setProviderURL(String providerURL) {
+    this.providerURL = providerURL;
+  }
+
+  String getURLPkgPrefixes( ) {
+    return urlPkgPrefixes;
+  }
+
+  public void setURLPkgPrefixes(String urlPkgPrefixes ) {
+    this.urlPkgPrefixes = urlPkgPrefixes;
+  }
+  
+  public String getSecurityCredentials() {
+    return securityCredentials;    
+  }
+
+  public void setSecurityCredentials(String securityCredentials) {
+    this.securityCredentials = securityCredentials;
+  }
+  
+  
+  public String getSecurityPrincipalName() {
+    return securityPrincipalName;    
+  }
+
+  public void setSecurityPrincipalName(String securityPrincipalName) {
+    this.securityPrincipalName = securityPrincipalName;
+  }
+
+  public String getUserName() {
+    return userName;    
+  }
+
+  /**
+   * The user name to use when {@link
+   * TopicConnectionFactory#createTopicConnection(String, String)
+   * creating a topic session}.  If you set this option, you should
+   * also set the <b>Password</b> option. See {@link
+   * #setPassword(String)}.
+   * */
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public String getPassword() {
+    return password;    
+  }
+
+  /**
+   * The paswword to use when creating a topic session.  
+   */
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+
+  /**
+      If true, the information sent to the remote subscriber will
+      include caller's location information. By default no location
+      information is sent to the subscriber.  */
+  public void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+  /**
+   * Returns the TopicConnection used for this appender.  Only valid after
+   * activateOptions() method has been invoked.
+   */
+  protected TopicConnection  getTopicConnection() {
+    return topicConnection;
+  }
+
+  /**
+   * Returns the TopicSession used for this appender.  Only valid after
+   * activateOptions() method has been invoked.
+   */
+  protected TopicSession  getTopicSession() {
+    return topicSession;
+  }
+
+  /**
+   * Returns the TopicPublisher used for this appender.  Only valid after
+   * activateOptions() method has been invoked.
+   */
+  protected TopicPublisher  getTopicPublisher() {
+    return topicPublisher;
+  }
+  
+  /** 
+   * The JMSAppender sends serialized events and consequently does not
+   * require a layout.
+   */
+  public boolean requiresLayout() {
+    return false;
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/JMSSink.java b/srcjar/org/apache/log4j/net/JMSSink.java
new file mode 100644 (file)
index 0000000..6a02831
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.DOMConfigurator;
+
+import javax.jms.JMSException;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * A simple application that consumes logging events sent by a {@link
+ * JMSAppender}.
+ *
+ *
+ * @author Ceki G&uuml;lc&uuml; 
+ * */
+public class JMSSink implements javax.jms.MessageListener {
+
+  static Logger logger = Logger.getLogger(JMSSink.class);
+
+  static public void main(String[] args) throws Exception {
+    if(args.length != 5) {
+      usage("Wrong number of arguments.");
+    }
+    
+    String tcfBindingName = args[0];
+    String topicBindingName = args[1];
+    String username = args[2];
+    String password = args[3];
+    
+    
+    String configFile = args[4];
+
+    if(configFile.endsWith(".xml")) {
+      DOMConfigurator.configure(configFile);
+    } else {
+      PropertyConfigurator.configure(configFile);
+    }
+    
+    new JMSSink(tcfBindingName, topicBindingName, username, password);
+
+    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+    // Loop until the word "exit" is typed
+    System.out.println("Type \"exit\" to quit JMSSink.");
+    while(true){
+      String s = stdin.readLine( );
+      if (s.equalsIgnoreCase("exit")) {
+       System.out.println("Exiting. Kill the application if it does not exit "
+                          + "due to daemon threads.");
+       return; 
+      }
+    } 
+  }
+
+  public JMSSink( String tcfBindingName, String topicBindingName, String username,
+                 String password) {
+    
+    try {
+      Context ctx = new InitialContext();
+      TopicConnectionFactory topicConnectionFactory;
+      topicConnectionFactory = (TopicConnectionFactory) lookup(ctx,
+                                                               tcfBindingName);
+
+      TopicConnection topicConnection =
+                               topicConnectionFactory.createTopicConnection(username,
+                                                                            password);
+      topicConnection.start();
+
+      TopicSession topicSession = topicConnection.createTopicSession(false,
+                                                       Session.AUTO_ACKNOWLEDGE);
+
+      Topic topic = (Topic)ctx.lookup(topicBindingName);
+
+      TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
+    
+      topicSubscriber.setMessageListener(this);
+
+    } catch(JMSException e) {
+      logger.error("Could not read JMS message.", e);
+    } catch(NamingException e) {
+      logger.error("Could not read JMS message.", e);
+    } catch(RuntimeException e) {
+      logger.error("Could not read JMS message.", e);
+    }
+  }
+
+  public void onMessage(javax.jms.Message message) {
+    LoggingEvent event;
+    Logger remoteLogger;
+
+    try {
+      if(message instanceof  ObjectMessage) {
+       ObjectMessage objectMessage = (ObjectMessage) message;
+       event = (LoggingEvent) objectMessage.getObject();
+       remoteLogger = Logger.getLogger(event.getLoggerName());
+       remoteLogger.callAppenders(event);
+      } else {
+       logger.warn("Received message is of type "+message.getJMSType()
+                   +", was expecting ObjectMessage.");
+      }      
+    } catch(JMSException jmse) {
+      logger.error("Exception thrown while processing incoming message.", 
+                  jmse);
+    }
+  }
+
+
+  protected static Object lookup(Context ctx, String name) throws NamingException {
+    try {
+      return ctx.lookup(name);
+    } catch(NameNotFoundException e) {
+      logger.error("Could not find name ["+name+"].");
+      throw e;
+    }
+  }
+
+  static void usage(String msg) {
+    System.err.println(msg);
+    System.err.println("Usage: java " + JMSSink.class.getName()
+            + " TopicConnectionFactoryBindingName TopicBindingName username password configFile");
+    System.exit(1);
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/SMTPAppender.java b/srcjar/org/apache/log4j/net/SMTPAppender.java
new file mode 100644 (file)
index 0000000..9162d07
--- /dev/null
@@ -0,0 +1,788 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.helpers.CyclicBuffer;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.TriggeringEventEvaluator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Properties;
+
+/**
+   Send an e-mail when a specific logging event occurs, typically on
+   errors or fatal errors.
+
+   <p>The number of logging events delivered in this e-mail depend on
+   the value of <b>BufferSize</b> option. The
+   <code>SMTPAppender</code> keeps only the last
+   <code>BufferSize</code> logging events in its cyclic buffer. This
+   keeps memory requirements at a reasonable level while still
+   delivering useful application context.
+
+   By default, an email message will be sent when an ERROR or higher
+   severity message is appended.  The triggering criteria can be
+   modified by setting the evaluatorClass property with the name
+   of a class implementing TriggeringEventEvaluator, setting the evaluator
+   property with an instance of TriggeringEventEvaluator or
+   nesting a triggeringPolicy element where the specified
+   class implements TriggeringEventEvaluator.
+   
+   This class has implemented UnrecognizedElementHandler since 1.2.15.
+
+   Since 1.2.16, SMTP over SSL is supported by setting SMTPProtocol to "smpts".
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.0 */
+public class SMTPAppender extends AppenderSkeleton
+        implements UnrecognizedElementHandler {
+  private String to;
+  /**
+   * Comma separated list of cc recipients.
+   */
+  private String cc;  
+  /**
+   * Comma separated list of bcc recipients.
+   */
+  private String bcc;  
+  private String from;
+  /**
+   * Comma separated list of replyTo addresses.
+   */
+  private String replyTo;
+  private String subject;
+  private String smtpHost;
+  private String smtpUsername;
+  private String smtpPassword;
+  private String smtpProtocol;
+  private int smtpPort = -1;
+  private boolean smtpDebug = false;
+  private int bufferSize = 512;
+  private boolean locationInfo = false;
+  private boolean sendOnClose = false;
+
+  protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
+  protected Message msg;
+
+  protected TriggeringEventEvaluator evaluator;
+
+
+
+  /**
+     The default constructor will instantiate the appender with a
+     {@link TriggeringEventEvaluator} that will trigger on events with
+     level ERROR or higher.*/
+  public
+  SMTPAppender() {
+    this(new DefaultEvaluator());
+  }
+
+
+  /**
+     Use <code>evaluator</code> passed as parameter as the {@link
+     TriggeringEventEvaluator} for this SMTPAppender.  */
+  public
+  SMTPAppender(TriggeringEventEvaluator evaluator) {
+    this.evaluator = evaluator;
+  }
+
+
+  /**
+     Activate the specified options, such as the smtp host, the
+     recipient, from, etc. */
+  public
+  void activateOptions() {
+    Session session = createSession();
+    msg = new MimeMessage(session);
+
+     try {
+        addressMessage(msg);
+        if(subject != null) {
+           try {
+                msg.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
+           } catch(UnsupportedEncodingException ex) {
+                LogLog.error("Unable to encode SMTP subject", ex);
+           }
+        }
+     } catch(MessagingException e) {
+       LogLog.error("Could not activate SMTPAppender options.", e );
+     }
+
+     if (evaluator instanceof OptionHandler) {
+         ((OptionHandler) evaluator).activateOptions();
+     }
+  }
+  
+  /**
+   *   Address message.
+   *   @param msg message, may not be null.
+   *   @throws MessagingException thrown if error addressing message. 
+   *   @since 1.2.14
+   */
+  protected void addressMessage(final Message msg) throws MessagingException {
+       if (from != null) {
+                       msg.setFrom(getAddress(from));
+       } else {
+                       msg.setFrom();
+          }
+
+      //Add ReplyTo addresses if defined.
+         if (replyTo != null && replyTo.length() > 0) {
+               msg.setReplyTo(parseAddress(replyTo));
+         }
+
+       if (to != null && to.length() > 0) {
+             msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
+       }
+
+      //Add CC receipients if defined.
+         if (cc != null && cc.length() > 0) {
+               msg.setRecipients(Message.RecipientType.CC, parseAddress(cc));
+         }
+
+      //Add BCC receipients if defined.
+         if (bcc != null && bcc.length() > 0) {
+               msg.setRecipients(Message.RecipientType.BCC, parseAddress(bcc));
+         }
+  }
+  
+  /**
+   *  Create mail session.
+   *  @return mail session, may not be null.
+   *  @since 1.2.14
+   */
+  protected Session createSession() {
+    Properties props = null;
+    try {
+        props = new Properties (System.getProperties());
+    } catch(SecurityException ex) {
+        props = new Properties();
+    }
+
+    String prefix = "mail.smtp";
+    if (smtpProtocol != null) {
+        props.put("mail.transport.protocol", smtpProtocol);
+        prefix = "mail." + smtpProtocol;
+    }
+    if (smtpHost != null) {
+      props.put(prefix + ".host", smtpHost);
+    }
+    if (smtpPort > 0) {
+        props.put(prefix + ".port", String.valueOf(smtpPort));
+    }
+    
+    Authenticator auth = null;
+    if(smtpPassword != null && smtpUsername != null) {
+      props.put(prefix + ".auth", "true");
+      auth = new Authenticator() {
+        protected PasswordAuthentication getPasswordAuthentication() {
+          return new PasswordAuthentication(smtpUsername, smtpPassword);
+        }
+      };
+    }
+    Session session = Session.getInstance(props, auth);
+    if (smtpProtocol != null) {
+        session.setProtocolForAddress("rfc822", smtpProtocol);
+    }
+    if (smtpDebug) {
+        session.setDebug(smtpDebug);
+    }
+    return session;
+  }
+
+  /**
+     Perform SMTPAppender specific appending actions, mainly adding
+     the event to a cyclic buffer and checking if the event triggers
+     an e-mail to be sent. */
+  public
+  void append(LoggingEvent event) {
+
+    if(!checkEntryConditions()) {
+      return;
+    }
+
+    event.getThreadName();
+    event.getNDC();
+    event.getMDCCopy();
+    if(locationInfo) {
+      event.getLocationInformation();
+    }
+    event.getRenderedMessage();
+    event.getThrowableStrRep();
+    cb.add(event);
+    if(evaluator.isTriggeringEvent(event)) {
+      sendBuffer();
+    }
+  }
+
+ /**
+     This method determines if there is a sense in attempting to append.
+
+     <p>It checks whether there is a set output target and also if
+     there is a set layout. If these checks fail, then the boolean
+     value <code>false</code> is returned. */
+  protected
+  boolean checkEntryConditions() {
+    if(this.msg == null) {
+      errorHandler.error("Message object not configured.");
+      return false;
+    }
+
+    if(this.evaluator == null) {
+      errorHandler.error("No TriggeringEventEvaluator is set for appender ["+
+                        name+"].");
+      return false;
+    }
+
+
+    if(this.layout == null) {
+      errorHandler.error("No layout set for appender named ["+name+"].");
+      return false;
+    }
+    return true;
+  }
+
+
+  synchronized
+  public
+  void close() {
+    this.closed = true;
+    if (sendOnClose && cb.length() > 0) {
+        sendBuffer();
+    }
+  }
+
+  InternetAddress getAddress(String addressStr) {
+    try {
+      return new InternetAddress(addressStr);
+    } catch(AddressException e) {
+      errorHandler.error("Could not parse address ["+addressStr+"].", e,
+                        ErrorCode.ADDRESS_PARSE_FAILURE);
+      return null;
+    }
+  }
+
+  InternetAddress[] parseAddress(String addressStr) {
+    try {
+      return InternetAddress.parse(addressStr, true);
+    } catch(AddressException e) {
+      errorHandler.error("Could not parse address ["+addressStr+"].", e,
+                        ErrorCode.ADDRESS_PARSE_FAILURE);
+      return null;
+    }
+  }
+
+  /**
+     Returns value of the <b>To</b> option.
+   */
+  public
+  String getTo() {
+    return to;
+  }
+
+
+  /**
+     The <code>SMTPAppender</code> requires a {@link
+     org.apache.log4j.Layout layout}.  */
+  public
+  boolean requiresLayout() {
+    return true;
+  }
+
+  /**
+   * Layout body of email message.
+   * @since 1.2.16  
+   */
+  protected String formatBody() {
+         
+         // Note: this code already owns the monitor for this
+         // appender. This frees us from needing to synchronize on 'cb'.
+         
+      StringBuffer sbuf = new StringBuffer();
+      String t = layout.getHeader();
+      if(t != null) {
+        sbuf.append(t);
+    }
+      int len =  cb.length();
+      for(int i = 0; i < len; i++) {
+       //sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
+       LoggingEvent event = cb.get();
+       sbuf.append(layout.format(event));
+       if(layout.ignoresThrowable()) {
+         String[] s = event.getThrowableStrRep();
+         if (s != null) {
+           for(int j = 0; j < s.length; j++) {
+             sbuf.append(s[j]);
+             sbuf.append(Layout.LINE_SEP);
+           }
+         }
+       }
+      }
+      t = layout.getFooter();
+      if(t != null) {
+           sbuf.append(t);
+      }
+      
+      return sbuf.toString();
+  }
+  
+  /**
+     Send the contents of the cyclic buffer as an e-mail message.
+   */
+  protected
+  void sendBuffer() {
+
+    try {
+      String s = formatBody();
+      boolean allAscii = true;
+      for(int i = 0; i < s.length() && allAscii; i++) {
+          allAscii = s.charAt(i) <= 0x7F;
+      }
+      MimeBodyPart part;
+      if (allAscii) {
+          part = new MimeBodyPart();
+          part.setContent(s, layout.getContentType());
+      } else {
+          try {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            Writer writer = new OutputStreamWriter(
+                    MimeUtility.encode(os, "quoted-printable"), "UTF-8");
+            writer.write(s);
+            writer.close();
+            InternetHeaders headers = new InternetHeaders();
+            headers.setHeader("Content-Type", layout.getContentType() + "; charset=UTF-8");
+            headers.setHeader("Content-Transfer-Encoding", "quoted-printable");
+            part = new MimeBodyPart(headers, os.toByteArray());
+          } catch(Exception ex) {
+              StringBuffer sbuf = new StringBuffer(s);
+              for (int i = 0; i < sbuf.length(); i++) {
+                  if (sbuf.charAt(i) >= 0x80) {
+                      sbuf.setCharAt(i, '?');
+                  }
+              }
+              part = new MimeBodyPart();
+              part.setContent(sbuf.toString(), layout.getContentType());
+          }
+      }
+
+
+
+      Multipart mp = new MimeMultipart();
+      mp.addBodyPart(part);
+      msg.setContent(mp);
+
+      msg.setSentDate(new Date());
+      Transport.send(msg);
+    } catch(MessagingException e) {
+      LogLog.error("Error occured while sending e-mail notification.", e);
+    } catch(RuntimeException e) {
+      LogLog.error("Error occured while sending e-mail notification.", e);
+    }
+  }
+
+
+
+  /**
+     Returns value of the <b>EvaluatorClass</b> option.
+   */
+  public
+  String getEvaluatorClass() {
+    return evaluator == null ? null : evaluator.getClass().getName();
+  }
+
+  /**
+     Returns value of the <b>From</b> option.
+   */
+  public
+  String getFrom() {
+    return from;
+  }
+
+  /**
+     Get the reply addresses.
+     @return reply addresses as comma separated string, may be null.
+     @since 1.2.16
+   */
+  public
+  String getReplyTo() {
+    return replyTo;
+  }
+
+  /**
+     Returns value of the <b>Subject</b> option.
+   */
+  public
+  String getSubject() {
+    return subject;
+  }
+
+  /**
+     The <b>From</b> option takes a string value which should be a
+     e-mail address of the sender.
+   */
+  public
+  void setFrom(String from) {
+    this.from = from;
+  }
+
+  /**
+     Set the e-mail addresses to which replies should be directed.
+     @param addresses reply addresses as comma separated string, may be null.
+     @since 1.2.16
+   */
+  public
+  void setReplyTo(final String addresses) {
+    this.replyTo = addresses;
+  }
+
+
+  /**
+     The <b>Subject</b> option takes a string value which should be a
+     the subject of the e-mail message.
+   */
+  public
+  void setSubject(String subject) {
+    this.subject = subject;
+  }
+
+
+  /**
+     The <b>BufferSize</b> option takes a positive integer
+     representing the maximum number of logging events to collect in a
+     cyclic buffer. When the <code>BufferSize</code> is reached,
+     oldest events are deleted as new events are added to the
+     buffer. By default the size of the cyclic buffer is 512 events.
+   */
+  public
+  void setBufferSize(int bufferSize) {
+    this.bufferSize = bufferSize;
+    cb.resize(bufferSize);
+  }
+
+  /**
+     The <b>SMTPHost</b> option takes a string value which should be a
+     the host name of the SMTP server that will send the e-mail message.
+   */
+  public
+  void setSMTPHost(String smtpHost) {
+    this.smtpHost = smtpHost;
+  }
+
+  /**
+     Returns value of the <b>SMTPHost</b> option.
+   */
+  public
+  String getSMTPHost() {
+    return smtpHost;
+  }
+
+  /**
+     The <b>To</b> option takes a string value which should be a
+     comma separated list of e-mail address of the recipients.
+   */
+  public
+  void setTo(String to) {
+    this.to = to;
+  }
+
+
+
+  /**
+     Returns value of the <b>BufferSize</b> option.
+   */
+  public
+  int getBufferSize() {
+    return bufferSize;
+  }
+
+  /**
+     The <b>EvaluatorClass</b> option takes a string value
+     representing the name of the class implementing the {@link
+     TriggeringEventEvaluator} interface. A corresponding object will
+     be instantiated and assigned as the triggering event evaluator
+     for the SMTPAppender.
+   */
+  public
+  void setEvaluatorClass(String value) {
+      evaluator = (TriggeringEventEvaluator)
+                OptionConverter.instantiateByClassName(value,
+                                          TriggeringEventEvaluator.class,
+                                                      evaluator);
+  }
+
+
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. By
+     default, it is set to false which means there will be no effort
+     to extract the location information related to the event. As a
+     result, the layout that formats the events as they are sent out
+     in an e-mail is likely to place the wrong location information
+     (if present in the format).
+
+     <p>Location information extraction is comparatively very slow and
+     should be avoided unless performance is not a concern.
+   */
+  public
+  void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+  /**
+     Returns value of the <b>LocationInfo</b> option.
+   */
+  public
+  boolean getLocationInfo() {
+    return locationInfo;
+  }
+  
+   /**
+      Set the cc recipient addresses.
+      @param addresses recipient addresses as comma separated string, may be null.
+      @since 1.2.14
+    */
+   public void setCc(final String addresses) {
+     this.cc = addresses;
+   }
+
+   /**
+      Get the cc recipient addresses.
+      @return recipient addresses as comma separated string, may be null.
+      @since 1.2.14
+    */
+    public String getCc() {
+     return cc;
+    }
+
+   /**
+      Set the bcc recipient addresses.
+      @param addresses recipient addresses as comma separated string, may be null.
+      @since 1.2.14
+    */
+   public void setBcc(final String addresses) {
+     this.bcc = addresses;
+   }
+
+   /**
+      Get the bcc recipient addresses.
+      @return recipient addresses as comma separated string, may be null.
+      @since 1.2.14
+    */
+    public String getBcc() {
+     return bcc;
+    }
+
+  /**
+   * The <b>SmtpPassword</b> option takes a string value which should be the password required to authenticate against
+   * the mail server.
+   * @param password password, may be null.
+   * @since 1.2.14
+   */
+  public void setSMTPPassword(final String password) {
+    this.smtpPassword = password;
+  }
+  /**
+   * The <b>SmtpUsername</b> option takes a string value which should be the username required to authenticate against
+   * the mail server.
+   * @param username user name, may be null.
+   * @since 1.2.14
+   */
+  public void setSMTPUsername(final String username) {
+    this.smtpUsername = username;
+  }
+
+  /**
+   * Setting the <b>SmtpDebug</b> option to true will cause the mail session to log its server interaction to stdout.
+   * This can be useful when debuging the appender but should not be used during production because username and
+   * password information is included in the output.
+   * @param debug debug flag.
+   * @since 1.2.14
+   */
+  public void setSMTPDebug(final boolean debug) {
+    this.smtpDebug = debug;
+  }
+  
+  /**
+   * Get SMTP password.
+   * @return SMTP password, may be null.
+   * @since 1.2.14
+   */
+  public String getSMTPPassword() {
+    return smtpPassword;
+  }
+  /**
+   * Get SMTP user name.
+   * @return SMTP user name, may be null.
+   * @since 1.2.14
+   */
+  public String getSMTPUsername() {
+    return smtpUsername;
+  }
+
+  /**
+   * Get SMTP debug.
+   * @return SMTP debug flag.
+   * @since 1.2.14
+   */
+  public boolean getSMTPDebug() {
+    return smtpDebug;
+  }
+
+    /**
+     * Sets triggering evaluator.
+     * @param trigger triggering event evaluator.
+     * @since 1.2.15
+     */
+  public final void setEvaluator(final TriggeringEventEvaluator trigger) {
+      if (trigger == null) {
+          throw new NullPointerException("trigger");
+      }
+      this.evaluator = trigger;
+  }
+
+    /**
+     * Get triggering evaluator.
+     * @return triggering event evaluator.
+     * @since 1.2.15
+     */
+  public final TriggeringEventEvaluator getEvaluator() {
+      return evaluator;
+  }
+
+  /** {@inheritDoc}
+   * @since 1.2.15 
+  */
+  public boolean parseUnrecognizedElement(final Element element,
+                                          final Properties props) throws Exception {
+      if ("triggeringPolicy".equals(element.getNodeName())) {
+          Object triggerPolicy =
+                  org.apache.log4j.xml.DOMConfigurator.parseElement(
+                          element, props, TriggeringEventEvaluator.class);
+          if (triggerPolicy instanceof TriggeringEventEvaluator) {
+              setEvaluator((TriggeringEventEvaluator) triggerPolicy);
+          }
+          return true;
+      }
+
+      return false;
+  }
+
+    /**
+     * Get transport protocol.
+     * Typically null or "smtps".
+     *
+     * @return transport protocol, may be null.
+     * @since 1.2.16
+     */
+  public final String getSMTPProtocol() {
+      return smtpProtocol;
+  }
+
+    /**
+     * Set transport protocol.
+     * Typically null or "smtps".
+     *
+     * @param val transport protocol, may be null.
+     * @since 1.2.16
+     */
+  public final void setSMTPProtocol(final String val) {
+      smtpProtocol = val;
+  }
+
+    /**
+     * Get port.
+     *
+     * @return port, negative values indicate use of default ports for protocol.
+     * @since 1.2.16
+     */
+  public final int getSMTPPort() {
+        return smtpPort;
+  }
+
+    /**
+     * Set port.
+     *
+     * @param val port, negative values indicate use of default ports for protocol.
+     * @since 1.2.16
+     */
+  public final void setSMTPPort(final int val) {
+        smtpPort = val;
+  }
+
+    /**
+     * Get sendOnClose.
+     *
+     * @return if true all buffered logging events will be sent when the appender is closed.
+     * @since 1.2.16
+     */
+  public final boolean getSendOnClose() {
+        return sendOnClose;
+  }
+
+    /**
+     * Set sendOnClose.
+     *
+     * @param val if true all buffered logging events will be sent when appender is closed.
+     * @since 1.2.16
+     */
+  public final void setSendOnClose(final boolean val) {
+        sendOnClose = val;
+  }
+
+}
+
+class DefaultEvaluator implements TriggeringEventEvaluator {
+  /**
+     Is this <code>event</code> the e-mail triggering event?
+
+     <p>This method returns <code>true</code>, if the event level
+     has ERROR level or higher. Otherwise it returns
+     <code>false</code>. */
+  public
+  boolean isTriggeringEvent(LoggingEvent event) {
+    return event.getLevel().isGreaterOrEqual(Level.ERROR);
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/SimpleSocketServer.java b/srcjar/org/apache/log4j/net/SimpleSocketServer.java
new file mode 100644 (file)
index 0000000..2afdbff
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.xml.DOMConfigurator;
+
+
+/**
+ *  A simple {@link SocketNode} based server.
+ *
+   <pre>
+   <b>Usage:</b> java org.apache.log4j.net.SimpleSocketServer port configFile
+
+   where <em>port</em> is a port number where the server listens and
+   <em>configFile</em> is a configuration file fed to the {@link
+   PropertyConfigurator} or to {@link DOMConfigurator} if an XML file.
+   </pre>
+  *
+  * @author  Ceki G&uuml;lc&uuml;
+  *
+  *  @since 0.8.4 
+  * */
+public class SimpleSocketServer  {
+
+  static Logger cat = Logger.getLogger(SimpleSocketServer.class);
+
+  static int port;
+
+  public
+  static
+  void main(String argv[]) {
+    if(argv.length == 2) {
+      init(argv[0], argv[1]);
+    } else {
+      usage("Wrong number of arguments.");
+    }
+    
+    try {
+      cat.info("Listening on port " + port);
+      ServerSocket serverSocket = new ServerSocket(port);
+      while(true) {
+       cat.info("Waiting to accept a new client.");
+       Socket socket = serverSocket.accept();
+       cat.info("Connected to client at " + socket.getInetAddress());
+       cat.info("Starting new socket node.");
+       new Thread(new SocketNode(socket,
+                                 LogManager.getLoggerRepository()),"SimpleSocketServer-" + port).start();
+      }
+    } catch(Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+
+  static void  usage(String msg) {
+    System.err.println(msg);
+    System.err.println(
+      "Usage: java " +SimpleSocketServer.class.getName() + " port configFile");
+    System.exit(1);
+  }
+
+  static void init(String portStr, String configFile) {
+    try {
+      port = Integer.parseInt(portStr);
+    } catch(java.lang.NumberFormatException e) {
+      e.printStackTrace();
+      usage("Could not interpret port number ["+ portStr +"].");
+    }
+   
+    if(configFile.endsWith(".xml")) {
+      DOMConfigurator.configure(configFile);
+    } else {
+      PropertyConfigurator.configure(configFile);
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/SocketAppender.java b/srcjar/org/apache/log4j/net/SocketAppender.java
new file mode 100644 (file)
index 0000000..88f0049
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors: Dan MacDonald <dan@redknee.com>
+
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+    Sends {@link LoggingEvent} objects to a remote a log server,
+    usually a {@link SocketNode}.
+
+    <p>The SocketAppender has the following properties:
+
+    <ul>
+
+      <p><li>If sent to a {@link SocketNode}, remote logging is
+      non-intrusive as far as the log event is concerned. In other
+      words, the event will be logged with the same time stamp, {@link
+      org.apache.log4j.NDC}, location info as if it were logged locally by
+      the client.
+
+      <p><li>SocketAppenders do not use a layout. They ship a
+      serialized {@link LoggingEvent} object to the server side.
+
+      <p><li>Remote logging uses the TCP protocol. Consequently, if
+      the server is reachable, then log events will eventually arrive
+      at the server.
+
+      <p><li>If the remote server is down, the logging requests are
+      simply dropped. However, if and when the server comes back up,
+      then event transmission is resumed transparently. This
+      transparent reconneciton is performed by a <em>connector</em>
+      thread which periodically attempts to connect to the server.
+
+      <p><li>Logging events are automatically <em>buffered</em> by the
+      native TCP implementation. This means that if the link to server
+      is slow but still faster than the rate of (log) event production
+      by the client, the client will not be affected by the slow
+      network connection. However, if the network connection is slower
+      then the rate of event production, then the client can only
+      progress at the network rate. In particular, if the network link
+      to the the server is down, the client will be blocked.
+
+      <p>On the other hand, if the network link is up, but the server
+      is down, the client will not be blocked when making log requests
+      but the log events will be lost due to server unavailability.
+
+      <p><li>Even if a <code>SocketAppender</code> is no longer
+      attached to any category, it will not be garbage collected in
+      the presence of a connector thread. A connector thread exists
+      only if the connection to the server is down. To avoid this
+      garbage collection problem, you should {@link #close} the the
+      <code>SocketAppender</code> explicitly. See also next item.
+
+      <p>Long lived applications which create/destroy many
+      <code>SocketAppender</code> instances should be aware of this
+      garbage collection problem. Most other applications can safely
+      ignore it.
+
+      <p><li>If the JVM hosting the <code>SocketAppender</code> exits
+      before the <code>SocketAppender</code> is closed either
+      explicitly or subsequent to garbage collection, then there might
+      be untransmitted data in the pipe which might be lost. This is a
+      common problem on Windows based systems.
+
+      <p>To avoid lost data, it is usually sufficient to {@link
+      #close} the <code>SocketAppender</code> either explicitly or by
+      calling the {@link org.apache.log4j.LogManager#shutdown} method
+      before exiting the application.
+
+
+     </ul>
+
+    @author  Ceki G&uuml;lc&uuml;
+    @since 0.8.4 */
+
+public class SocketAppender extends AppenderSkeleton {
+
+  /**
+     The default port number of remote logging server (4560).
+     @since 1.2.15
+  */
+  static public final int DEFAULT_PORT                 = 4560;
+
+  /**
+     The default reconnection delay (30000 milliseconds or 30 seconds).
+  */
+  static final int DEFAULT_RECONNECTION_DELAY   = 30000;
+
+  /**
+     We remember host name as String in addition to the resolved
+     InetAddress so that it can be returned via getOption().
+  */
+  String remoteHost;
+
+  /**
+   * The MulticastDNS zone advertised by a SocketAppender
+   */
+  public static final String ZONE = "_log4j_obj_tcpconnect_appender.local.";
+
+  InetAddress address;
+  int port = DEFAULT_PORT;
+  ObjectOutputStream oos;
+  int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
+  boolean locationInfo = false;
+  private String application;
+
+  private Connector connector;
+
+  int counter = 0;
+
+  // reset the ObjectOutputStream every 70 calls
+  //private static final int RESET_FREQUENCY = 70;
+  private static final int RESET_FREQUENCY = 1;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  public SocketAppender() {
+  }
+
+  /**
+     Connects to remote server at <code>address</code> and <code>port</code>.
+  */
+  public SocketAppender(InetAddress address, int port) {
+    this.address = address;
+    this.remoteHost = address.getHostName();
+    this.port = port;
+    connect(address, port);
+  }
+
+  /**
+     Connects to remote server at <code>host</code> and <code>port</code>.
+  */
+  public SocketAppender(String host, int port) {
+    this.port = port;
+    this.address = getAddressByName(host);
+    this.remoteHost = host;
+    connect(address, port);
+  }
+
+  /**
+     Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
+  */
+  public void activateOptions() {
+    if (advertiseViaMulticastDNS) {
+      zeroConf = new ZeroConfSupport(ZONE, port, getName());
+      zeroConf.advertise();
+    }
+    connect(address, port);
+  }
+
+  /**
+   * Close this appender.  
+   *
+   * <p>This will mark the appender as closed and call then {@link
+   * #cleanUp} method.
+   * */
+  synchronized public void close() {
+    if(closed) {
+        return;
+    }
+
+    this.closed = true;
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+
+    cleanUp();
+  }
+
+  /**
+   * Drop the connection to the remote host and release the underlying
+   * connector thread if it has been created 
+   * */
+  public void cleanUp() {
+    if(oos != null) {
+      try {
+       oos.close();
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             LogLog.error("Could not close oos.", e);
+      }
+      oos = null;
+    }
+    if(connector != null) {
+      //LogLog.debug("Interrupting the connector.");
+      connector.interrupted = true;
+      connector = null;  // allow gc
+    }
+  }
+
+  void connect(InetAddress address, int port) {
+    if(this.address == null) {
+        return;
+    }
+    try {
+      // First, close the previous connection if any.
+      cleanUp();
+      oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
+    } catch(IOException e) {
+      if (e instanceof InterruptedIOException) {
+          Thread.currentThread().interrupt();
+      }
+      String msg = "Could not connect to remote log4j server at ["
+       +address.getHostName()+"].";
+      if(reconnectionDelay > 0) {
+        msg += " We will try again later.";
+       fireConnector(); // fire the connector thread
+      } else {
+          msg += " We are not retrying.";
+          errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
+      } 
+      LogLog.error(msg);
+    }
+  }
+
+
+  public void append(LoggingEvent event) {
+    if(event == null) {
+        return;
+    }
+
+    if(address==null) {
+      errorHandler.error("No remote host is set for SocketAppender named \""+
+                       this.name+"\".");
+      return;
+    }
+
+    if(oos != null) {
+      try {
+        
+       if(locationInfo) {
+          event.getLocationInformation();
+       }
+    if (application != null) {
+        event.setProperty("application", application);
+    }
+    event.getNDC();
+    event.getThreadName();
+    event.getMDCCopy();
+    event.getRenderedMessage();
+    event.getThrowableStrRep();
+    
+       oos.writeObject(event);
+       //LogLog.debug("=========Flushing.");
+       oos.flush();
+       if(++counter >= RESET_FREQUENCY) {
+         counter = 0;
+         // Failing to reset the object output stream every now and
+         // then creates a serious memory leak.
+         //System.err.println("Doing oos.reset()");
+         oos.reset();
+       }
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             oos = null;
+             LogLog.warn("Detected problem with connection: "+e);
+             if(reconnectionDelay > 0) {
+                fireConnector();
+             } else {
+                errorHandler.error("Detected problem with connection, not reconnecting.", e,
+                      ErrorCode.GENERIC_FAILURE);
+             }
+      }
+    }
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
+  void fireConnector() {
+    if(connector == null) {
+      LogLog.debug("Starting a new connector thread.");
+      connector = new Connector();
+      connector.setDaemon(true);
+      connector.setPriority(Thread.MIN_PRIORITY);
+      connector.start();
+    }
+  }
+
+  static
+  InetAddress getAddressByName(String host) {
+    try {
+      return InetAddress.getByName(host);
+    } catch(Exception e) {
+      if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+          Thread.currentThread().interrupt();
+      }
+      LogLog.error("Could not find address of ["+host+"].", e);
+      return null;
+    }
+  }
+
+  /**
+   * The SocketAppender does not use a layout. Hence, this method
+   * returns <code>false</code>.  
+   * */
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  /**
+   * The <b>RemoteHost</b> option takes a string value which should be
+   * the host name of the server where a {@link SocketNode} is
+   * running.
+   * */
+  public void setRemoteHost(String host) {
+    address = getAddressByName(host);
+    remoteHost = host;
+  }
+
+  /**
+     Returns value of the <b>RemoteHost</b> option.
+   */
+  public String getRemoteHost() {
+    return remoteHost;
+  }
+
+  /**
+     The <b>Port</b> option takes a positive integer representing
+     the port where the server is waiting for connections.
+   */
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  /**
+     Returns value of the <b>Port</b> option.
+   */
+  public int getPort() {
+    return port;
+  }
+
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. If true,
+     the information sent to the remote host will include location
+     information. By default no location information is sent to the server.
+   */
+  public void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+  /**
+     Returns value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+   * The <b>App</b> option takes a string value which should be the name of the 
+   * application getting logged.
+   * If property was already set (via system property), don't set here.
+   * @since 1.2.15
+   */
+  public void setApplication(String lapp) {
+    this.application = lapp;
+  }
+
+  /**
+   *  Returns value of the <b>Application</b> option.
+   * @since 1.2.15
+   */
+  public String getApplication() {
+    return application;
+  }
+
+  /**
+     The <b>ReconnectionDelay</b> option takes a positive integer
+     representing the number of milliseconds to wait between each
+     failed connection attempt to the server. The default value of
+     this option is 30000 which corresponds to 30 seconds.
+
+     <p>Setting this option to zero turns off reconnection
+     capability.
+   */
+  public void setReconnectionDelay(int delay) {
+    this.reconnectionDelay = delay;
+  }
+
+  /**
+     Returns value of the <b>ReconnectionDelay</b> option.
+   */
+  public int getReconnectionDelay() {
+    return reconnectionDelay;
+  }
+
+  /**
+     The Connector will reconnect when the server becomes available
+     again.  It does this by attempting to open a new connection every
+     <code>reconnectionDelay</code> milliseconds.
+
+     <p>It stops trying whenever a connection is established. It will
+     restart to try reconnect to the server when previously open
+     connection is droppped.
+
+     @author  Ceki G&uuml;lc&uuml;
+     @since 0.8.4
+  */
+  class Connector extends Thread {
+
+    boolean interrupted = false;
+
+    public
+    void run() {
+      Socket socket;
+      while(!interrupted) {
+       try {
+         sleep(reconnectionDelay);
+         LogLog.debug("Attempting connection to "+address.getHostName());
+         socket = new Socket(address, port);
+         synchronized(this) {
+           oos = new ObjectOutputStream(socket.getOutputStream());
+           connector = null;
+           LogLog.debug("Connection established. Exiting connector thread.");
+           break;
+         }
+       } catch(InterruptedException e) {
+         LogLog.debug("Connector interrupted. Leaving loop.");
+         return;
+       } catch(java.net.ConnectException e) {
+         LogLog.debug("Remote host "+address.getHostName()
+                      +" refused connection.");
+       } catch(IOException e) {
+        if (e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+           LogLog.debug("Could not connect to " + address.getHostName()+
+                      ". Exception is " + e);
+       }
+      }
+      //LogLog.debug("Exiting Connector.run() method.");
+    }
+
+    /**
+       public
+       void finalize() {
+       LogLog.debug("Connector finalize() has been called.");
+       }
+    */
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/net/SocketHubAppender.java b/srcjar/org/apache/log4j/net/SocketHubAppender.java
new file mode 100644 (file)
index 0000000..74d7186
--- /dev/null
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Vector;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.CyclicBuffer;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+  Sends {@link LoggingEvent} objects to a set of remote log servers,
+  usually a {@link SocketNode SocketNodes}.
+    
+  <p>Acts just like {@link SocketAppender} except that instead of
+  connecting to a given remote log server,
+  <code>SocketHubAppender</code> accepts connections from the remote
+  log servers as clients.  It can accept more than one connection.
+  When a log event is received, the event is sent to the set of
+  currently connected remote log servers. Implemented this way it does
+  not require any update to the configuration file to send data to
+  another remote log server. The remote log server simply connects to
+  the host and port the <code>SocketHubAppender</code> is running on.
+  
+  <p>The <code>SocketHubAppender</code> does not store events such
+  that the remote side will events that arrived after the
+  establishment of its connection. Once connected, events arrive in
+  order as guaranteed by the TCP protocol.
+
+  <p>This implementation borrows heavily from the {@link
+  SocketAppender}.
+
+  <p>The SocketHubAppender has the following characteristics:
+  
+  <ul>
+  
+  <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
+  far as the log event is concerned. In other words, the event will be
+  logged with the same time stamp, {@link org.apache.log4j.NDC},
+  location info as if it were logged locally.
+  
+  <p><li><code>SocketHubAppender</code> does not use a layout. It
+  ships a serialized {@link LoggingEvent} object to the remote side.
+  
+  <p><li><code>SocketHubAppender</code> relies on the TCP
+  protocol. Consequently, if the remote side is reachable, then log
+  events will eventually arrive at remote client.
+  
+  <p><li>If no remote clients are attached, the logging requests are
+  simply dropped.
+  
+  <p><li>Logging events are automatically <em>buffered</em> by the
+  native TCP implementation. This means that if the link to remote
+  client is slow but still faster than the rate of (log) event
+  production, the application will not be affected by the slow network
+  connection. However, if the network connection is slower then the
+  rate of event production, then the local application can only
+  progress at the network rate. In particular, if the network link to
+  the the remote client is down, the application will be blocked.
+  
+  <p>On the other hand, if the network link is up, but the remote
+  client is down, the client will not be blocked when making log
+  requests but the log events will be lost due to client
+  unavailability. 
+
+  <p>The single remote client case extends to multiple clients
+  connections. The rate of logging will be determined by the slowest
+  link.
+    
+  <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
+  before the <code>SocketHubAppender</code> is closed either
+  explicitly or subsequent to garbage collection, then there might
+  be untransmitted data in the pipe which might be lost. This is a
+  common problem on Windows based systems.
+  
+  <p>To avoid lost data, it is usually sufficient to {@link #close}
+  the <code>SocketHubAppender</code> either explicitly or by calling
+  the {@link org.apache.log4j.LogManager#shutdown} method before
+  exiting the application.
+  
+  </ul>
+     
+  @author Mark Womack */
+
+public class SocketHubAppender extends AppenderSkeleton {
+
+  /**
+     The default port number of the ServerSocket will be created on. */
+  static final int DEFAULT_PORT = 4560;
+  
+  private int port = DEFAULT_PORT;
+  private Vector oosList = new Vector();
+  private ServerMonitor serverMonitor = null;
+  private boolean locationInfo = false;
+  private CyclicBuffer buffer = null;
+  private String application;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  /**
+   * The MulticastDNS zone advertised by a SocketHubAppender
+   */
+  public static final String ZONE = "_log4j_obj_tcpaccept_appender.local.";
+  private ServerSocket serverSocket;
+
+
+    public SocketHubAppender() { }
+
+  /**
+     Connects to remote server at <code>address</code> and <code>port</code>. */
+  public
+  SocketHubAppender(int _port) {
+    port = _port;
+    startServer();
+  }
+
+  /**
+     Set up the socket server on the specified port.  */
+  public
+  void activateOptions() {
+    if (advertiseViaMulticastDNS) {
+      zeroConf = new ZeroConfSupport(ZONE, port, getName());
+      zeroConf.advertise();
+    }
+    startServer();
+  }
+
+  /**
+     Close this appender. 
+     <p>This will mark the appender as closed and
+     call then {@link #cleanUp} method. */
+  synchronized
+  public
+  void close() {
+    if(closed) {
+        return;
+    }
+
+       LogLog.debug("closing SocketHubAppender " + getName());
+    this.closed = true;
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+    cleanUp();
+
+       LogLog.debug("SocketHubAppender " + getName() + " closed");
+  }
+
+  /**
+     Release the underlying ServerMonitor thread, and drop the connections
+     to all connected remote servers. */
+  public 
+  void cleanUp() {
+    // stop the monitor thread
+       LogLog.debug("stopping ServerSocket");
+    serverMonitor.stopMonitor();
+    serverMonitor = null;
+
+    // close all of the connections
+       LogLog.debug("closing client connections");
+    while (oosList.size() != 0) {
+      ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
+      if(oos != null) {
+        try {
+               oos.close();
+        } catch(InterruptedIOException e) {
+            Thread.currentThread().interrupt();
+            LogLog.error("could not close oos.", e);
+        } catch(IOException e) {
+            LogLog.error("could not close oos.", e);
+        }
+        
+        oosList.removeElementAt(0);     
+      }
+    }
+  }
+
+  /**
+    Append an event to all of current connections. */
+  public
+  void append(LoggingEvent event) {
+    if (event != null) {
+      // set up location info if requested
+      if (locationInfo) {
+        event.getLocationInformation();
+      }
+      if (application != null) {
+          event.setProperty("application", application);
+        } 
+        event.getNDC();
+        event.getThreadName();
+        event.getMDCCopy();
+        event.getRenderedMessage();
+        event.getThrowableStrRep();
+        
+      if (buffer != null) {
+        buffer.add(event);
+      }
+    }
+
+    // if no event or no open connections, exit now
+    if ((event == null) || (oosList.size() == 0)) {
+      return;
+    }
+
+       // loop through the current set of open connections, appending the event to each
+    for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {           
+
+      ObjectOutputStream oos = null;
+      try {
+        oos = (ObjectOutputStream)oosList.elementAt(streamCount);
+      }
+      catch (ArrayIndexOutOfBoundsException e) {
+        // catch this, but just don't assign a value
+        // this should not really occur as this method is
+        // the only one that can remove oos's (besides cleanUp).
+      }
+      
+      // list size changed unexpectedly? Just exit the append.
+      if (oos == null) {
+        break;
+    }
+        
+      try {
+       oos.writeObject(event);
+       oos.flush();
+       // Failing to reset the object output stream every now and
+       // then creates a serious memory leak.
+       // right now we always reset. TODO - set up frequency counter per oos?
+       oos.reset();
+      }
+      catch(IOException e) {
+        if (e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+          // there was an io exception so just drop the connection
+       oosList.removeElementAt(streamCount);
+       LogLog.debug("dropped connection");
+       
+       // decrement to keep the counter in place (for loop always increments)
+       streamCount--;
+      }
+    }
+  }
+  
+  /**
+     The SocketHubAppender does not use a layout. Hence, this method returns
+     <code>false</code>. */
+  public
+  boolean requiresLayout() {
+    return false;
+  }
+  
+  /**
+     The <b>Port</b> option takes a positive integer representing
+     the port where the server is waiting for connections. */
+  public
+  void setPort(int _port) {
+    port = _port;
+       }
+
+  /**
+   * The <b>App</b> option takes a string value which should be the name of the application getting logged. If property was already set (via system
+   * property), don't set here.
+   */
+  public 
+  void setApplication(String lapp) {
+    this.application = lapp;
+  }
+
+  /**
+   * Returns value of the <b>Application</b> option.
+   */
+  public 
+  String getApplication() {
+    return application;
+  }
+  
+  /**
+     Returns value of the <b>Port</b> option. */
+  public
+  int getPort() {
+    return port;
+  }
+
+  /**
+   * The <b>BufferSize</b> option takes a positive integer representing the number of events this appender will buffer and send to newly connected
+   * clients.
+   */
+  public 
+  void setBufferSize(int _bufferSize) {
+    buffer = new CyclicBuffer(_bufferSize);
+  }
+
+  /**
+   * Returns value of the <b>bufferSize</b> option.
+   */
+  public 
+  int getBufferSize() {
+    if (buffer == null) {
+      return 0;
+    } else {
+      return buffer.getMaxSize();
+    }
+  }
+  
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. If true,
+     the information sent to the remote host will include location
+     information. By default no location information is sent to the server. */
+  public
+  void setLocationInfo(boolean _locationInfo) {
+    locationInfo = _locationInfo;
+  }
+  
+  /**
+     Returns value of the <b>LocationInfo</b> option. */
+  public
+  boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
+  /**
+    Start the ServerMonitor thread. */
+  private
+  void startServer() {
+    serverMonitor = new ServerMonitor(port, oosList);
+  }
+  
+  /**
+   * Creates a server socket to accept connections.
+   * @param socketPort port on which the socket should listen, may be zero.
+   * @return new socket.
+   * @throws IOException IO error when opening the socket. 
+   */
+  protected ServerSocket createServerSocket(final int socketPort) throws IOException {
+      return new ServerSocket(socketPort);
+  }
+
+  /**
+    This class is used internally to monitor a ServerSocket
+    and register new connections in a vector passed in the
+    constructor. */
+  private class ServerMonitor implements Runnable {
+    private int port;
+    private Vector oosList;
+    private boolean keepRunning;
+    private Thread monitorThread;
+    
+    /**
+      Create a thread and start the monitor. */
+    public
+    ServerMonitor(int _port, Vector _oosList) {
+      port = _port;
+      oosList = _oosList;
+      keepRunning = true;
+      monitorThread = new Thread(this);
+      monitorThread.setDaemon(true);
+      monitorThread.setName("SocketHubAppender-Monitor-" + port);
+      monitorThread.start();
+    }
+    
+    /**
+      Stops the monitor. This method will not return until
+      the thread has finished executing. */
+    public synchronized void stopMonitor() {
+      if (keepRunning) {
+       LogLog.debug("server monitor thread shutting down");
+        keepRunning = false;
+        try {
+            if (serverSocket != null) {
+                serverSocket.close();
+                serverSocket = null;
+            }
+        } catch (IOException ioe) {}
+
+        try {
+          monitorThread.join();
+        }
+        catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+          // do nothing?
+        }
+        
+        // release the thread
+        monitorThread = null;
+       LogLog.debug("server monitor thread shut down");
+      }
+    }
+    
+    private 
+    void sendCachedEvents(ObjectOutputStream stream) throws IOException {
+      if (buffer != null) {
+        for (int i = 0; i < buffer.length(); i++) {
+          stream.writeObject(buffer.get(i));
+        }
+        stream.flush();
+        stream.reset();
+      }
+    }
+
+    /**
+      Method that runs, monitoring the ServerSocket and adding connections as
+      they connect to the socket. */
+    public
+    void run() {
+      serverSocket = null;
+      try {
+        serverSocket = createServerSocket(port);
+        serverSocket.setSoTimeout(1000);
+      }
+      catch (Exception e) {
+        if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+            Thread.currentThread().interrupt();
+        }
+        LogLog.error("exception setting timeout, shutting down server socket.", e);
+        keepRunning = false;
+        return;
+      }
+
+      try {
+       try {
+               serverSocket.setSoTimeout(1000);
+       }
+       catch (SocketException e) {
+          LogLog.error("exception setting timeout, shutting down server socket.", e);
+          return;
+       }
+      
+       while (keepRunning) {
+          Socket socket = null;
+          try {
+            socket = serverSocket.accept();
+          }
+          catch (InterruptedIOException e) {
+            // timeout occurred, so just loop
+          }
+          catch (SocketException e) {
+            LogLog.error("exception accepting socket, shutting down server socket.", e);
+            keepRunning = false;
+          }
+          catch (IOException e) {
+            LogLog.error("exception accepting socket.", e);
+          }
+               
+          // if there was a socket accepted
+          if (socket != null) {
+            try {
+              InetAddress remoteAddress = socket.getInetAddress();
+              LogLog.debug("accepting connection from " + remoteAddress.getHostName() 
+                          + " (" + remoteAddress.getHostAddress() + ")");
+                       
+              // create an ObjectOutputStream
+              ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
+              if (buffer != null && buffer.length() > 0) {
+                sendCachedEvents(oos);
+              }
+                   
+              // add it to the oosList.  OK since Vector is synchronized.
+              oosList.addElement(oos);
+            } catch (IOException e) {
+              if (e instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+              }
+              LogLog.error("exception creating output stream on socket.", e);
+            }
+          }
+        }
+      }
+      finally {
+       // close the socket
+       try {
+               serverSocket.close();
+       } catch(InterruptedIOException e) {
+            Thread.currentThread().interrupt();  
+        } catch (IOException e) {
+               // do nothing with it?
+       }
+      }
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/net/SocketNode.java b/srcjar/org/apache/log4j/net/SocketNode.java
new file mode 100644 (file)
index 0000000..e977f13
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.ObjectInputStream;
+import java.net.Socket;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+// Contributors:  Moses Hohman <mmhohman@rainbow.uchicago.edu>
+
+/**
+   Read {@link LoggingEvent} objects sent from a remote client using
+   Sockets (TCP). These logging events are logged according to local
+   policy, as if they were generated locally.
+
+   <p>For example, the socket node might decide to log events to a
+   local file and also resent them to a second socket node.
+
+    @author  Ceki G&uuml;lc&uuml;
+
+    @since 0.8.4
+*/
+public class SocketNode implements Runnable {
+
+  Socket socket;
+  LoggerRepository hierarchy;
+  ObjectInputStream ois;
+
+  static Logger logger = Logger.getLogger(SocketNode.class);
+
+  public SocketNode(Socket socket, LoggerRepository hierarchy) {
+    this.socket = socket;
+    this.hierarchy = hierarchy;
+    try {
+      ois = new ObjectInputStream(
+                         new BufferedInputStream(socket.getInputStream()));
+    } catch(InterruptedIOException e) {
+      Thread.currentThread().interrupt();
+      logger.error("Could not open ObjectInputStream to "+socket, e);
+    } catch(IOException e) {
+      logger.error("Could not open ObjectInputStream to "+socket, e);
+    } catch(RuntimeException e) {
+      logger.error("Could not open ObjectInputStream to "+socket, e);
+    }
+  }
+
+  //public
+  //void finalize() {
+  //System.err.println("-------------------------Finalize called");
+  // System.err.flush();
+  //}
+
+  public void run() {
+    LoggingEvent event;
+    Logger remoteLogger;
+
+    try {
+      if (ois != null) {
+          while(true) {
+               // read an event from the wire
+               event = (LoggingEvent) ois.readObject();
+               // get a logger from the hierarchy. The name of the logger is taken to be the name contained in the event.
+               remoteLogger = hierarchy.getLogger(event.getLoggerName());
+               //event.logger = remoteLogger;
+               // apply the logger-level filter
+               if(event.getLevel().isGreaterOrEqual(remoteLogger.getEffectiveLevel())) {
+               // finally log the event as if was generated locally
+               remoteLogger.callAppenders(event);
+             }
+        }
+      }
+    } catch(java.io.EOFException e) {
+      logger.info("Caught java.io.EOFException closing conneciton.");
+    } catch(java.net.SocketException e) {
+      logger.info("Caught java.net.SocketException closing conneciton.");
+    } catch(InterruptedIOException e) {
+      Thread.currentThread().interrupt();
+      logger.info("Caught java.io.InterruptedIOException: "+e);
+      logger.info("Closing connection.");
+    } catch(IOException e) {
+      logger.info("Caught java.io.IOException: "+e);
+      logger.info("Closing connection.");
+    } catch(Exception e) {
+      logger.error("Unexpected exception. Closing conneciton.", e);
+    } finally {
+      if (ois != null) {
+         try {
+            ois.close();
+         } catch(Exception e) {
+            logger.info("Could not close connection.", e);
+         }
+      }
+      if (socket != null) {
+        try {
+          socket.close();
+        } catch(InterruptedIOException e) {
+            Thread.currentThread().interrupt();
+        } catch(IOException ex) {
+        }
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/SocketServer.java b/srcjar/org/apache/log4j/net/SocketServer.java
new file mode 100644 (file)
index 0000000..fda74ad
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Hashtable;
+
+import org.apache.log4j.Hierarchy;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.RootLogger;
+
+
+/**
+   A {@link SocketNode} based server that uses a different hierarchy
+   for each client.
+
+   <pre>
+     <b>Usage:</b> java org.apache.log4j.net.SocketServer port configFile configDir
+
+     where <b>port</b> is a part number where the server listens,
+           <b>configFile</b> is a configuration file fed to the {@link PropertyConfigurator} and
+           <b>configDir</b> is a path to a directory containing configuration files, possibly one for each client host.
+     </pre>
+
+     <p>The <code>configFile</code> is used to configure the log4j
+     default hierarchy that the <code>SocketServer</code> will use to
+     report on its actions.
+
+     <p>When a new connection is opened from a previously unknown
+     host, say <code>foo.bar.net</code>, then the
+     <code>SocketServer</code> will search for a configuration file
+     called <code>foo.bar.net.lcf</code> under the directory
+     <code>configDir</code> that was passed as the third argument. If
+     the file can be found, then a new hierarchy is instantiated and
+     configured using the configuration file
+     <code>foo.bar.net.lcf</code>. If and when the host
+     <code>foo.bar.net</code> opens another connection to the server,
+     then the previously configured hierarchy is used.
+
+     <p>In case there is no file called <code>foo.bar.net.lcf</code>
+     under the directory <code>configDir</code>, then the
+     <em>generic</em> hierarchy is used. The generic hierarchy is
+     configured using a configuration file called
+     <code>generic.lcf</code> under the <code>configDir</code>
+     directory. If no such file exists, then the generic hierarchy will be
+     identical to the log4j default hierarchy.
+
+     <p>Having different client hosts log using different hierarchies
+     ensures the total independence of the clients with respect to
+     their logging settings.
+
+     <p>Currently, the hierarchy that will be used for a given request
+     depends on the IP address of the client host. For example, two
+     separate applications running on the same host and logging to the
+     same server will share the same hierarchy. This is perfectly safe
+     except that it might not provide the right amount of independence
+     between applications. The <code>SocketServer</code> is intended
+     as an example to be enhanced in order to implement more elaborate
+     policies.
+
+
+    @author  Ceki G&uuml;lc&uuml;
+
+    @since 1.0 */
+
+public class SocketServer  {
+
+  static String GENERIC = "generic";
+  static String CONFIG_FILE_EXT = ".lcf";
+
+  static Logger cat = Logger.getLogger(SocketServer.class);
+  static SocketServer server;
+  static int port;
+
+  // key=inetAddress, value=hierarchy
+  Hashtable hierarchyMap;
+  LoggerRepository genericHierarchy;
+  File dir;
+
+  public
+  static
+  void main(String argv[]) {
+    if(argv.length == 3) {
+        init(argv[0], argv[1], argv[2]);
+    } else {
+        usage("Wrong number of arguments.");
+    }
+
+    try {
+      cat.info("Listening on port " + port);
+      ServerSocket serverSocket = new ServerSocket(port);
+      while(true) {
+       cat.info("Waiting to accept a new client.");
+       Socket socket = serverSocket.accept();
+       InetAddress inetAddress =  socket.getInetAddress();
+       cat.info("Connected to client at " + inetAddress);
+
+       LoggerRepository h = (LoggerRepository) server.hierarchyMap.get(inetAddress);
+       if(h == null) {
+         h = server.configureHierarchy(inetAddress);
+       }
+
+       cat.info("Starting new socket node.");
+       new Thread(new SocketNode(socket, h)).start();
+      }
+    }
+    catch(Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+
+  static
+  void  usage(String msg) {
+    System.err.println(msg);
+    System.err.println(
+      "Usage: java " +SocketServer.class.getName() + " port configFile directory");
+    System.exit(1);
+  }
+
+  static
+  void init(String portStr, String configFile, String dirStr) {
+    try {
+      port = Integer.parseInt(portStr);
+    }
+    catch(java.lang.NumberFormatException e) {
+      e.printStackTrace();
+      usage("Could not interpret port number ["+ portStr +"].");
+    }
+
+    PropertyConfigurator.configure(configFile);
+
+    File dir = new File(dirStr);
+    if(!dir.isDirectory()) {
+      usage("["+dirStr+"] is not a directory.");
+    }
+    server = new SocketServer(dir);
+  }
+
+
+  public
+  SocketServer(File directory) {
+    this.dir = directory;
+    hierarchyMap = new Hashtable(11);
+  }
+
+  // This method assumes that there is no hiearchy for inetAddress
+  // yet. It will configure one and return it.
+  LoggerRepository configureHierarchy(InetAddress inetAddress) {
+    cat.info("Locating configuration file for "+inetAddress);
+    // We assume that the toSting method of InetAddress returns is in
+    // the format hostname/d1.d2.d3.d4 e.g. torino/192.168.1.1
+    String s = inetAddress.toString();
+    int i = s.indexOf("/");
+    if(i == -1) {
+      cat.warn("Could not parse the inetAddress ["+inetAddress+
+              "]. Using default hierarchy.");
+      return genericHierarchy();
+    } else {
+      String key = s.substring(0, i);
+
+      File configFile = new File(dir, key+CONFIG_FILE_EXT);
+      if(configFile.exists()) {
+       Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
+       hierarchyMap.put(inetAddress, h);
+
+       new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);
+
+       return h;
+      } else {
+       cat.warn("Could not find config file ["+configFile+"].");
+       return genericHierarchy();
+      }
+    }
+  }
+
+  LoggerRepository  genericHierarchy() {
+    if(genericHierarchy == null) {
+      File f = new File(dir, GENERIC+CONFIG_FILE_EXT);
+      if(f.exists()) {
+       genericHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
+       new PropertyConfigurator().doConfigure(f.getAbsolutePath(), genericHierarchy);
+      } else {
+       cat.warn("Could not find config file ["+f+
+                "]. Will use the default hierarchy.");
+       genericHierarchy = LogManager.getLoggerRepository();
+      }
+    }
+    return genericHierarchy;
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/SyslogAppender.java b/srcjar/org/apache/log4j/net/SyslogAppender.java
new file mode 100644 (file)
index 0000000..b024645
--- /dev/null
@@ -0,0 +1,602 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.SyslogQuietWriter;
+import org.apache.log4j.helpers.SyslogWriter;
+import org.apache.log4j.spi.LoggingEvent;
+
+// Contributors: Yves Bossel <ybossel@opengets.cl>
+//               Christopher Taylor <cstaylor@pacbell.net>
+
+/**
+    Use SyslogAppender to send log messages to a remote syslog daemon.
+
+    @author Ceki G&uuml;lc&uuml;
+    @author Anders Kristensen
+ */
+public class SyslogAppender extends AppenderSkeleton {
+  // The following constants are extracted from a syslog.h file
+  // copyrighted by the Regents of the University of California
+  // I hope nobody at Berkley gets offended.
+
+  /**
+    * Maximum length of a TAG string.
+    */
+  private static final int MAX_TAG_LEN = 32;
+  
+  /** Kernel messages */
+  final static public int LOG_KERN     = 0;
+  /** Random user-level messages */
+  final static public int LOG_USER     = 1<<3;
+  /** Mail system */
+  final static public int LOG_MAIL     = 2<<3;
+  /** System daemons */
+  final static public int LOG_DAEMON   = 3<<3;
+  /** security/authorization messages */
+  final static public int LOG_AUTH     = 4<<3;
+  /** messages generated internally by syslogd */
+  final static public int LOG_SYSLOG   = 5<<3;
+
+  /** line printer subsystem */
+  final static public int LOG_LPR      = 6<<3;
+  /** network news subsystem */
+  final static public int LOG_NEWS     = 7<<3;
+  /** UUCP subsystem */
+  final static public int LOG_UUCP     = 8<<3;
+  /** clock daemon */
+  final static public int LOG_CRON     = 9<<3;
+  /** security/authorization  messages (private) */
+  final static public int LOG_AUTHPRIV = 10<<3;
+  /** ftp daemon */
+  final static public int LOG_FTP      = 11<<3;
+
+  // other codes through 15 reserved for system use
+  /** reserved for local use */
+  final static public int LOG_LOCAL0 = 16<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL1 = 17<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL2 = 18<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL3 = 19<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL4 = 20<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL5 = 21<<3;
+  /** reserved for local use */
+  final static public int LOG_LOCAL6 = 22<<3;
+  /** reserved for local use*/
+  final static public int LOG_LOCAL7 = 23<<3;
+
+  protected static final int SYSLOG_HOST_OI = 0;
+  protected static final int FACILITY_OI = 1;
+
+  static final String TAB = "    ";
+
+  static final Pattern NOT_ALPHANUM = Pattern.compile("[^\\p{Alnum}]");
+
+  // Have LOG_USER as default
+  int syslogFacility = LOG_USER;
+  String facilityStr;
+  boolean facilityPrinting = false;
+
+  //SyslogTracerPrintWriter stp;
+  SyslogQuietWriter sqw;
+  String syslogHost;
+
+    /**
+     * If true, the appender will generate the HEADER (timestamp and host name)
+     * part of the syslog packet.
+     * @since 1.2.15
+     */
+  private boolean header = false;
+  
+    /**
+     * The TAG part of the syslog message.
+     * 
+     * @since 1.2.18
+     */
+  private String tag = null;
+  
+    /**
+     * Date format used if header = true.
+     * @since 1.2.15
+     */
+  private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH);
+  
+    /**
+     * Host name used to identify messages from this appender.
+     * @since 1.2.15
+     */
+  private String localHostname;
+
+    /**
+     * Set to true after the header of the layout has been sent or if it has none.
+     */
+  private boolean layoutHeaderChecked = false;
+
+  public
+  SyslogAppender() {
+    this.initSyslogFacilityStr();
+  }
+
+  public
+  SyslogAppender(Layout layout, int syslogFacility) {
+    this.layout = layout;
+    this.syslogFacility = syslogFacility;
+    this.initSyslogFacilityStr();
+  }
+
+  public
+  SyslogAppender(Layout layout, String syslogHost, int syslogFacility) {
+    this(layout, syslogFacility);
+    setSyslogHost(syslogHost);
+  }
+
+  /**
+     Release any resources held by this SyslogAppender.
+
+     @since 0.8.4
+   */
+  synchronized
+  public
+  void close() {
+    closed = true;
+    if (sqw != null) {
+        try {
+            if (layoutHeaderChecked && layout != null && layout.getFooter() != null) {
+                sendLayoutMessage(layout.getFooter());
+            }
+            sqw.close();
+            sqw = null;
+        } catch(java.io.InterruptedIOException e) {
+            Thread.currentThread().interrupt();
+            sqw = null;
+        } catch(IOException e) {
+            sqw = null;
+        }
+    }
+  }
+
+  private
+  void initSyslogFacilityStr() {
+    facilityStr = getFacilityString(this.syslogFacility);
+
+    if (facilityStr == null) {
+      System.err.println("\"" + syslogFacility +
+                  "\" is an unknown syslog facility. Defaulting to \"USER\".");
+      this.syslogFacility = LOG_USER;
+      facilityStr = "user:";
+    } else {
+      facilityStr += ":";
+    }
+  }
+
+  /**
+     Returns the specified syslog facility as a lower-case String,
+     e.g. "kern", "user", etc.
+  */
+  public
+  static
+  String getFacilityString(int syslogFacility) {
+    switch(syslogFacility) {
+    case LOG_KERN:      return "kern";
+    case LOG_USER:      return "user";
+    case LOG_MAIL:      return "mail";
+    case LOG_DAEMON:    return "daemon";
+    case LOG_AUTH:      return "auth";
+    case LOG_SYSLOG:    return "syslog";
+    case LOG_LPR:       return "lpr";
+    case LOG_NEWS:      return "news";
+    case LOG_UUCP:      return "uucp";
+    case LOG_CRON:      return "cron";
+    case LOG_AUTHPRIV:  return "authpriv";
+    case LOG_FTP:       return "ftp";
+    case LOG_LOCAL0:    return "local0";
+    case LOG_LOCAL1:    return "local1";
+    case LOG_LOCAL2:    return "local2";
+    case LOG_LOCAL3:    return "local3";
+    case LOG_LOCAL4:    return "local4";
+    case LOG_LOCAL5:    return "local5";
+    case LOG_LOCAL6:    return "local6";
+    case LOG_LOCAL7:    return "local7";
+    default:            return null;
+    }
+  }
+
+  /**
+     Returns the integer value corresponding to the named syslog
+     facility, or -1 if it couldn't be recognized.
+
+     @param facilityName one of the strings KERN, USER, MAIL, DAEMON,
+            AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, LOCAL0,
+            LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7.
+            The matching is case-insensitive.
+
+     @since 1.1
+  */
+  public
+  static
+  int getFacility(String facilityName) {
+    if(facilityName != null) {
+      facilityName = facilityName.trim();
+    }
+    if("KERN".equalsIgnoreCase(facilityName)) {
+      return LOG_KERN;
+    } else if("USER".equalsIgnoreCase(facilityName)) {
+      return LOG_USER;
+    } else if("MAIL".equalsIgnoreCase(facilityName)) {
+      return LOG_MAIL;
+    } else if("DAEMON".equalsIgnoreCase(facilityName)) {
+      return LOG_DAEMON;
+    } else if("AUTH".equalsIgnoreCase(facilityName)) {
+      return LOG_AUTH;
+    } else if("SYSLOG".equalsIgnoreCase(facilityName)) {
+      return LOG_SYSLOG;
+    } else if("LPR".equalsIgnoreCase(facilityName)) {
+      return LOG_LPR;
+    } else if("NEWS".equalsIgnoreCase(facilityName)) {
+      return LOG_NEWS;
+    } else if("UUCP".equalsIgnoreCase(facilityName)) {
+      return LOG_UUCP;
+    } else if("CRON".equalsIgnoreCase(facilityName)) {
+      return LOG_CRON;
+    } else if("AUTHPRIV".equalsIgnoreCase(facilityName)) {
+      return LOG_AUTHPRIV;
+    } else if("FTP".equalsIgnoreCase(facilityName)) {
+      return LOG_FTP;
+    } else if("LOCAL0".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL0;
+    } else if("LOCAL1".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL1;
+    } else if("LOCAL2".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL2;
+    } else if("LOCAL3".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL3;
+    } else if("LOCAL4".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL4;
+    } else if("LOCAL5".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL5;
+    } else if("LOCAL6".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL6;
+    } else if("LOCAL7".equalsIgnoreCase(facilityName)) {
+      return LOG_LOCAL7;
+    } else {
+      return -1;
+    }
+  }
+
+
+  private void splitPacket(final String header, final String packet) {
+      int byteCount = packet.getBytes().length;
+      //
+      //   if packet is less than RFC 3164 limit
+      //      of 1024 bytes, then write it
+      //      (must allow for up 5to 5 characters in the PRI section
+      //          added by SyslogQuietWriter)
+      if (byteCount <= 1019) {
+          sqw.write(packet);
+      } else {
+          int split = header.length() + (packet.length() - header.length())/2;
+          splitPacket(header, packet.substring(0, split) + "...");
+          splitPacket(header, header + "..." + packet.substring(split));
+      }      
+  }
+
+  public
+  void append(LoggingEvent event) {
+
+    if(!isAsSevereAsThreshold(event.getLevel())) {
+        return;
+    }
+
+    // We must not attempt to append if sqw is null.
+    if(sqw == null) {
+      errorHandler.error("No syslog host is set for SyslogAppedender named \""+
+                       this.name+"\".");
+      return;
+    }
+
+    if (!layoutHeaderChecked) {
+        if (layout != null && layout.getHeader() != null) {
+            sendLayoutMessage(layout.getHeader());
+        }
+        layoutHeaderChecked = true;
+    }
+
+    String hdr = getPacketHeader(event.timeStamp);
+    String packet;
+    if (layout == null) {
+        packet = String.valueOf(event.getMessage());
+    } else {
+        packet = layout.format(event);
+    }
+    if(facilityPrinting || hdr.length() > 0) {
+        StringBuffer buf = new StringBuffer(hdr);
+        if(facilityPrinting) {
+            buf.append(facilityStr);
+        }
+        buf.append(packet);
+        packet = buf.toString();
+    }
+
+    sqw.setLevel(event.getLevel().getSyslogEquivalent());
+    //
+    //   if message has a remote likelihood of exceeding 1024 bytes
+    //      when encoded, consider splitting message into multiple packets
+    if (packet.length() > 256) {
+        splitPacket(hdr, packet);
+    } else {
+        sqw.write(packet);
+    }
+
+    if (layout == null || layout.ignoresThrowable()) {
+      String[] s = event.getThrowableStrRep();
+      if (s != null) {
+        for(int i = 0; i < s.length; i++) {
+            if (s[i].startsWith("\t")) {
+               sqw.write(hdr+TAB+s[i].substring(1));
+            } else {
+               sqw.write(hdr+s[i]);
+            }
+        }
+      }
+    }
+  }
+
+  /**
+     This method returns immediately as options are activated when they
+     are set.
+  */
+  public
+  void activateOptions() {
+      if (header) {
+        getLocalHostname();
+      }
+      if (layout != null && layout.getHeader() != null) {
+          sendLayoutMessage(layout.getHeader());
+      }
+      layoutHeaderChecked = true;
+  }
+
+  /**
+     The SyslogAppender requires a layout. Hence, this method returns
+     <code>true</code>.
+
+     @since 0.8.4 */
+  public
+  boolean requiresLayout() {
+    return true;
+  }
+
+  /**
+    The <b>SyslogHost</b> option is the name of the the syslog host
+    where log output should go.  A non-default port can be specified by
+    appending a colon and port number to a host name,
+    an IPv4 address or an IPv6 address enclosed in square brackets.
+
+    <b>WARNING</b> If the SyslogHost is not set, then this appender
+    will fail.
+   */
+  public
+  void setSyslogHost(final String syslogHost) {
+    this.sqw = new SyslogQuietWriter(new SyslogWriter(syslogHost),
+                                    syslogFacility, errorHandler);
+    //this.stp = new SyslogTracerPrintWriter(sqw);
+    this.syslogHost = syslogHost;
+  }
+
+  /**
+     Returns the value of the <b>SyslogHost</b> option.
+   */
+  public
+  String getSyslogHost() {
+    return syslogHost;
+  }
+
+  /**
+     Set the syslog facility. This is the <b>Facility</b> option.
+
+     <p>The <code>facilityName</code> parameter must be one of the
+     strings KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP,
+     CRON, AUTHPRIV, FTP, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4,
+     LOCAL5, LOCAL6, LOCAL7. Case is unimportant.
+
+     @since 0.8.1 */
+  public
+  void setFacility(String facilityName) {
+    if(facilityName == null) {
+        return;
+    }
+
+    syslogFacility = getFacility(facilityName);
+    if (syslogFacility == -1) {
+      System.err.println("["+facilityName +
+                  "] is an unknown syslog facility. Defaulting to [USER].");
+      syslogFacility = LOG_USER;
+    }
+
+    this.initSyslogFacilityStr();
+
+    // If there is already a sqw, make it use the new facility.
+    if(sqw != null) {
+      sqw.setSyslogFacility(this.syslogFacility);
+    }
+  }
+
+  /**
+     Returns the value of the <b>Facility</b> option.
+   */
+  public
+  String getFacility() {
+    return getFacilityString(syslogFacility);
+  }
+
+  /**
+    If the <b>FacilityPrinting</b> option is set to true, the printed
+    message will include the facility name of the application. It is
+    <em>false</em> by default.
+   */
+  public
+  void setFacilityPrinting(boolean on) {
+    facilityPrinting = on;
+  }
+
+  /**
+     Returns the value of the <b>FacilityPrinting</b> option.
+   */
+  public
+  boolean getFacilityPrinting() {
+    return facilityPrinting;
+  }
+
+  /**
+   * If true, the appender will generate the HEADER part (that is, timestamp and host name)
+   * of the syslog packet.  Default value is false for compatibility with existing behavior,
+   * however should be true unless there is a specific justification.
+   * @since 1.2.15
+  */
+  public final boolean getHeader() {
+      return header;
+  }
+
+    /**
+     * Returns whether the appender produces the HEADER part (that is, timestamp and host name)
+     * of the syslog packet.
+     * @since 1.2.15
+    */
+  public final void setHeader(final boolean val) {
+      header = val;
+  }
+
+    /**
+     * Sets the <b>Tag</b> option.
+     * 
+     * <p>
+     * If non-{@code null}, the printed HEADER will include the specified tag followed by a colon. If {@code null}, then no tag is printed.
+     * </p>
+     * <p>
+     * The default value is {@code null}.
+     * </p>
+     * 
+     * @param tag
+     *            the TAG to be printed out with the header
+     * @see #getTag()
+     * @since 1.2.18
+     */
+    public void setTag(final String tag) {
+        String newTag = tag;
+        if (newTag != null) {
+            if (newTag.length() > MAX_TAG_LEN) {
+                newTag = newTag.substring(0, MAX_TAG_LEN);
+            }
+            if (NOT_ALPHANUM.matcher(newTag).find()) {
+                throw new IllegalArgumentException("tag contains non-alphanumeric characters");
+            }
+        }
+
+        this.tag = newTag;
+    }
+
+    /**
+     * Gets the TAG to be printed with the HEADER portion of the log message. This will return {@code null} if no TAG is to be printed.
+     * <p>
+     * The default value is {@code null}.
+     * </p>
+     * 
+     * @return the TAG, max length 32.
+     * @see #setTag(String)
+     * @since 1.2.18
+     */
+  public String getTag() {
+      return this.tag;
+  }
+
+    /**
+     * Get the host name used to identify this appender.
+     * @return local host name
+     * @since 1.2.15
+     */
+  private String getLocalHostname() {
+      if (localHostname == null) {
+          try {
+            InetAddress addr = InetAddress.getLocalHost();
+            localHostname = addr.getHostName();
+          } catch (UnknownHostException uhe) {
+            localHostname = "UNKNOWN_HOST";
+          }
+      }
+      return localHostname;
+  }
+
+    /**
+     * Gets HEADER portion of packet.
+     * @param timeStamp number of milliseconds after the standard base time.
+     * @return HEADER portion of packet, will be zero-length string if header is false.
+     * @since 1.2.15
+     */
+  private String getPacketHeader(final long timeStamp) {
+      if (header) {
+        StringBuffer buf = new StringBuffer(dateFormat.format(new Date(timeStamp)));
+        //  RFC 3164 says leading space, not leading zero on days 1-9
+        if (buf.charAt(4) == '0') {
+          buf.setCharAt(4, ' ');
+        }
+        buf.append(getLocalHostname());
+        buf.append(' ');
+        if(this.tag != null) {
+            buf.append(this.tag);
+            buf.append(": ");
+        }
+        return buf.toString();
+      }
+      return "";
+  }
+
+    /**
+     * Set header or footer of layout.
+     * @param msg message body, may not be null.
+     */
+  private void sendLayoutMessage(final String msg) {
+      if (sqw != null) {
+          String packet = msg;
+          String hdr = getPacketHeader(new Date().getTime());
+          if(facilityPrinting || hdr.length() > 0) {
+              StringBuffer buf = new StringBuffer(hdr);
+              if(facilityPrinting) {
+                  buf.append(facilityStr);
+              }
+              buf.append(msg);
+              packet = buf.toString();
+          }
+          sqw.setLevel(6);
+          sqw.write(packet);
+      }
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/TelnetAppender.java b/srcjar/org/apache/log4j/net/TelnetAppender.java
new file mode 100644 (file)
index 0000000..5b49a2c
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.InterruptedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+  <p>The TelnetAppender is a log4j appender that specializes in
+  writing to a read-only socket.  The output is provided in a
+  telnet-friendly way so that a log can be monitored over TCP/IP.
+  Clients using telnet connect to the socket and receive log data.
+  This is handy for remote monitoring, especially when monitoring a
+  servlet.
+
+  <p>Here is a list of the available configuration options:
+
+  <table border=1>
+   <tr>
+   <th>Name</th>
+   <th>Requirement</th>
+   <th>Description</th>
+   <th>Sample Value</th>
+   </tr>
+
+   <tr>
+   <td>Port</td>
+   <td>optional</td>
+   <td>This parameter determines the port to use for announcing log events.  The default port is 23 (telnet).</td>
+   <td>5875</td>
+   </table>
+
+   @author <a HREF="mailto:jay@v-wave.com">Jay Funnell</a>
+*/
+
+public class TelnetAppender extends AppenderSkeleton {
+
+  private static final String EOL = "\r\n";
+  private SocketHandler sh;
+  private int port = 23;
+
+  /** 
+      This appender requires a layout to format the text to the
+      attached client(s). */
+  public boolean requiresLayout() {
+    return true;
+  }
+
+  /** all of the options have been set, create the socket handler and
+      wait for connections. */
+  public void activateOptions() {
+    try {
+      sh = new SocketHandler(port);
+      sh.start();
+    }
+    catch(InterruptedIOException e) {
+      Thread.currentThread().interrupt();
+      e.printStackTrace();
+    } catch(IOException e) {
+      e.printStackTrace();
+    } catch(RuntimeException e) {
+      e.printStackTrace();
+    }
+    super.activateOptions();
+  }
+
+  public
+  int getPort() {
+    return port;
+  }
+
+  public
+  void setPort(int port) {
+    this.port = port;
+  }
+
+
+  /** shuts down the appender. */
+  public void close() {
+    if (sh != null) {
+        sh.close();
+        try {
+            sh.join();
+        } catch(InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+    }
+  }
+
+  /** Handles a log event.  For this appender, that means writing the
+    message to each connected client.  */
+  protected void append(LoggingEvent event) {
+      if(sh != null) {
+        sh.send(layout.format(event));
+        if(layout.ignoresThrowable()) {
+            String[] s = event.getThrowableStrRep();
+            if (s != null) {
+                StringBuffer buf = new StringBuffer();
+                for(int i = 0; i < s.length; i++) {
+                    buf.append(s[i]);
+                    buf.append(EOL);
+                }
+                sh.send(buf.toString());
+            }
+        }
+      }
+  }
+
+  //---------------------------------------------------------- SocketHandler:
+
+  /** The SocketHandler class is used to accept connections from
+      clients.  It is threaded so that clients can connect/disconnect
+      asynchronously. */
+  protected class SocketHandler extends Thread {
+
+    private Vector writers = new Vector();
+    private Vector connections = new Vector();
+    private ServerSocket serverSocket;
+    private int MAX_CONNECTIONS = 20;
+
+    public void finalize() {
+        close();
+    }
+      
+    /** 
+    * make sure we close all network connections when this handler is destroyed.
+    * @since 1.2.15 
+    */
+    public void close() {
+      synchronized(this) {
+        for(Enumeration e = connections.elements();e.hasMoreElements();) {
+            try {
+                ((Socket)e.nextElement()).close();
+            } catch(InterruptedIOException ex) {
+                Thread.currentThread().interrupt();
+            } catch(IOException ex) {
+            } catch(RuntimeException ex) {
+            }
+        }
+      }
+
+      try {
+        serverSocket.close();
+      } catch(InterruptedIOException ex) {
+          Thread.currentThread().interrupt();
+      } catch(IOException ex) {
+      } catch(RuntimeException ex) {
+      }
+    }
+
+    /** sends a message to each of the clients in telnet-friendly output. */
+    public synchronized void send(final String message) {
+      Iterator ce = connections.iterator();
+      for(Iterator e = writers.iterator();e.hasNext();) {
+        ce.next();
+        PrintWriter writer = (PrintWriter)e.next();
+        writer.print(message);
+        if(writer.checkError()) {
+          ce.remove();
+          e.remove();
+        }
+      }
+    }
+
+    /** 
+       Continually accepts client connections.  Client connections
+        are refused when MAX_CONNECTIONS is reached. 
+    */
+    public void run() {
+      while(!serverSocket.isClosed()) {
+        try {
+          Socket newClient = serverSocket.accept();
+          PrintWriter pw = new PrintWriter(newClient.getOutputStream());
+          if(connections.size() < MAX_CONNECTIONS) {
+            synchronized(this) {
+                connections.addElement(newClient);
+                writers.addElement(pw);
+                pw.print("TelnetAppender v1.0 (" + connections.size()
+                           + " active connections)" + EOL + EOL);
+                pw.flush();
+            }
+          } else {
+            pw.print("Too many connections." + EOL);
+            pw.flush();
+            newClient.close();
+          }
+        } catch(Exception e) {
+          if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+              Thread.currentThread().interrupt();
+          }
+          if (!serverSocket.isClosed()) {
+            LogLog.error("Encountered error while in SocketHandler loop.", e);
+          }
+          break;
+        }
+      }
+
+      try {
+          serverSocket.close();
+      } catch(InterruptedIOException ex) {
+          Thread.currentThread().interrupt();
+      } catch(IOException ex) {
+      }
+    }
+
+    public SocketHandler(int port) throws IOException {
+      serverSocket = new ServerSocket(port);
+      setName("TelnetAppender-" + getName() + "-" + port);
+    }
+
+  }
+}
diff --git a/srcjar/org/apache/log4j/net/ZeroConfSupport.java b/srcjar/org/apache/log4j/net/ZeroConfSupport.java
new file mode 100644 (file)
index 0000000..2fb85dc
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.net;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.log4j.helpers.LogLog;
+
+public class ZeroConfSupport {
+    private static Object jmDNS = initializeJMDNS();
+
+    Object serviceInfo;
+    private static Class jmDNSClass;
+    private static Class serviceInfoClass;
+
+    public ZeroConfSupport(String zone, int port, String name, Map properties) {
+        //if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
+        boolean isVersion3 = false;
+        try {
+            //create method is in version 3, not version 1
+            jmDNSClass.getMethod("create", null);
+            isVersion3 = true;
+        } catch (NoSuchMethodException e) {
+            //no-op
+        }
+
+        if (isVersion3) {
+            LogLog.debug("using JmDNS version 3 to construct serviceInfo instance");
+            serviceInfo = buildServiceInfoVersion3(zone, port, name, properties);
+        } else {
+            LogLog.debug("using JmDNS version 1.0 to construct serviceInfo instance");
+            serviceInfo = buildServiceInfoVersion1(zone, port, name, properties);
+        }
+    }
+
+    public ZeroConfSupport(String zone, int port, String name) {
+        this(zone, port, name, new HashMap());
+    }
+
+    private static Object createJmDNSVersion1()
+    {
+        try {
+            return jmDNSClass.newInstance();
+        } catch (InstantiationException e) {
+            LogLog.warn("Unable to instantiate JMDNS", e);
+        } catch (IllegalAccessException e) {
+            LogLog.warn("Unable to instantiate JMDNS", e);
+        }
+        return null;
+    }
+
+    private static Object createJmDNSVersion3()
+    {
+        try {
+            Method jmDNSCreateMethod = jmDNSClass.getMethod("create", null);
+            return jmDNSCreateMethod.invoke(null, null);
+        } catch (IllegalAccessException e) {
+            LogLog.warn("Unable to instantiate jmdns class", e);
+        } catch (NoSuchMethodException e) {
+            LogLog.warn("Unable to access constructor", e);
+        } catch (InvocationTargetException e) {
+                LogLog.warn("Unable to call constructor", e);
+        }
+        return null;
+    }
+
+    private Object buildServiceInfoVersion1(String zone, int port, String name, Map properties) {
+        //version 1 uses a hashtable
+        Hashtable hashtableProperties = new Hashtable(properties);
+        try {
+            Class[] args = new Class[6];
+            args[0] = String.class;
+            args[1] = String.class;
+            args[2] = int.class;
+            args[3] = int.class; //weight (0)
+            args[4] = int.class; //priority (0)
+            args[5] = Hashtable.class;
+            Constructor constructor  = serviceInfoClass.getConstructor(args);
+            Object[] values = new Object[6];
+            values[0] = zone;
+            values[1] = name;
+            values[2] = new Integer(port);
+            values[3] = new Integer(0);
+            values[4] = new Integer(0);
+            values[5] = hashtableProperties;
+            Object result = constructor.newInstance(values);
+            LogLog.debug("created serviceinfo: " + result);
+            return result;
+        } catch (IllegalAccessException e) {
+            LogLog.warn("Unable to construct ServiceInfo instance", e);
+        } catch (NoSuchMethodException e) {
+            LogLog.warn("Unable to get ServiceInfo constructor", e);
+        } catch (InstantiationException e) {
+            LogLog.warn("Unable to construct ServiceInfo instance", e);
+        } catch (InvocationTargetException e) {
+            LogLog.warn("Unable to construct ServiceInfo instance", e);
+        }
+        return null;
+    }
+
+    private Object buildServiceInfoVersion3(String zone, int port, String name, Map properties) {
+        try {
+            Class[] args = new Class[6];
+            args[0] = String.class; //zone/type
+            args[1] = String.class; //display name
+            args[2] = int.class; //port
+            args[3] = int.class; //weight (0)
+            args[4] = int.class; //priority (0)
+            args[5] = Map.class;
+            Method serviceInfoCreateMethod = serviceInfoClass.getMethod("create", args);
+            Object[] values = new Object[6];
+            values[0] = zone;
+            values[1] = name;
+            values[2] = new Integer(port);
+            values[3] = new Integer(0);
+            values[4] = new Integer(0);
+            values[5] = properties;
+            Object result = serviceInfoCreateMethod.invoke(null, values);
+            LogLog.debug("created serviceinfo: " + result);
+            return result;
+        } catch (IllegalAccessException e) {
+            LogLog.warn("Unable to invoke create method", e);
+        } catch (NoSuchMethodException e) {
+            LogLog.warn("Unable to find create method", e);
+        } catch (InvocationTargetException e) {
+                LogLog.warn("Unable to invoke create method", e);
+        }
+        return null;
+    }
+
+    public void advertise() {
+        try {
+            Method method = jmDNSClass.getMethod("registerService", new Class[]{serviceInfoClass});
+            method.invoke(jmDNS, new Object[]{serviceInfo});
+            LogLog.debug("registered serviceInfo: " + serviceInfo);
+        } catch(IllegalAccessException e) {
+            LogLog.warn("Unable to invoke registerService method", e);
+        } catch(NoSuchMethodException e) {
+            LogLog.warn("No registerService method", e);
+        } catch(InvocationTargetException e) {
+            LogLog.warn("Unable to invoke registerService method", e);
+        }
+    }
+
+    public void unadvertise() {
+        try {
+            Method method = jmDNSClass.getMethod("unregisterService", new Class[]{serviceInfoClass});
+            method.invoke(jmDNS, new Object[]{serviceInfo});
+            LogLog.debug("unregistered serviceInfo: " + serviceInfo);
+        } catch(IllegalAccessException e) {
+            LogLog.warn("Unable to invoke unregisterService method", e);
+        } catch(NoSuchMethodException e) {
+            LogLog.warn("No unregisterService method", e);
+        } catch(InvocationTargetException e) {
+            LogLog.warn("Unable to invoke unregisterService method", e);
+       }
+    }
+
+    private static Object initializeJMDNS() {
+        try {
+            jmDNSClass = Class.forName("javax.jmdns.JmDNS");
+            serviceInfoClass = Class.forName("javax.jmdns.ServiceInfo");
+        } catch (ClassNotFoundException e) {
+            LogLog.warn("JmDNS or serviceInfo class not found", e);
+        }
+
+        //if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
+        boolean isVersion3 = false;
+        try {
+            //create method is in version 3, not version 1
+            jmDNSClass.getMethod("create", null);
+            isVersion3 = true;
+        } catch (NoSuchMethodException e) {
+            //no-op
+        }
+
+        if (isVersion3) {
+            return createJmDNSVersion3();
+        } else {
+            return createJmDNSVersion1();
+        }
+    }
+
+    public static Object getJMDNSInstance() {
+        return jmDNS;
+    }
+}
diff --git a/srcjar/org/apache/log4j/net/package.html b/srcjar/org/apache/log4j/net/package.html
new file mode 100644 (file)
index 0000000..1d2e965
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+<body>
+<p>Package for remote logging.
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Tue Mar 21 20:28:14 MET 2000
+<!-- hhmts end -->
+</body> </html>
diff --git a/srcjar/org/apache/log4j/nt/NTEventLogAppender.java b/srcjar/org/apache/log4j/nt/NTEventLogAppender.java
new file mode 100644 (file)
index 0000000..9f80add
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.nt;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Layout;
+import org.apache.log4j.TTCCLayout;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+   Append to the NT event log system.
+
+   <p><b>WARNING</b> This appender can only be installed and used on a
+   Windows system.
+
+   <p>Do not forget to place NTEventLogAppender.dll,
+   NTEventLogAppender.amd64.dll, NTEventLogAppender.ia64.dll
+   or NTEventLogAppender.x86.dll as appropriate in a
+   directory that is on the PATH of the Windows system. Otherwise, you
+   will get a java.lang.UnsatisfiedLinkError.
+
+   @author <a href="mailto:cstaylor@pacbell.net">Chris Taylor</a>
+   @author <a href="mailto:jim_cakalic@na.biomerieux.com">Jim Cakalic</a> */
+public class NTEventLogAppender extends AppenderSkeleton {
+  private int _handle = 0;
+
+  private String source = null;
+  private String server = null;
+
+
+  public NTEventLogAppender() {
+    this(null, null, null);
+  }
+
+  public NTEventLogAppender(String source) {
+    this(null, source, null);
+  }
+
+  public NTEventLogAppender(String server, String source) {
+    this(server, source, null);
+  }
+
+  public NTEventLogAppender(Layout layout) {
+    this(null, null, layout);
+  }
+
+  public NTEventLogAppender(String source, Layout layout) {
+    this(null, source, layout);
+  }
+
+  public NTEventLogAppender(String server, String source, Layout layout) {
+    if (source == null) {
+      source = "Log4j";
+    }
+    if (layout == null) {
+      this.layout = new TTCCLayout();
+    } else {
+      this.layout = layout;
+    }
+
+    try {
+      _handle = registerEventSource(server, source);
+    } catch (Exception e) {
+      e.printStackTrace();
+      _handle = 0;
+    }
+  }
+
+  public
+  void close() {
+    // unregister ...
+  }
+
+  public
+  void activateOptions() {
+    if (source != null) {
+      try {
+   _handle = registerEventSource(server, source);
+      } catch (Exception e) {
+   LogLog.error("Could not register event source.", e);
+   _handle = 0;
+      }
+    }
+  }
+
+
+  public void append(LoggingEvent event) {
+
+    StringBuffer sbuf = new StringBuffer();
+
+    sbuf.append(layout.format(event));
+    if(layout.ignoresThrowable()) {
+      String[] s = event.getThrowableStrRep();
+      if (s != null) {
+   int len = s.length;
+   for(int i = 0; i < len; i++) {
+     sbuf.append(s[i]);
+   }
+      }
+    }
+    // Normalize the log message level into the supported categories
+    int nt_category = event.getLevel().toInt();
+
+    // Anything above FATAL or below DEBUG is labeled as INFO.
+    //if (nt_category > FATAL || nt_category < DEBUG) {
+    //  nt_category = INFO;
+    //}
+    reportEvent(_handle, sbuf.toString(), nt_category);
+  }
+
+
+  public
+  void finalize() {
+    deregisterEventSource(_handle);
+    _handle = 0;
+  }
+
+  /**
+     The <b>Source</b> option which names the source of the event. The
+     current value of this constant is <b>Source</b>.
+   */
+  public
+  void setSource(String source) {
+    this.source = source.trim();
+  }
+
+  public
+  String getSource() {
+    return source;
+  }
+
+/**
+     The <code>NTEventLogAppender</code> requires a layout. Hence,
+     this method always returns <code>true</code>. */
+  public
+  boolean requiresLayout() {
+    return true;
+  }
+
+  native private int registerEventSource(String server, String source);
+  native private void reportEvent(int handle, String message, int level);
+  native private void deregisterEventSource(int handle);
+
+  static {
+    String[] archs;
+    try {
+        archs = new String[] { System.getProperty("os.arch")};
+    } catch(SecurityException e) {
+        archs = new String[] { "amd64", "ia64", "x86"};
+    }
+    boolean loaded = false;
+    for(int i = 0; i < archs.length; i++) {
+        try {
+            System.loadLibrary("NTEventLogAppender." + archs[i]);
+            loaded = true;
+            break;
+        } catch(java.lang.UnsatisfiedLinkError e) {
+            loaded = false;
+        }
+    }
+    if (!loaded) {
+        System.loadLibrary("NTEventLogAppender");
+    }
+}
+}
diff --git a/srcjar/org/apache/log4j/nt/package.html b/srcjar/org/apache/log4j/nt/package.html
new file mode 100644 (file)
index 0000000..a50567c
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>Package for NT event logging.
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Sat Apr 29 14:30:12 MDT 2000
+<!-- hhmts end -->
+</body> </html>
diff --git a/srcjar/org/apache/log4j/or/DefaultRenderer.java b/srcjar/org/apache/log4j/or/DefaultRenderer.java
new file mode 100644 (file)
index 0000000..7fe4ac6
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or;
+
+/**
+   The default Renderer renders objects by calling their
+   <code>toString</code> method.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.0 */
+class DefaultRenderer implements ObjectRenderer {
+  
+  DefaultRenderer() {
+  }
+
+  /**
+     Render the object passed as parameter by calling its
+     <code>toString</code> method.  */
+  public
+  String doRender(final Object o) {
+          try {
+            return o.toString();
+          } catch(Exception ex) {
+            return ex.toString();
+          }
+  }
+}  
diff --git a/srcjar/org/apache/log4j/or/ObjectRenderer.java b/srcjar/org/apache/log4j/or/ObjectRenderer.java
new file mode 100644 (file)
index 0000000..8ad9943
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or;
+
+/**
+   Implement this interface in order to render objects as strings.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.0 */
+public interface ObjectRenderer {
+
+  /**
+     Render the object passed as parameter as a String.
+   */
+  public
+  String doRender(Object o);
+}
diff --git a/srcjar/org/apache/log4j/or/RendererMap.java b/srcjar/org/apache/log4j/or/RendererMap.java
new file mode 100644 (file)
index 0000000..6b3483e
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or;
+
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.OptionConverter;
+import java.util.Hashtable;
+
+/**
+   Map class objects to an {@link ObjectRenderer}.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since version 1.0 */
+public class RendererMap {
+
+  Hashtable map;
+
+  static ObjectRenderer defaultRenderer = new DefaultRenderer();
+
+  public
+  RendererMap() {
+    map = new Hashtable();
+  }
+
+  /**
+     Add a renderer to a hierarchy passed as parameter.
+  */
+  static
+  public
+  void addRenderer(RendererSupport repository, String renderedClassName,
+                  String renderingClassName) {
+    LogLog.debug("Rendering class: ["+renderingClassName+"], Rendered class: ["+
+                renderedClassName+"].");
+    ObjectRenderer renderer = (ObjectRenderer)
+             OptionConverter.instantiateByClassName(renderingClassName,
+                                                   ObjectRenderer.class,
+                                                   null);
+    if(renderer == null) {
+      LogLog.error("Could not instantiate renderer ["+renderingClassName+"].");
+      return;
+    } else {
+      try {
+       Class renderedClass = Loader.loadClass(renderedClassName);
+       repository.setRenderer(renderedClass, renderer);
+      } catch(ClassNotFoundException e) {
+       LogLog.error("Could not find class ["+renderedClassName+"].", e);
+      }
+    }
+  }
+
+
+  /**
+     Find the appropriate renderer for the class type of the
+     <code>o</code> parameter. This is accomplished by calling the
+     {@link #get(Class)} method. Once a renderer is found, it is
+     applied on the object <code>o</code> and the result is returned
+     as a {@link String}. */
+  public
+  String findAndRender(Object o) {
+    if(o == null) {
+        return null;
+    } else {
+        return get(o.getClass()).doRender(o);
+    }
+  }
+
+
+  /**
+     Syntactic sugar method that calls {@link #get(Class)} with the
+     class of the object parameter. */
+  public
+  ObjectRenderer get(Object o) {
+    if(o == null) {
+        return null;
+    } else {
+        return get(o.getClass());
+    }
+  }
+
+
+  /**
+     Search the parents of <code>clazz</code> for a renderer. The
+     renderer closest in the hierarchy will be returned. If no
+     renderers could be found, then the default renderer is returned.
+
+     <p>The search first looks for a renderer configured for
+     <code>clazz</code>. If a renderer could not be found, then the
+     search continues by looking at all the interfaces implemented by
+     <code>clazz</code> including the super-interfaces of each
+     interface.  If a renderer cannot be found, then the search looks
+     for a renderer defined for the parent (superclass) of
+     <code>clazz</code>. If that fails, then all the interfaces
+     implemented by the parent of <code>clazz</code> are searched and
+     so on.
+
+     <p>For example, if A0, A1, A2 are classes and X0, X1, X2, Y0, Y1
+     are interfaces where A2 extends A1 which in turn extends A0 and
+     similarly X2 extends X1 which extends X0 and Y1 extends Y0. Let
+     us also assume that A1 implements the Y0 interface and that A2
+     implements the X2 interface.
+
+     <p>The table below shows the results returned by the
+     <code>get(A2.class)</code> method depending on the renderers
+     added to the map.
+
+     <p><table border="1">
+     <tr><th>Added renderers</th><th>Value returned by <code>get(A2.class)</code></th>
+
+     <tr><td><code>A0Renderer</code>
+         <td align="center"><code>A0Renderer</code>
+
+     <tr><td><code>A0Renderer, A1Renderer</code>
+         <td align="center"><code>A1Renderer</code>
+
+     <tr><td><code>X0Renderer</code>
+         <td align="center"><code>X0Renderer</code>
+
+     <tr><td><code>A1Renderer, X0Renderer</code>
+         <td align="center"><code>X0Renderer</code>
+
+     </table>
+
+     <p>This search algorithm is not the most natural, although it is
+     particularly easy to implement. Future log4j versions
+     <em>may</em> implement a more intuitive search
+     algorithm. However, the present algorithm should be acceptable in
+     the vast majority of circumstances.
+
+ */
+  public
+  ObjectRenderer get(Class clazz) {
+    //System.out.println("\nget: "+clazz);
+    ObjectRenderer r = null;
+    for(Class c = clazz; c != null; c = c.getSuperclass()) {
+      //System.out.println("Searching for class: "+c);
+      r = (ObjectRenderer) map.get(c);
+      if(r != null) {
+       return r;
+      }
+      r = searchInterfaces(c);
+      if(r != null) {
+        return r;
+    }
+    }
+    return defaultRenderer;
+  }
+
+  ObjectRenderer searchInterfaces(Class c) {
+    //System.out.println("Searching interfaces of class: "+c);
+
+    ObjectRenderer r = (ObjectRenderer) map.get(c);
+    if(r != null) {
+      return r;
+    } else {
+      Class[] ia = c.getInterfaces();
+      for(int i = 0; i < ia.length; i++) {
+       r = searchInterfaces(ia[i]);
+       if(r != null) {
+        return r;
+    }
+      }
+    }
+    return null;
+  }
+
+
+  public
+  ObjectRenderer getDefaultRenderer() {
+    return defaultRenderer;
+  }
+
+
+  public
+  void clear() {
+    map.clear();
+  }
+
+  /**
+     Register an {@link ObjectRenderer} for <code>clazz</code>.
+  */
+  public
+  void put(Class clazz, ObjectRenderer or) {
+    map.put(clazz, or);
+  }
+}
diff --git a/srcjar/org/apache/log4j/or/ThreadGroupRenderer.java b/srcjar/org/apache/log4j/or/ThreadGroupRenderer.java
new file mode 100644 (file)
index 0000000..026ff4f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or;
+
+import org.apache.log4j.Layout;
+
+
+/**
+   Render {@link ThreadGroup} objects in a format similar to the
+   information output by the {@link ThreadGroup#list} method.
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.0 */
+public class ThreadGroupRenderer implements ObjectRenderer {
+
+  public
+  ThreadGroupRenderer() {
+  }
+  
+  /**
+     Render a {@link ThreadGroup} object similar to the way that the
+     {@link ThreadGroup#list} method output information. 
+
+     <p>The output of a simple program consisting of one
+     <code>main</code> thread is:
+     <pre>
+     java.lang.ThreadGroup[name=main, maxpri=10]
+         Thread=[main,5,false]
+     </pre>
+     
+     <p>The boolean value in thread information is the value returned
+     by {@link Thread#isDaemon}.
+     
+  */
+  public
+  String  doRender(Object o) {
+    if(o instanceof ThreadGroup) {
+      StringBuffer sbuf = new StringBuffer();
+      ThreadGroup tg = (ThreadGroup) o;
+      sbuf.append("java.lang.ThreadGroup[name=");
+      sbuf.append(tg.getName());
+      sbuf.append(", maxpri=");
+      sbuf.append(tg.getMaxPriority());
+      sbuf.append("]");
+      Thread[] t = new Thread[tg.activeCount()];
+      tg.enumerate(t);
+      for(int i = 0; i < t.length; i++) {
+       sbuf.append(Layout.LINE_SEP);   
+       sbuf.append("   Thread=[");
+       sbuf.append(t[i].getName());
+       sbuf.append(",");
+       sbuf.append(t[i].getPriority());
+       sbuf.append(",");
+       sbuf.append(t[i].isDaemon());
+       sbuf.append("]");
+      }
+      return sbuf.toString();
+    } else {
+      try {
+        // this is the best we can do
+        return o.toString();
+      } catch(Exception ex) {
+          return ex.toString();
+      }
+    }    
+  }
+}  
diff --git a/srcjar/org/apache/log4j/or/jms/MessageRenderer.java b/srcjar/org/apache/log4j/or/jms/MessageRenderer.java
new file mode 100644 (file)
index 0000000..e3140cb
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or.jms;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.or.ObjectRenderer;
+
+import javax.jms.Message;
+import javax.jms.JMSException;
+import javax.jms.DeliveryMode;
+
+/**
+   Render <code>javax.jms.Message</code> objects.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.0 */
+public class MessageRenderer implements ObjectRenderer {
+
+  public
+  MessageRenderer() {
+  }
+
+
+  /**
+     Render a {@link javax.jms.Message}.
+  */
+  public
+  String  doRender(Object o) {
+    if(o instanceof Message) {
+      StringBuffer sbuf = new StringBuffer();
+      Message m = (Message) o;
+      try {
+       sbuf.append("DeliveryMode=");
+       switch(m.getJMSDeliveryMode()) {
+       case DeliveryMode.NON_PERSISTENT :
+         sbuf.append("NON_PERSISTENT");
+         break;
+       case DeliveryMode.PERSISTENT :
+         sbuf.append("PERSISTENT");
+         break;
+       default: sbuf.append("UNKNOWN");
+       }
+       sbuf.append(", CorrelationID=");
+       sbuf.append(m.getJMSCorrelationID());
+
+       sbuf.append(", Destination=");
+       sbuf.append(m.getJMSDestination());
+
+       sbuf.append(", Expiration=");
+       sbuf.append(m.getJMSExpiration());
+
+       sbuf.append(", MessageID=");
+       sbuf.append(m.getJMSMessageID());
+
+       sbuf.append(", Priority=");
+       sbuf.append(m.getJMSPriority());
+
+       sbuf.append(", Redelivered=");
+       sbuf.append(m.getJMSRedelivered());
+
+       sbuf.append(", ReplyTo=");
+       sbuf.append(m.getJMSReplyTo());
+
+       sbuf.append(", Timestamp=");
+       sbuf.append(m.getJMSTimestamp());
+
+       sbuf.append(", Type=");
+       sbuf.append(m.getJMSType());
+
+       //Enumeration enum = m.getPropertyNames();
+       //while(enum.hasMoreElements()) {
+       //  String key = (String) enum.nextElement();
+       //  sbuf.append("; "+key+"=");
+       //  sbuf.append(m.getStringProperty(key));
+       //}
+
+      } catch(JMSException e) {
+       LogLog.error("Could not parse Message.", e);
+      }
+      return sbuf.toString();
+    } else {
+      return o.toString();
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/or/jms/package.html b/srcjar/org/apache/log4j/or/jms/package.html
new file mode 100644 (file)
index 0000000..d4db1c8
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+
+  <body>
+    This package contains the MessageRenderer which renders objects of
+    type <code>javax.jms.Message</code>.
+  </body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/or/package.html b/srcjar/org/apache/log4j/or/package.html
new file mode 100644 (file)
index 0000000..17fd176
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+ <head>
+<title>org.apache.log4j.or package</title>
+</head>
+
+<body>
+
+<p>ObjectRenders are resposible for rendering messages depending on
+their class type.
+
+<hr>
+</body> </html>
diff --git a/srcjar/org/apache/log4j/or/sax/AttributesRenderer.java b/srcjar/org/apache/log4j/or/sax/AttributesRenderer.java
new file mode 100644 (file)
index 0000000..b5d088c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.or.sax;
+
+import org.apache.log4j.or.ObjectRenderer;
+
+import org.xml.sax.Attributes;
+
+/**
+   Render <code>org.xml.sax.Attributes</code> objects.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2 */
+public class AttributesRenderer implements ObjectRenderer {
+
+  public
+  AttributesRenderer() {
+  }
+
+
+  /**
+     Render a {@link org.xml.sax.Attributes}.
+  */
+  public
+  String  doRender(Object o) {
+    if(o instanceof Attributes) {
+      StringBuffer sbuf = new StringBuffer();
+      Attributes a = (Attributes) o;
+      int len = a.getLength();
+      boolean first = true;
+      for(int i = 0; i < len; i++) {
+       if(first) {
+         first = false;
+       } else {
+         sbuf.append(", ");
+       }
+       sbuf.append(a.getQName(i));
+       sbuf.append('=');
+       sbuf.append(a.getValue(i));
+      }
+      return sbuf.toString();
+    } else {
+      try {
+        return o.toString();
+      } catch(Exception ex) {
+          return ex.toString();
+      }
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/or/sax/package.html b/srcjar/org/apache/log4j/or/sax/package.html
new file mode 100644 (file)
index 0000000..a597141
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+
+  <body>
+    This package contains the AttributesRenderer which renders object of
+    class <code>org.xml.sax.Attributes</code>.
+  </body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/package.html b/srcjar/org/apache/log4j/package.html
new file mode 100644 (file)
index 0000000..3cf6e3e
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>The main log4j package.
+
+<hr>
+</body> </html>
diff --git a/srcjar/org/apache/log4j/pattern/BridgePatternConverter.java b/srcjar/org/apache/log4j/pattern/BridgePatternConverter.java
new file mode 100644 (file)
index 0000000..96068d5
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * The class implements the pre log4j 1.3 org.apache.log4j.helpers.PatternConverter
+ * contract by delegating to the log4j 1.3 pattern implementation.
+ *
+ *
+ * @author Curt Arnold
+ *
+ */
+public final class BridgePatternConverter
+  extends org.apache.log4j.helpers.PatternConverter {
+  /**
+   * Pattern converters.
+   */
+  private LoggingEventPatternConverter[] patternConverters;
+
+  /**
+   * Field widths and alignment corresponding to pattern converters.
+   */
+  private FormattingInfo[] patternFields;
+
+  /**
+   * Does pattern process exceptions.
+   */
+  private boolean handlesExceptions;
+
+  /**
+   * Create a new instance.
+   * @param pattern pattern, may not be null.
+   */
+  public BridgePatternConverter(
+    final String pattern) {
+    next = null;
+    handlesExceptions = false;
+
+    List converters = new ArrayList();
+    List fields = new ArrayList();
+    Map converterRegistry = null;
+
+    PatternParser.parse(
+      pattern, converters, fields, converterRegistry,
+      PatternParser.getPatternLayoutRules());
+
+    patternConverters = new LoggingEventPatternConverter[converters.size()];
+    patternFields = new FormattingInfo[converters.size()];
+
+    int i = 0;
+    Iterator converterIter = converters.iterator();
+    Iterator fieldIter = fields.iterator();
+
+    while (converterIter.hasNext()) {
+      Object converter = converterIter.next();
+
+      if (converter instanceof LoggingEventPatternConverter) {
+        patternConverters[i] = (LoggingEventPatternConverter) converter;
+        handlesExceptions |= patternConverters[i].handlesThrowable();
+      } else {
+        patternConverters[i] =
+          new org.apache.log4j.pattern.LiteralPatternConverter("");
+      }
+
+      if (fieldIter.hasNext()) {
+        patternFields[i] = (FormattingInfo) fieldIter.next();
+      } else {
+        patternFields[i] = FormattingInfo.getDefault();
+      }
+
+      i++;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected String convert(final LoggingEvent event) {
+    //
+    //  code should be unreachable.
+    //
+    StringBuffer sbuf = new StringBuffer();
+    format(sbuf, event);
+
+    return sbuf.toString();
+  }
+
+  /**
+     Format event to string buffer.
+     @param sbuf string buffer to receive formatted event, may not be null.
+     @param e event to format, may not be null.
+   */
+  public void format(final StringBuffer sbuf, final LoggingEvent e) {
+    for (int i = 0; i < patternConverters.length; i++) {
+      int startField = sbuf.length();
+      patternConverters[i].format(e, sbuf);
+      patternFields[i].format(startField, sbuf);
+    }
+  }
+
+  /**
+   * Will return false if any of the conversion specifiers in the pattern
+   * handles {@link Exception Exceptions}.
+   * @return true if the pattern formats any information from exceptions.
+   */
+  public boolean ignoresThrowable() {
+    return !handlesExceptions;
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/BridgePatternParser.java b/srcjar/org/apache/log4j/pattern/BridgePatternParser.java
new file mode 100644 (file)
index 0000000..1c0d4e7
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+/**
+ * The class implements the pre log4j 1.3 org.apache.log4j.helpers.PatternConverter
+ * contract by delegating to the log4j 1.3 pattern implementation.
+ *
+ *
+ * @author Curt Arnold
+ *
+ */
+public final class BridgePatternParser
+  extends org.apache.log4j.helpers.PatternParser {
+
+
+  /**
+   * Create a new instance.
+   * @param conversionPattern pattern, may not be null.
+   */
+  public BridgePatternParser(
+    final String conversionPattern) {
+    super(conversionPattern);
+  }
+
+  /**
+   * Create new pattern converter.
+   * @return pattern converter.
+   */
+  public org.apache.log4j.helpers.PatternConverter parse() {
+    return new BridgePatternConverter(pattern);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/CachedDateFormat.java b/srcjar/org/apache/log4j/pattern/CachedDateFormat.java
new file mode 100644 (file)
index 0000000..9a468aa
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.TimeZone;
+
+
+/**
+ * CachedDateFormat optimizes the performance of a wrapped
+ * DateFormat.  The implementation is not thread-safe.
+ * If the millisecond pattern is not recognized,
+ * the class will only use the cache if the
+ * same value is requested.
+ *
+ */
+public final class CachedDateFormat extends DateFormat {
+  /**
+   *  Serialization version.
+  */
+  private static final long serialVersionUID = 1;
+  /**
+   *  Constant used to represent that there was no change
+   *  observed when changing the millisecond count.
+   */
+  public static final int NO_MILLISECONDS = -2;
+
+  /**
+   *  Supported digit set.  If the wrapped DateFormat uses
+   *  a different unit set, the millisecond pattern
+   *  will not be recognized and duplicate requests
+   *  will use the cache.
+   */
+  private static final String DIGITS = "0123456789";
+
+  /**
+   *  Constant used to represent that there was an
+   *  observed change, but was an expected change.
+   */
+  public static final int UNRECOGNIZED_MILLISECONDS = -1;
+
+  /**
+   *  First magic number used to detect the millisecond position.
+   */
+  private static final int MAGIC1 = 654;
+
+  /**
+   *  Expected representation of first magic number.
+   */
+  private static final String MAGICSTRING1 = "654";
+
+  /**
+   *  Second magic number used to detect the millisecond position.
+   */
+  private static final int MAGIC2 = 987;
+
+  /**
+   *  Expected representation of second magic number.
+   */
+  private static final String MAGICSTRING2 = "987";
+
+  /**
+   *  Expected representation of 0 milliseconds.
+   */
+  private static final String ZERO_STRING = "000";
+
+  /**
+   *   Wrapped formatter.
+   */
+  private final DateFormat formatter;
+
+  /**
+   *  Index of initial digit of millisecond pattern or
+   *   UNRECOGNIZED_MILLISECONDS or NO_MILLISECONDS.
+   */
+  private int millisecondStart;
+
+  /**
+   *  Integral second preceding the previous convered Date.
+   */
+  private long slotBegin;
+
+  /**
+   *  Cache of previous conversion.
+   */
+  private StringBuffer cache = new StringBuffer(50);
+
+  /**
+   *  Maximum validity period for the cache.
+   *  Typically 1, use cache for duplicate requests only, or
+   *  1000, use cache for requests within the same integral second.
+   */
+  private final int expiration;
+
+  /**
+   *  Date requested in previous conversion.
+   */
+  private long previousTime;
+
+  /**
+   *   Scratch date object used to minimize date object creation.
+   */
+  private final Date tmpDate = new Date(0);
+
+  /**
+   *  Creates a new CachedDateFormat object.
+   *  @param dateFormat Date format, may not be null.
+   *  @param expiration maximum cached range in milliseconds.
+   *    If the dateFormat is known to be incompatible with the
+   *      caching algorithm, use a value of 0 to totally disable
+   *      caching or 1 to only use cache for duplicate requests.
+   */
+  public CachedDateFormat(final DateFormat dateFormat, final int expiration) {
+    if (dateFormat == null) {
+      throw new IllegalArgumentException("dateFormat cannot be null");
+    }
+
+    if (expiration < 0) {
+      throw new IllegalArgumentException("expiration must be non-negative");
+    }
+
+    formatter = dateFormat;
+    this.expiration = expiration;
+    millisecondStart = 0;
+
+    //
+    //   set the previousTime so the cache will be invalid
+    //        for the next request.
+    previousTime = Long.MIN_VALUE;
+    slotBegin = Long.MIN_VALUE;
+  }
+
+  /**
+   * Finds start of millisecond field in formatted time.
+   * @param time long time, must be integral number of seconds
+   * @param formatted String corresponding formatted string
+   * @param formatter DateFormat date format
+   * @return int position in string of first digit of milliseconds,
+   *    -1 indicates no millisecond field, -2 indicates unrecognized
+   *    field (likely RelativeTimeDateFormat)
+   */
+  public static int findMillisecondStart(
+    final long time, final String formatted, final DateFormat formatter) {
+    long slotBegin = (time / 1000) * 1000;
+
+    if (slotBegin > time) {
+      slotBegin -= 1000;
+    }
+
+    int millis = (int) (time - slotBegin);
+
+    int magic = MAGIC1;
+    String magicString = MAGICSTRING1;
+
+    if (millis == MAGIC1) {
+      magic = MAGIC2;
+      magicString = MAGICSTRING2;
+    }
+
+    String plusMagic = formatter.format(new Date(slotBegin + magic));
+
+    /**
+     *   If the string lengths differ then
+     *      we can't use the cache except for duplicate requests.
+     */
+    if (plusMagic.length() != formatted.length()) {
+      return UNRECOGNIZED_MILLISECONDS;
+    } else {
+      // find first difference between values
+      for (int i = 0; i < formatted.length(); i++) {
+        if (formatted.charAt(i) != plusMagic.charAt(i)) {
+          //
+          //   determine the expected digits for the base time
+          StringBuffer formattedMillis = new StringBuffer("ABC");
+          millisecondFormat(millis, formattedMillis, 0);
+
+          String plusZero = formatter.format(new Date(slotBegin));
+
+          //   If the next 3 characters match the magic
+          //      string and the expected string
+          if (
+            (plusZero.length() == formatted.length())
+              && magicString.regionMatches(
+                0, plusMagic, i, magicString.length())
+              && formattedMillis.toString().regionMatches(
+                0, formatted, i, magicString.length())
+              && ZERO_STRING.regionMatches(
+                0, plusZero, i, ZERO_STRING.length())) {
+            return i;
+          } else {
+            return UNRECOGNIZED_MILLISECONDS;
+          }
+        }
+      }
+    }
+
+    return NO_MILLISECONDS;
+  }
+
+  /**
+   * Formats a Date into a date/time string.
+   *
+   *  @param date the date to format.
+   *  @param sbuf the string buffer to write to.
+   *  @param fieldPosition remains untouched.
+   * @return the formatted time string.
+   */
+  public StringBuffer format(
+    Date date, StringBuffer sbuf, FieldPosition fieldPosition) {
+    format(date.getTime(), sbuf);
+
+    return sbuf;
+  }
+
+  /**
+   * Formats a millisecond count into a date/time string.
+   *
+   *  @param now Number of milliseconds after midnight 1 Jan 1970 GMT.
+   *  @param buf the string buffer to write to.
+   * @return the formatted time string.
+   */
+  public StringBuffer format(long now, StringBuffer buf) {
+    //
+    // If the current requested time is identical to the previously
+    //     requested time, then append the cache contents.
+    //
+    if (now == previousTime) {
+      buf.append(cache);
+
+      return buf;
+    }
+
+    //
+    //   If millisecond pattern was not unrecognized 
+    //     (that is if it was found or milliseconds did not appear)   
+    //    
+    if (millisecondStart != UNRECOGNIZED_MILLISECONDS &&
+      //    Check if the cache is still valid.
+      //    If the requested time is within the same integral second
+      //       as the last request and a shorter expiration was not requested.
+        (now < (slotBegin + expiration)) && (now >= slotBegin)
+          && (now < (slotBegin + 1000L))) {
+        // 
+        //    if there was a millisecond field then update it
+        //
+        if (millisecondStart >= 0) {
+          millisecondFormat((int) (now - slotBegin), cache, millisecondStart);
+        }
+
+        //
+        //   update the previously requested time
+        //      (the slot begin should be unchanged)
+        previousTime = now;
+        buf.append(cache);
+
+        return buf;
+    }
+
+    //
+    //  could not use previous value.  
+    //    Call underlying formatter to format date.
+    cache.setLength(0);
+    tmpDate.setTime(now);
+    cache.append(formatter.format(tmpDate));
+    buf.append(cache);
+    previousTime = now;
+    slotBegin = (previousTime / 1000) * 1000;
+
+    if (slotBegin > previousTime) {
+      slotBegin -= 1000;
+    }
+
+    //
+    //    if the milliseconds field was previous found
+    //       then reevaluate in case it moved.
+    //
+    if (millisecondStart >= 0) {
+      millisecondStart =
+        findMillisecondStart(now, cache.toString(), formatter);
+    }
+
+    return buf;
+  }
+
+  /**
+   *   Formats a count of milliseconds (0-999) into a numeric representation.
+   *   @param millis Millisecond coun between 0 and 999.
+   *   @param buf String buffer, may not be null.
+   *   @param offset Starting position in buffer, the length of the
+   *       buffer must be at least offset + 3.
+   */
+  private static void millisecondFormat(
+    final int millis, final StringBuffer buf, final int offset) {
+    buf.setCharAt(offset, DIGITS.charAt(millis / 100));
+    buf.setCharAt(offset + 1, DIGITS.charAt((millis / 10) % 10));
+    buf.setCharAt(offset + 2, DIGITS.charAt(millis % 10));
+  }
+
+  /**
+   * Set timezone.
+   *
+   * Setting the timezone using getCalendar().setTimeZone()
+   * will likely cause caching to misbehave.
+   * @param timeZone TimeZone new timezone
+   */
+  public void setTimeZone(final TimeZone timeZone) {
+    formatter.setTimeZone(timeZone);
+    previousTime = Long.MIN_VALUE;
+    slotBegin = Long.MIN_VALUE;
+  }
+
+  /**
+   *  This method is delegated to the formatter which most
+   *  likely returns null.
+   * @param s string representation of date.
+   * @param pos field position, unused.
+   * @return parsed date, likely null.
+   */
+  public Date parse(String s, ParsePosition pos) {
+    return formatter.parse(s, pos);
+  }
+
+  /**
+   * Gets number formatter.
+   *
+   * @return NumberFormat number formatter
+   */
+  public NumberFormat getNumberFormat() {
+    return formatter.getNumberFormat();
+  }
+
+  /**
+   * Gets maximum cache validity for the specified SimpleDateTime
+   *    conversion pattern.
+   *  @param pattern conversion pattern, may not be null.
+   *  @return Duration in milliseconds from an integral second
+   *      that the cache will return consistent results.
+   */
+  public static int getMaximumCacheValidity(final String pattern) {
+    //
+    //   If there are more "S" in the pattern than just one "SSS" then
+    //      (for example, "HH:mm:ss,SSS SSS"), then set the expiration to
+    //      one millisecond which should only perform duplicate request caching.
+    //
+    int firstS = pattern.indexOf('S');
+
+    if ((firstS >= 0) && (firstS != pattern.lastIndexOf("SSS"))) {
+      return 1;
+    }
+
+    return 1000;
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/ClassNamePatternConverter.java b/srcjar/org/apache/log4j/pattern/ClassNamePatternConverter.java
new file mode 100644 (file)
index 0000000..7a24916
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats the class name of the site of the logging request.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class ClassNamePatternConverter extends NamePatternConverter {
+  /**
+   * Private constructor.
+   * @param options options, may be null.
+   */
+  private ClassNamePatternConverter(
+    final String[] options) {
+    super("Class Name", "class name", options);
+  }
+
+  /**
+   * Gets an instance of ClassNamePatternConverter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static ClassNamePatternConverter newInstance(
+    final String[] options) {
+    return new ClassNamePatternConverter(options);
+  }
+
+  /**
+   * Format a logging event.
+    * @param event event to format.
+   * @param toAppendTo string buffer to which class name will be appended.
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    final int initialLength = toAppendTo.length();
+    LocationInfo li = event.getLocationInformation();
+
+    if (li == null) {
+      toAppendTo.append(LocationInfo.NA);
+    } else {
+      toAppendTo.append(li.getClassName());
+    }
+
+    abbreviate(initialLength, toAppendTo);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/DatePatternConverter.java b/srcjar/org/apache/log4j/pattern/DatePatternConverter.java
new file mode 100644 (file)
index 0000000..511fb70
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.text.SimpleDateFormat;
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.TimeZone;
+
+
+/**
+ * Convert and format the event's date in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class DatePatternConverter extends LoggingEventPatternConverter {
+    /**
+     * ABSOLUTE string literal.
+     */
+  private static final String ABSOLUTE_FORMAT = "ABSOLUTE";
+    /**
+     * SimpleTimePattern for ABSOLUTE.
+     */
+  private static final String ABSOLUTE_TIME_PATTERN = "HH:mm:ss,SSS";
+
+
+    /**
+     * DATE string literal.
+     */
+  private static final String DATE_AND_TIME_FORMAT = "DATE";
+    /**
+     * SimpleTimePattern for DATE.
+     */
+  private static final String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
+
+    /**
+     * ISO8601 string literal.
+     */
+  private static final String ISO8601_FORMAT = "ISO8601";
+    /**
+     * SimpleTimePattern for ISO8601.
+     */
+  private static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
+  /**
+   * Date format.
+   */
+  private final CachedDateFormat df;
+
+    /**
+     * This class wraps a DateFormat and forces the time zone to the
+     *   default time zone before each format and parse request.
+     */
+  private static class DefaultZoneDateFormat extends DateFormat {
+     /**
+      * Serialization version ID.
+      */
+     private static final long serialVersionUID = 1;
+     /**
+         * Wrapped instance of DateFormat.
+         */
+    private final DateFormat dateFormat;
+
+        /**
+         * Construct new instance.
+         * @param format format, may not be null.
+         */
+    public DefaultZoneDateFormat(final DateFormat format) {
+        dateFormat = format;
+    }
+
+        /**
+         * @{inheritDoc}
+         */
+    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
+        dateFormat.setTimeZone(TimeZone.getDefault());
+        return dateFormat.format(date, toAppendTo, fieldPosition);
+    }
+
+        /**
+         * @{inheritDoc}
+         */
+    public Date parse(String source, ParsePosition pos) {
+        dateFormat.setTimeZone(TimeZone.getDefault());
+        return dateFormat.parse(source, pos);
+    }
+  }
+  
+  /**
+   * Private constructor.
+   * @param options options, may be null.
+   */
+  private DatePatternConverter(final String[] options) {
+    super("Date", "date");
+
+    String patternOption;
+
+    if ((options == null) || (options.length == 0)) {
+      // the branch could be optimized, but here we are making explicit
+      // that null values for patternOption are allowed.
+      patternOption = null;
+    } else {
+      patternOption = options[0];
+    }
+
+    String pattern;
+
+    if (
+      (patternOption == null)
+        || patternOption.equalsIgnoreCase(ISO8601_FORMAT)) {
+      pattern = ISO8601_PATTERN;
+    } else if (patternOption.equalsIgnoreCase(ABSOLUTE_FORMAT)) {
+      pattern = ABSOLUTE_TIME_PATTERN;
+    } else if (patternOption.equalsIgnoreCase(DATE_AND_TIME_FORMAT)) {
+      pattern = DATE_AND_TIME_PATTERN;
+    } else {
+      pattern = patternOption;
+    }
+
+    int maximumCacheValidity = 1000;
+    DateFormat simpleFormat = null;
+
+    try {
+      simpleFormat = new SimpleDateFormat(pattern);
+      maximumCacheValidity = CachedDateFormat.getMaximumCacheValidity(pattern);
+    } catch (IllegalArgumentException e) {
+        LogLog.warn(
+          "Could not instantiate SimpleDateFormat with pattern "
+          + patternOption, e);
+
+      // default to the ISO8601 format
+      simpleFormat = new SimpleDateFormat(ISO8601_PATTERN);
+    }
+
+    // if the option list contains a TZ option, then set it.
+    if ((options != null) && (options.length > 1)) {
+      TimeZone tz = TimeZone.getTimeZone(options[1]);
+      simpleFormat.setTimeZone(tz);
+    } else {
+      simpleFormat = new DefaultZoneDateFormat(simpleFormat);
+    }
+
+    df = new CachedDateFormat(simpleFormat, maximumCacheValidity);
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static DatePatternConverter newInstance(
+    final String[] options) {
+    return new DatePatternConverter(options);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer output) {
+    synchronized(this) {
+       df.format(event.timeStamp, output);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final Object obj, final StringBuffer output) {
+    if (obj instanceof Date) {
+      format((Date) obj, output);
+    }
+
+    super.format(obj, output);
+  }
+
+  /**
+   * Append formatted date to string buffer.
+   * @param date date
+   * @param toAppendTo buffer to which formatted date is appended.
+   */
+  public void format(final Date date, final StringBuffer toAppendTo) {
+    synchronized(this) {
+       df.format(date.getTime(), toAppendTo);
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/FileDatePatternConverter.java b/srcjar/org/apache/log4j/pattern/FileDatePatternConverter.java
new file mode 100644 (file)
index 0000000..1e08826
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+/**
+ * Formats an date by delegating to DatePatternConverter.  The default
+ * date pattern for a %d specifier in a file name is different than
+ * the %d pattern in pattern layout.
+ *
+ * @author Curt Arnold
+ */
+public final class FileDatePatternConverter {
+  /**
+   * Private constructor.
+   */
+  private FileDatePatternConverter() {
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static PatternConverter newInstance(
+    final String[] options) {
+    if ((options == null) || (options.length == 0)) {
+      return DatePatternConverter.newInstance(
+        new String[] {
+                "yyyy-MM-dd"
+        });
+    }
+
+    return DatePatternConverter.newInstance(options);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/FileLocationPatternConverter.java b/srcjar/org/apache/log4j/pattern/FileLocationPatternConverter.java
new file mode 100644 (file)
index 0000000..14eb31c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's line location information in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class FileLocationPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final FileLocationPatternConverter INSTANCE =
+    new FileLocationPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private FileLocationPatternConverter() {
+    super("File Location", "file");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static FileLocationPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer output) {
+    LocationInfo locationInfo = event.getLocationInformation();
+
+    if (locationInfo != null) {
+      output.append(locationInfo.getFileName());
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/FormattingInfo.java b/srcjar/org/apache/log4j/pattern/FormattingInfo.java
new file mode 100644 (file)
index 0000000..3fb127e
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+
+/**
+ * Modifies the output of a pattern converter for a specified minimum
+ * and maximum width and alignment.
+ *
+ *
+ *  @author <a href=mailto:jim_cakalic@na.biomerieux.com>Jim Cakalic</a>
+ *  @author Ceki G&uuml;lc&uuml;
+ *  @author Curt Arnold
+ *
+ */
+public final class FormattingInfo {
+  /**
+   *  Array of spaces.
+   */
+  private static final char[] SPACES =
+    new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
+
+  /**
+   * Default instance.
+   */
+  private static final FormattingInfo DEFAULT =
+    new FormattingInfo(false, 0, Integer.MAX_VALUE);
+
+  /**
+   * Minimum length.
+   */
+  private final int minLength;
+
+  /**
+   * Maximum length.
+   */
+  private final int maxLength;
+
+  /**
+   * Alignment.
+   */
+  private final boolean leftAlign;
+
+  /**
+   * Right truncation.
+   * @since 1.2.17
+   */
+  private final boolean rightTruncate;
+
+  /**
+   * Creates new instance.
+   * @param leftAlign left align if true.
+   * @param minLength minimum length.
+   * @param maxLength maximum length.
+   * @deprecated since 1.2.17
+   */
+  public FormattingInfo(
+    final boolean leftAlign, 
+    final int minLength, 
+    final int maxLength) {
+    this.leftAlign = leftAlign;
+    this.minLength = minLength;
+    this.maxLength = maxLength;
+    this.rightTruncate = false;
+  }
+
+  /**
+   * Creates new instance.
+   * @param leftAlign left align if true.
+   * @param rightTruncate right truncate if true.
+   * @param minLength minimum length.
+   * @param maxLength maximum length.
+   * @since 1.2.17
+   */
+  public FormattingInfo(
+    final boolean leftAlign, 
+    final boolean rightTruncate,
+    final int minLength, 
+    final int maxLength) {
+    this.leftAlign = leftAlign;
+    this.minLength = minLength;
+    this.maxLength = maxLength;
+    this.rightTruncate = rightTruncate;
+  }
+
+  /**
+   * Gets default instance.
+   * @return default instance.
+   */
+  public static FormattingInfo getDefault() {
+    return DEFAULT;
+  }
+
+  /**
+   * Determine if left aligned.
+   * @return true if left aligned.
+   */
+  public boolean isLeftAligned() {
+    return leftAlign;
+  }
+
+  /**
+   * Determine if right truncated.
+   * @return true if right truncated.
+   * @since 1.2.17
+   */
+  public boolean isRightTruncated() {
+    return rightTruncate;
+  }
+
+  /**
+   * Get minimum length.
+   * @return minimum length.
+   */
+  public int getMinLength() {
+    return minLength;
+  }
+
+  /**
+   * Get maximum length.
+   * @return maximum length.
+   */
+  public int getMaxLength() {
+    return maxLength;
+  }
+
+  /**
+   * Adjust the content of the buffer based on the specified lengths and alignment.
+   *
+   * @param fieldStart start of field in buffer.
+   * @param buffer buffer to be modified.
+   */
+  public void format(final int fieldStart, final StringBuffer buffer) {
+    final int rawLength = buffer.length() - fieldStart;
+
+    if (rawLength > maxLength) {
+      if(rightTruncate) {
+         buffer.setLength(fieldStart + maxLength);
+      } else {
+         buffer.delete(fieldStart, buffer.length() - maxLength);
+      }
+    } else if (rawLength < minLength) {
+      if (leftAlign) {
+        final int fieldEnd = buffer.length();
+        buffer.setLength(fieldStart + minLength);
+
+        for (int i = fieldEnd; i < buffer.length(); i++) {
+          buffer.setCharAt(i, ' ');
+        }
+      } else {
+        int padLength = minLength - rawLength;
+
+        for (; padLength > 8; padLength -= 8) {
+          buffer.insert(fieldStart, SPACES);
+        }
+
+        buffer.insert(fieldStart, SPACES, 0, padLength);
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/FullLocationPatternConverter.java b/srcjar/org/apache/log4j/pattern/FullLocationPatternConverter.java
new file mode 100644 (file)
index 0000000..066016c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Format the event's line location information.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class FullLocationPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final FullLocationPatternConverter INSTANCE =
+    new FullLocationPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private FullLocationPatternConverter() {
+    super("Full Location", "fullLocation");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static FullLocationPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer output) {
+    LocationInfo locationInfo = event.getLocationInformation();
+
+    if (locationInfo != null) {
+      output.append(locationInfo.fullInfo);
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/IntegerPatternConverter.java b/srcjar/org/apache/log4j/pattern/IntegerPatternConverter.java
new file mode 100644 (file)
index 0000000..f50feeb
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import java.util.Date;
+
+
+/**
+ * Formats an integer.
+ *
+ * @author Curt Arnold
+ */
+public final class IntegerPatternConverter extends PatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final IntegerPatternConverter INSTANCE =
+    new IntegerPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private IntegerPatternConverter() {
+    super("Integer", "integer");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static IntegerPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(Object obj, final StringBuffer toAppendTo) {
+    if (obj instanceof Integer) {
+      toAppendTo.append(obj.toString());
+    }
+
+    if (obj instanceof Date) {
+      toAppendTo.append(Long.toString(((Date) obj).getTime()));
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LevelPatternConverter.java b/srcjar/org/apache/log4j/pattern/LevelPatternConverter.java
new file mode 100644 (file)
index 0000000..3dbc6e6
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's level in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class LevelPatternConverter extends LoggingEventPatternConverter {
+
+  /**
+   *   Integer severity for Level.TRACE.
+   */
+  private static final int TRACE_INT = 5000;
+  /**
+   * Singleton.
+   */
+  private static final LevelPatternConverter INSTANCE =
+    new LevelPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private LevelPatternConverter() {
+    super("Level", "level");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static LevelPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer output) {
+    output.append(event.getLevel().toString());
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getStyleClass(Object e) {
+    if (e instanceof LoggingEvent) {
+      int lint = ((LoggingEvent) e).getLevel().toInt();
+
+      switch (lint) {
+      case TRACE_INT:
+        return "level trace";
+
+      case Level.DEBUG_INT:
+        return "level debug";
+
+      case Level.INFO_INT:
+        return "level info";
+
+      case Level.WARN_INT:
+        return "level warn";
+
+      case Level.ERROR_INT:
+        return "level error";
+
+      case Level.FATAL_INT:
+        return "level fatal";
+
+      default:
+        return "level " + ((LoggingEvent) e).getLevel().toString();
+      }
+    }
+
+    return "level";
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LineLocationPatternConverter.java b/srcjar/org/apache/log4j/pattern/LineLocationPatternConverter.java
new file mode 100644 (file)
index 0000000..0a9dfd3
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's line location information in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class LineLocationPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final LineLocationPatternConverter INSTANCE =
+    new LineLocationPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private LineLocationPatternConverter() {
+    super("Line", "line");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static LineLocationPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer output) {
+    LocationInfo locationInfo = event.getLocationInformation();
+
+    if (locationInfo != null) {
+      output.append(locationInfo.getLineNumber());
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LineSeparatorPatternConverter.java b/srcjar/org/apache/log4j/pattern/LineSeparatorPatternConverter.java
new file mode 100644 (file)
index 0000000..a859870
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats a line separator.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class LineSeparatorPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final LineSeparatorPatternConverter INSTANCE =
+    new LineSeparatorPatternConverter();
+
+  /**
+   * Line separator.
+   */
+  private final String lineSep;
+
+  /**
+   * Private constructor.
+   */
+  private LineSeparatorPatternConverter() {
+    super("Line Sep", "lineSep");
+    lineSep = Layout.LINE_SEP;
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static LineSeparatorPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append(lineSep);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final Object obj, final StringBuffer toAppendTo) {
+    toAppendTo.append(lineSep);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LiteralPatternConverter.java b/srcjar/org/apache/log4j/pattern/LiteralPatternConverter.java
new file mode 100644 (file)
index 0000000..b88b6f7
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats a string literal.
+ *
+ * @author Curt Arnold
+ *
+ */
+public final class LiteralPatternConverter extends LoggingEventPatternConverter {
+  /**
+   * String literal.
+   */
+  private final String literal;
+
+  /**
+   * Create a new instance.
+   * @param literal string literal.
+   */
+  public LiteralPatternConverter(final String literal) {
+    super("Literal", "literal");
+    this.literal = literal;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append(literal);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final Object obj, final StringBuffer toAppendTo) {
+    toAppendTo.append(literal);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LogEvent.java b/srcjar/org/apache/log4j/pattern/LogEvent.java
new file mode 100644 (file)
index 0000000..703d3a5
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.MDC;
+import org.apache.log4j.NDC;
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableInformation;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+// Contributors:   Nelson Minar <nelson@monkey.org>
+//                 Wolf Siberski
+//                 Anders Kristensen <akristensen@dynamicsoft.com>
+
+/**
+ * This class is a copy of o.a.l.spi.LoggingEvent from
+ * log4j 1.2.15 which has been renamed to keep
+ * the same overall class name length to allow a
+ * serialization written with a prior instance of o.a.l.spi.LoggingEvent
+ * to be deserialized with this class just by mangling the class name
+ * in the byte stream.
+ *
+ */
+public class LogEvent implements java.io.Serializable {
+
+  private static long startTime = System.currentTimeMillis();
+
+  /** Fully qualified name of the calling category class. */
+  transient public final String fqnOfCategoryClass;
+
+  /** 
+   * The category of the logging event. This field is not serialized
+   * for performance reasons.
+   *
+   * <p>It is set by the LoggingEvent constructor or set by a remote
+   * entity after deserialization.
+   * 
+   * @deprecated This field will be marked as private or be completely
+   * removed in future releases. Please do not use it.
+   * */
+  transient private Category logger;
+
+  /** 
+   * <p>The category (logger) name.
+   *   
+   * @deprecated This field will be marked as private in future
+   * releases. Please do not access it directly. Use the {@link
+   * #getLoggerName} method instead.
+
+   * */
+  final public String categoryName;
+
+  /** 
+   * Level of logging event. Level cannot be serializable because it
+   * is a flyweight.  Due to its special seralization it cannot be
+   * declared final either.
+   *   
+   * <p> This field should not be accessed directly. You shoud use the
+   * {@link #getLevel} method instead.
+   *
+   * @deprecated This field will be marked as private in future
+   * releases. Please do not access it directly. Use the {@link
+   * #getLevel} method instead.
+   * */
+  transient public Priority level;
+
+  /** The nested diagnostic context (NDC) of logging event. */
+  private String ndc;
+
+  /** The mapped diagnostic context (MDC) of logging event. */
+  private Hashtable mdcCopy;
+
+
+  /** Have we tried to do an NDC lookup? If we did, there is no need
+   *  to do it again.  Note that its value is always false when
+   *  serialized. Thus, a receiving SocketNode will never use it's own
+   *  (incorrect) NDC. See also writeObject method. */
+  private boolean ndcLookupRequired = true;
+
+
+  /** Have we tried to do an MDC lookup? If we did, there is no need
+   *  to do it again.  Note that its value is always false when
+   *  serialized. See also the getMDC and getMDCCopy methods.  */
+  private boolean mdcCopyLookupRequired = true;
+
+  /** The application supplied message of logging event. */
+  transient private Object message;
+
+  /** The application supplied message rendered through the log4j
+      objet rendering mechanism.*/
+  private String renderedMessage;
+
+  /** The name of thread in which this logging event was generated. */
+  private String threadName;
+
+
+  /** This
+      variable contains information about this event's throwable
+  */
+  private ThrowableInformation throwableInfo;
+
+  /** The number of milliseconds elapsed from 1/1/1970 until logging event
+      was created. */
+  public final long timeStamp;
+  /** Location information for the caller. */
+  private LocationInfo locationInfo;
+
+  // Serialization
+  static final long serialVersionUID = -868428216207166145L;
+
+  static final Integer[] PARAM_ARRAY = new Integer[1];
+  static final String TO_LEVEL = "toLevel";
+  static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class};
+  static final Hashtable methodCache = new Hashtable(3); // use a tiny table
+
+  /**
+     Instantiate a LoggingEvent from the supplied parameters.
+
+     <p>Except {@link #timeStamp} all the other fields of
+     <code>LoggingEvent</code> are filled when actually needed.
+     <p>
+     @param logger The logger generating this event.
+     @param level The level of this event.
+     @param message  The message of this event.
+     @param throwable The throwable of this event.  */
+  public LogEvent(String fqnOfCategoryClass, Category logger,
+                     Priority level, Object message, Throwable throwable) {
+    this.fqnOfCategoryClass = fqnOfCategoryClass;
+    this.logger = logger;
+    this.categoryName = logger.getName();
+    this.level = level;
+    this.message = message;
+    if(throwable != null) {
+      this.throwableInfo = new ThrowableInformation(throwable);
+    }
+    timeStamp = System.currentTimeMillis();
+  }
+
+  /**
+     Instantiate a LoggingEvent from the supplied parameters.
+
+     <p>Except {@link #timeStamp} all the other fields of
+     <code>LoggingEvent</code> are filled when actually needed.
+     <p>
+     @param logger The logger generating this event.
+     @param timeStamp the timestamp of this logging event
+     @param level The level of this event.
+     @param message  The message of this event.
+     @param throwable The throwable of this event.  */
+  public LogEvent(String fqnOfCategoryClass, Category logger,
+                     long timeStamp, Priority level, Object message,
+                     Throwable throwable) {
+    this.fqnOfCategoryClass = fqnOfCategoryClass;
+    this.logger = logger;
+    this.categoryName = logger.getName();
+    this.level = level;
+    this.message = message;
+    if(throwable != null) {
+      this.throwableInfo = new ThrowableInformation(throwable);
+    }
+
+    this.timeStamp = timeStamp;
+  }
+
+    /**
+       Create new instance.
+       @since 1.2.15
+       @param fqnOfCategoryClass Fully qualified class name
+                 of Logger implementation.
+       @param logger The logger generating this event.
+       @param timeStamp the timestamp of this logging event
+       @param level The level of this event.
+       @param message  The message of this event.
+       @param threadName thread name
+       @param throwable The throwable of this event.
+       @param ndc Nested diagnostic context
+       @param info Location info
+       @param properties MDC properties
+     */
+    public LogEvent(final String fqnOfCategoryClass,
+                        final Logger logger,
+                        final long timeStamp,
+                        final Level level,
+                        final Object message,
+                        final String threadName,
+                        final ThrowableInformation throwable,
+                        final String ndc,
+                        final LocationInfo info,
+                        final java.util.Map properties) {
+      super();
+      this.fqnOfCategoryClass = fqnOfCategoryClass;
+      this.logger = logger;
+      if (logger != null) {
+          categoryName = logger.getName();
+      } else {
+          categoryName = null;
+      }
+      this.level = level;
+      this.message = message;
+      if(throwable != null) {
+        this.throwableInfo = throwable;
+      }
+
+      this.timeStamp = timeStamp;
+      this.threadName = threadName;
+      ndcLookupRequired = false;
+      this.ndc = ndc;
+      this.locationInfo = info;
+      mdcCopyLookupRequired = false;
+      if (properties != null) {
+        mdcCopy = new java.util.Hashtable(properties);
+      }
+    }
+
+  /**
+     Set the location information for this logging event. The collected
+     information is cached for future use.
+   */
+  public LocationInfo getLocationInformation() {
+    if(locationInfo == null) {
+      locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass);
+    }
+    return locationInfo;
+  }
+
+  /**
+   * Return the level of this event. Use this form instead of directly
+   * accessing the <code>level</code> field.  */
+  public Level getLevel() {
+    return (Level) level;
+  }
+
+  /**
+   * Return the name of the logger. Use this form instead of directly
+   * accessing the <code>categoryName</code> field.  
+   */
+  public String getLoggerName() {
+    return categoryName;
+  }
+
+  /**
+     Return the message for this logging event.
+
+     <p>Before serialization, the returned object is the message
+     passed by the user to generate the logging event. After
+     serialization, the returned value equals the String form of the
+     message possibly after object rendering.
+
+     @since 1.1 */
+  public
+  Object getMessage() {
+    if(message != null) {
+      return message;
+    } else {
+      return getRenderedMessage();
+    }
+  }
+
+  /**
+   * This method returns the NDC for this event. It will return the
+   * correct content even if the event was generated in a different
+   * thread or even on a different machine. The {@link NDC#get} method
+   * should <em>never</em> be called directly.  */
+  public
+  String getNDC() {
+    if(ndcLookupRequired) {
+      ndcLookupRequired = false;
+      ndc = NDC.get();
+    }
+    return ndc;
+  }
+
+
+  /**
+      Returns the the context corresponding to the <code>key</code>
+      parameter. If there is a local MDC copy, possibly because we are
+      in a logging server or running inside AsyncAppender, then we
+      search for the key in MDC copy, if a value is found it is
+      returned. Otherwise, if the search in MDC copy returns a null
+      result, then the current thread's <code>MDC</code> is used.
+      
+      <p>Note that <em>both</em> the local MDC copy and the current
+      thread's MDC are searched.
+
+  */
+  public
+  Object getMDC(String key) {
+    Object r;
+    // Note the mdcCopy is used if it exists. Otherwise we use the MDC
+    // that is associated with the thread.
+    if(mdcCopy != null) {
+      r = mdcCopy.get(key);
+      if(r != null) {
+        return r;
+      }
+    }
+    return MDC.get(key);
+  }
+
+  /**
+     Obtain a copy of this thread's MDC prior to serialization or
+     asynchronous logging.  
+  */
+  public
+  void getMDCCopy() {
+    if(mdcCopyLookupRequired) {
+      mdcCopyLookupRequired = false;
+      // the clone call is required for asynchronous logging.
+      // See also bug #5932.
+      Hashtable t = MDC.getContext();
+      if(t != null) {
+       mdcCopy = (Hashtable) t.clone();
+      }
+    }
+  }
+
+  public
+  String getRenderedMessage() {
+     if(renderedMessage == null && message != null) {
+       if(message instanceof String) {
+        renderedMessage = (String) message;
+    } else {
+        LoggerRepository repository = logger.getLoggerRepository();
+
+        if(repository instanceof RendererSupport) {
+          RendererSupport rs = (RendererSupport) repository;
+          renderedMessage= rs.getRendererMap().findAndRender(message);
+        } else {
+          renderedMessage = message.toString();
+        }
+       }
+     }
+     return renderedMessage;
+  }
+
+  /**
+     Returns the time when the application started, in milliseconds
+     elapsed since 01.01.1970.  */
+  public static long getStartTime() {
+    return startTime;
+  }
+
+  public
+  String getThreadName() {
+    if(threadName == null) {
+        threadName = (Thread.currentThread()).getName();
+    }
+    return threadName;
+  }
+
+  /**
+     Returns the throwable information contained within this
+     event. May be <code>null</code> if there is no such information.
+
+     <p>Note that the {@link Throwable} object contained within a
+     {@link ThrowableInformation} does not survive serialization.
+
+     @since 1.1 */
+  public
+  ThrowableInformation getThrowableInformation() {
+    return throwableInfo;
+  }
+
+  /**
+     Return this event's throwable's string[] representaion.
+  */
+  public
+  String[] getThrowableStrRep() {
+
+    if(throwableInfo ==  null) {
+        return null;
+    } else {
+        return throwableInfo.getThrowableStrRep();
+    }
+  }
+
+
+  private
+  void readLevel(ObjectInputStream ois)
+                      throws java.io.IOException, ClassNotFoundException {
+
+    int p = ois.readInt();
+    try {
+      String className = (String) ois.readObject();
+      if(className == null) {
+       level = Level.toLevel(p);
+      } else {
+       Method m = (Method) methodCache.get(className);
+       if(m == null) {
+         Class clazz = Loader.loadClass(className);
+         // Note that we use Class.getDeclaredMethod instead of
+         // Class.getMethod. This assumes that the Level subclass
+         // implements the toLevel(int) method which is a
+         // requirement. Actually, it does not make sense for Level
+         // subclasses NOT to implement this method. Also note that
+         // only Level can be subclassed and not Priority.
+         m = clazz.getDeclaredMethod(TO_LEVEL, TO_LEVEL_PARAMS);
+         methodCache.put(className, m);
+       }
+       PARAM_ARRAY[0] = new Integer(p);
+       level = (Level) m.invoke(null,  PARAM_ARRAY);
+      }
+    } catch(Exception e) {
+       LogLog.warn("Level deserialization failed, reverting to default.", e);
+       level = Level.toLevel(p);
+    }
+  }
+
+  private void readObject(ObjectInputStream ois)
+                        throws java.io.IOException, ClassNotFoundException {
+    ois.defaultReadObject();
+    readLevel(ois);
+
+    // Make sure that no location info is available to Layouts
+    if(locationInfo == null) {
+        locationInfo = new LocationInfo(null, null);
+    }
+  }
+
+  private
+  void writeObject(ObjectOutputStream oos) throws java.io.IOException {
+    // Aside from returning the current thread name the wgetThreadName
+    // method sets the threadName variable.
+    this.getThreadName();
+
+    // This sets the renders the message in case it wasn't up to now.
+    this.getRenderedMessage();
+
+    // This call has a side effect of setting this.ndc and
+    // setting ndcLookupRequired to false if not already false.
+    this.getNDC();
+
+    // This call has a side effect of setting this.mdcCopy and
+    // setting mdcLookupRequired to false if not already false.
+    this.getMDCCopy();
+
+    // This sets the throwable sting representation of the event throwable.
+    this.getThrowableStrRep();
+
+    oos.defaultWriteObject();
+
+    // serialize this event's level
+    writeLevel(oos);
+  }
+
+  private
+  void writeLevel(ObjectOutputStream oos) throws java.io.IOException {
+
+    oos.writeInt(level.toInt());
+
+    Class clazz = level.getClass();
+    if(clazz == Level.class) {
+      oos.writeObject(null);
+    } else {
+      // writing directly the Class object would be nicer, except that
+      // serialized a Class object can not be read back by JDK
+      // 1.1.x. We have to resort to this hack instead.
+      oos.writeObject(clazz.getName());
+    }
+  }
+
+    /**
+     * Set value for MDC property.
+     * This adds the specified MDC property to the event.
+     * Access to the MDC is not synchronized, so this
+     * method should only be called when it is known that
+     * no other threads are accessing the MDC.
+     * @since 1.2.15
+     * @param propName
+     * @param propValue
+     */
+  public final void setProperty(final String propName,
+                          final String propValue) {
+        if (mdcCopy == null) {
+            getMDCCopy();
+        }
+        if (mdcCopy == null) {
+            mdcCopy = new Hashtable();
+        }
+        mdcCopy.put(propName, propValue);      
+  }
+
+    /**
+     * Return a property for this event. The return value can be null.
+     *
+     * Equivalent to getMDC(String) in log4j 1.2.  Provided
+     * for compatibility with log4j 1.3.
+     *
+     * @param key property name
+     * @return property value or null if property not set
+     * @since 1.2.15
+     */
+    public final String getProperty(final String key) {
+        Object value = getMDC(key);
+        String retval = null;
+        if (value != null) {
+            retval = value.toString();
+        }
+        return retval;
+    }
+
+    /**
+     * Check for the existence of location information without creating it
+     * (a byproduct of calling getLocationInformation).
+     * @return true if location information has been extracted.
+     * @since 1.2.15
+     */
+    public final boolean locationInformationExists() {
+      return (locationInfo != null);
+    }
+
+    /**
+     * Getter for the event's time stamp. The time stamp is calculated starting
+     * from 1970-01-01 GMT.
+     * @return timestamp
+     *
+     * @since 1.2.15
+     */
+    public final long getTimeStamp() {
+      return timeStamp;
+    }
+
+    /**
+     * Returns the set of the key values in the properties
+     * for the event.
+     *
+     * The returned set is unmodifiable by the caller.
+     *
+     * Provided for compatibility with log4j 1.3
+     *
+     * @return Set an unmodifiable set of the property keys.
+     * @since 1.2.15
+     */
+    public Set getPropertyKeySet() {
+      return getProperties().keySet();
+    }
+
+    /**
+     * Returns the set of properties
+     * for the event.
+     *
+     * The returned set is unmodifiable by the caller.
+     *
+     * Provided for compatibility with log4j 1.3
+     *
+     * @return Set an unmodifiable map of the properties.
+     * @since 1.2.15
+     */
+    public Map getProperties() {
+      getMDCCopy();
+      Map properties;
+      if (mdcCopy == null) {
+         properties = new HashMap();
+      } else {
+         properties = mdcCopy;
+      }
+      return Collections.unmodifiableMap(properties);
+    }
+
+    /**
+     * Get the fully qualified name of the calling logger sub-class/wrapper.
+     * Provided for compatibility with log4j 1.3
+     * @return fully qualified class name, may be null.
+     * @since 1.2.15
+     */
+    public String getFQNOfLoggerClass() {
+      return fqnOfCategoryClass;
+    }
+
+
+
+}
diff --git a/srcjar/org/apache/log4j/pattern/LoggerPatternConverter.java b/srcjar/org/apache/log4j/pattern/LoggerPatternConverter.java
new file mode 100644 (file)
index 0000000..7052a4e
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats a logger name.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ *
+ */
+public final class LoggerPatternConverter extends NamePatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final LoggerPatternConverter INSTANCE =
+    new LoggerPatternConverter(null);
+
+  /**
+   * Private constructor.
+   * @param options options, may be null.
+   */
+  private LoggerPatternConverter(final String[] options) {
+    super("Logger", "logger", options);
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static LoggerPatternConverter newInstance(
+    final String[] options) {
+    if ((options == null) || (options.length == 0)) {
+      return INSTANCE;
+    }
+
+    return new LoggerPatternConverter(options);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    final int initialLength = toAppendTo.length();
+    toAppendTo.append(event.getLoggerName());
+    abbreviate(initialLength, toAppendTo);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/LoggingEventPatternConverter.java b/srcjar/org/apache/log4j/pattern/LoggingEventPatternConverter.java
new file mode 100644 (file)
index 0000000..d0fd4f4
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * LoggingEventPatternConverter is a base class for pattern converters
+ * that can format information from instances of LoggingEvent.
+ *
+ * @author Curt Arnold
+ *
+ */
+public abstract class LoggingEventPatternConverter extends PatternConverter {
+  /**
+   * Constructs an instance of LoggingEventPatternConverter.
+   * @param name name of converter.
+   * @param style CSS style for output.
+   */
+  protected LoggingEventPatternConverter(
+    final String name, final String style) {
+    super(name, style);
+  }
+
+  /**
+   * Formats an event into a string buffer.
+   * @param event event to format, may not be null.
+   * @param toAppendTo string buffer to which the formatted event will be appended.  May not be null.
+   */
+  public abstract void format(
+    final LoggingEvent event, final StringBuffer toAppendTo);
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final Object obj, final StringBuffer output) {
+    if (obj instanceof LoggingEvent) {
+      format((LoggingEvent) obj, output);
+    }
+  }
+
+  /**
+   * Normally pattern converters are not meant to handle Exceptions although
+   * few pattern converters might.
+   *
+   * By examining the return values for this method, the containing layout will
+   * determine whether it handles throwables or not.
+
+   * @return true if this PatternConverter handles throwables
+   */
+  public boolean handlesThrowable() {
+    return false;
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/MessagePatternConverter.java b/srcjar/org/apache/log4j/pattern/MessagePatternConverter.java
new file mode 100644 (file)
index 0000000..c29f64a
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's rendered message in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class MessagePatternConverter extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final MessagePatternConverter INSTANCE =
+    new MessagePatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private MessagePatternConverter() {
+    super("Message", "message");
+  }
+
+  /**
+   * Obtains an instance of pattern converter.
+   * @param options options, may be null.
+   * @return instance of pattern converter.
+   */
+  public static MessagePatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append(event.getRenderedMessage());
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/MethodLocationPatternConverter.java b/srcjar/org/apache/log4j/pattern/MethodLocationPatternConverter.java
new file mode 100644 (file)
index 0000000..4d1b533
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's line location information in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class MethodLocationPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final MethodLocationPatternConverter INSTANCE =
+    new MethodLocationPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private MethodLocationPatternConverter() {
+    super("Method", "method");
+  }
+
+  /**
+   * Obtains an instance of MethodLocationPatternConverter.
+   * @param options options, may be null.
+   * @return instance of MethodLocationPatternConverter.
+   */
+  public static MethodLocationPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    LocationInfo locationInfo = event.getLocationInformation();
+
+    if (locationInfo != null) {
+      toAppendTo.append(locationInfo.getMethodName());
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/NDCPatternConverter.java b/srcjar/org/apache/log4j/pattern/NDCPatternConverter.java
new file mode 100644 (file)
index 0000000..9788cb1
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the event's NDC in a StringBuffer.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class NDCPatternConverter extends LoggingEventPatternConverter {
+  /**
+   *   Singleton.
+   */
+  private static final NDCPatternConverter INSTANCE =
+    new NDCPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private NDCPatternConverter() {
+    super("NDC", "ndc");
+  }
+
+  /**
+   * Obtains an instance of NDCPatternConverter.
+   * @param options options, may be null.
+   * @return instance of NDCPatternConverter.
+   */
+  public static NDCPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append(event.getNDC());
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/NameAbbreviator.java b/srcjar/org/apache/log4j/pattern/NameAbbreviator.java
new file mode 100644 (file)
index 0000000..b7e89a0
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * NameAbbreviator generates abbreviated logger and class names.
+ *
+ */
+public abstract class NameAbbreviator {
+  /**
+   * Default (no abbreviation) abbreviator.
+   */
+  private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
+
+  /**
+   * Gets an abbreviator.
+   *
+   * For example, "%logger{2}" will output only 2 elements of the logger name,
+   * %logger{-2} will drop 2 elements from the logger name,
+   * "%logger{1.}" will output only the first character of the non-final elements in the name,
+   * "%logger{1~.2~} will output the first character of the first element, two characters of
+   * the second and subsequent elements and will use a tilde to indicate abbreviated characters.
+   *
+   * @param pattern abbreviation pattern.
+   * @return abbreviator, will not be null.
+   */
+  public static NameAbbreviator getAbbreviator(final String pattern) {
+    if (pattern.length() > 0) {
+      //  if pattern is just spaces and numbers then
+      //     use MaxElementAbbreviator
+      String trimmed = pattern.trim();
+
+      if (trimmed.length() == 0) {
+        return DEFAULT;
+      }
+
+      int i = 0;
+      if (trimmed.length() > 0) {
+          if (trimmed.charAt(0) == '-') {
+              i++;
+          }
+          for (;
+                (i < trimmed.length()) &&
+                  (trimmed.charAt(i) >= '0') &&
+                  (trimmed.charAt(i) <= '9');
+               i++) {
+          }
+      }
+
+
+      //
+      //  if all blanks and digits
+      //
+      if (i == trimmed.length()) {
+        int elements = Integer.parseInt(trimmed);
+        if (elements >= 0) {
+            return new MaxElementAbbreviator(elements);
+        } else {
+            return new DropElementAbbreviator(-elements);
+        }
+      }
+
+      ArrayList fragments = new ArrayList(5);
+      char ellipsis;
+      int charCount;
+      int pos = 0;
+
+      while ((pos < trimmed.length()) && (pos >= 0)) {
+        int ellipsisPos = pos;
+
+        if (trimmed.charAt(pos) == '*') {
+          charCount = Integer.MAX_VALUE;
+          ellipsisPos++;
+        } else {
+          if ((trimmed.charAt(pos) >= '0') && (trimmed.charAt(pos) <= '9')) {
+            charCount = trimmed.charAt(pos) - '0';
+            ellipsisPos++;
+          } else {
+            charCount = 0;
+          }
+        }
+
+        ellipsis = '\0';
+
+        if (ellipsisPos < trimmed.length()) {
+          ellipsis = trimmed.charAt(ellipsisPos);
+
+          if (ellipsis == '.') {
+            ellipsis = '\0';
+          }
+        }
+
+        fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
+        pos = trimmed.indexOf(".", pos);
+
+        if (pos == -1) {
+          break;
+        }
+
+        pos++;
+      }
+
+      return new PatternAbbreviator(fragments);
+    }
+
+    //
+    //  no matching abbreviation, return defaultAbbreviator
+    //
+    return DEFAULT;
+  }
+
+  /**
+   * Gets default abbreviator.
+   *
+   * @return default abbreviator.
+   */
+  public static NameAbbreviator getDefaultAbbreviator() {
+    return DEFAULT;
+  }
+
+  /**
+   * Abbreviates a name in a StringBuffer.
+   *
+   * @param nameStart starting position of name in buf.
+   * @param buf buffer, may not be null.
+   */
+  public abstract void abbreviate(final int nameStart, final StringBuffer buf);
+
+  /**
+   * Abbreviator that simply appends full name to buffer.
+   */
+  private static class NOPAbbreviator extends NameAbbreviator {
+    /**
+     * Constructor.
+     */
+    public NOPAbbreviator() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void abbreviate(final int nameStart, final StringBuffer buf) {
+    }
+  }
+
+  /**
+   * Abbreviator that drops starting path elements.
+   */
+  private static class MaxElementAbbreviator extends NameAbbreviator {
+    /**
+     * Maximum number of path elements to output.
+     */
+    private final int count;
+
+    /**
+     * Create new instance.
+     * @param count maximum number of path elements to output.
+     */
+    public MaxElementAbbreviator(final int count) {
+      this.count = count;
+    }
+
+    /**
+     * Abbreviate name.
+     * @param buf buffer to append abbreviation.
+     * @param nameStart start of name to abbreviate.
+     */
+    public void abbreviate(final int nameStart, final StringBuffer buf) {
+      // We substract 1 from 'len' when assigning to 'end' to avoid out of
+      // bounds exception in return r.substring(end+1, len). This can happen if
+      // precision is 1 and the category name ends with a dot.
+      int end = buf.length() - 1;
+
+      String bufString = buf.toString();
+      for (int i = count; i > 0; i--) {
+        end = bufString.lastIndexOf(".", end - 1);
+
+        if ((end == -1) || (end < nameStart)) {
+          return;
+        }
+      }
+
+      buf.delete(nameStart, end + 1);
+    }
+  }
+
+  /**
+   * Abbreviator that drops starting path elements.
+   */
+  private static class DropElementAbbreviator extends NameAbbreviator {
+    /**
+     * Maximum number of path elements to output.
+     */
+    private final int count;
+
+    /**
+     * Create new instance.
+     * @param count maximum number of path elements to output.
+     */
+    public DropElementAbbreviator(final int count) {
+      this.count = count;
+    }
+
+    /**
+     * Abbreviate name.
+     * @param buf buffer to append abbreviation.
+     * @param nameStart start of name to abbreviate.
+     */
+    public void abbreviate(final int nameStart, final StringBuffer buf) {
+      int i = count;
+      for(int pos = buf.indexOf(".", nameStart);
+        pos != -1;
+        pos = buf.indexOf(".", pos + 1)) {
+          if(--i == 0) {
+              buf.delete(nameStart, pos + 1);
+              break;
+          }
+      }
+    }
+  }
+
+
+  /**
+   * Fragment of an pattern abbreviator.
+   *
+   */
+  private static class PatternAbbreviatorFragment {
+    /**
+     * Count of initial characters of element to output.
+     */
+    private final int charCount;
+
+    /**
+     *  Character used to represent dropped characters.
+     * '\0' indicates no representation of dropped characters.
+     */
+    private final char ellipsis;
+
+    /**
+     * Creates a PatternAbbreviatorFragment.
+     * @param charCount number of initial characters to preserve.
+     * @param ellipsis character to represent elimination of characters,
+     *    '\0' if no ellipsis is desired.
+     */
+    public PatternAbbreviatorFragment(
+      final int charCount, final char ellipsis) {
+      this.charCount = charCount;
+      this.ellipsis = ellipsis;
+    }
+
+    /**
+     * Abbreviate element of name.
+     * @param buf buffer to receive element.
+     * @param startPos starting index of name element.
+     * @return starting index of next element.
+     */
+    public int abbreviate(final StringBuffer buf, final int startPos) {
+      int nextDot = buf.toString().indexOf(".", startPos);
+
+      if (nextDot != -1) {
+        if ((nextDot - startPos) > charCount) {
+          buf.delete(startPos + charCount, nextDot);
+          nextDot = startPos + charCount;
+
+          if (ellipsis != '\0') {
+            buf.insert(nextDot, ellipsis);
+            nextDot++;
+          }
+        }
+
+        nextDot++;
+      }
+
+      return nextDot;
+    }
+  }
+
+  /**
+   * Pattern abbreviator.
+   *
+   *
+   */
+  private static class PatternAbbreviator extends NameAbbreviator {
+    /**
+     * Element abbreviation patterns.
+     */
+    private final PatternAbbreviatorFragment[] fragments;
+
+    /**
+     * Create PatternAbbreviator.
+     *
+     * @param fragments element abbreviation patterns.
+     */
+    public PatternAbbreviator(List fragments) {
+      if (fragments.size() == 0) {
+        throw new IllegalArgumentException(
+          "fragments must have at least one element");
+      }
+
+      this.fragments = new PatternAbbreviatorFragment[fragments.size()];
+      fragments.toArray(this.fragments);
+    }
+
+    /**
+     * Abbreviate name.
+     * @param buf buffer that abbreviated name is appended.
+     * @param nameStart start of name.
+     */
+    public void abbreviate(final int nameStart, final StringBuffer buf) {
+      //
+      //  all non-terminal patterns are executed once
+      //
+      int pos = nameStart;
+
+      for (int i = 0; (i < (fragments.length - 1)) && (pos < buf.length());
+          i++) {
+        pos = fragments[i].abbreviate(buf, pos);
+      }
+
+      //
+      //   last pattern in executed repeatedly
+      //
+      PatternAbbreviatorFragment terminalFragment =
+        fragments[fragments.length - 1];
+
+      while ((pos < buf.length()) && (pos >= 0)) {
+        pos = terminalFragment.abbreviate(buf, pos);
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/NamePatternConverter.java b/srcjar/org/apache/log4j/pattern/NamePatternConverter.java
new file mode 100644 (file)
index 0000000..fbdd999
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+
+/**
+ *
+ * Base class for other pattern converters which can return only parts of their name.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Curt Arnold
+ *
+ */
+public abstract class NamePatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Abbreviator.
+   */
+  private final NameAbbreviator abbreviator;
+
+  /**
+   * Constructor.
+   * @param name name of converter.
+   * @param style style name for associated output.
+   * @param options options, may be null, first element will be interpreted as an abbreviation pattern.
+   */
+  protected NamePatternConverter(
+    final String name, final String style, final String[] options) {
+    super(name, style);
+
+    if ((options != null) && (options.length > 0)) {
+      abbreviator = NameAbbreviator.getAbbreviator(options[0]);
+    } else {
+      abbreviator = NameAbbreviator.getDefaultAbbreviator();
+    }
+  }
+
+  /**
+   * Abbreviate name in string buffer.
+   * @param nameStart starting position of name to abbreviate.
+   * @param buf string buffer containing name.
+   */
+  protected final void abbreviate(final int nameStart, final StringBuffer buf) {
+    abbreviator.abbreviate(nameStart, buf);
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/PatternConverter.java b/srcjar/org/apache/log4j/pattern/PatternConverter.java
new file mode 100644 (file)
index 0000000..21fb7cd
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+
+/**
+
+   <p>PatternConverter is an abstract class that provides the
+   formatting functionality that derived classes need.
+
+   <p>Conversion specifiers in a conversion patterns are parsed to
+   individual PatternConverters. Each of which is responsible for
+   converting an object in a converter specific manner.
+
+   @author <a href="mailto:cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+   @author Chris Nokes
+   @author Curt Arnold
+
+ */
+public abstract class PatternConverter {
+  /**
+   * Converter name.
+   */
+  private final String name;
+
+  /**
+   * Converter style name.
+   */
+  private final String style;
+
+  /**
+   * Create a new pattern converter.
+   * @param name name for pattern converter.
+   * @param style CSS style for formatted output.
+   */
+  protected PatternConverter(final String name, final String style) {
+    this.name = name;
+    this.style = style;
+  }
+
+  /**
+   * Formats an object into a string buffer.
+   * @param obj event to format, may not be null.
+   * @param toAppendTo string buffer to which the formatted event will be appended.  May not be null.
+   */
+  public abstract void format(final Object obj, final StringBuffer toAppendTo);
+
+  /**
+   * This method returns the name of the conversion pattern.
+   *
+   * The name can be useful to certain Layouts such as HTMLLayout.
+   *
+   * @return        the name of the conversion pattern
+   */
+  public final String getName() {
+    return name;
+  }
+
+  /**
+   * This method returns the CSS style class that should be applied to
+   * the LoggingEvent passed as parameter, which can be null.
+   *
+   * This information is currently used only by HTMLLayout.
+   *
+   * @param e null values are accepted
+   * @return  the name of the conversion pattern
+   */
+  public String getStyleClass(Object e) {
+    return style;
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/PatternParser.java b/srcjar/org/apache/log4j/pattern/PatternParser.java
new file mode 100644 (file)
index 0000000..901b0fe
--- /dev/null
@@ -0,0 +1,701 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.LogLog;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// Contributors:   Nelson Minar <(nelson@monkey.org>
+//                 Igor E. Poteryaev <jah@mail.ru>
+//                 Reinhard Deschler <reinhard.deschler@web.de>
+
+/**
+ * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class
+ * is delegated to the PatternParser class.
+ * <p>It is this class that parses conversion patterns and creates
+ * a chained list of {@link PatternConverter PatternConverters}.
+ *
+ * @author James P. Cakalic
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Anders Kristensen
+ * @author Paul Smith
+ * @author Curt Arnold
+ *
+*/
+public final class PatternParser {
+  /**
+   * Escape character for format specifier.
+   */
+  private static final char ESCAPE_CHAR = '%';
+
+  /**
+   * Literal state.
+   */
+  private static final int LITERAL_STATE = 0;
+
+  /**
+   * In converter name state.
+   */
+  private static final int CONVERTER_STATE = 1;
+
+  /**
+   * Dot state.
+   */
+  private static final int DOT_STATE = 3;
+
+  /**
+   * Min state.
+   */
+  private static final int MIN_STATE = 4;
+
+  /**
+   * Max state.
+   */
+  private static final int MAX_STATE = 5;
+
+  /**
+   * Standard format specifiers for EnhancedPatternLayout.
+   */
+  private static final Map PATTERN_LAYOUT_RULES;
+
+  /**
+   * Standard format specifiers for rolling file appenders.
+   */
+  private static final Map FILENAME_PATTERN_RULES;
+
+  static {
+    // We set the global rules in the static initializer of PatternParser class
+    Map rules = new HashMap(17);
+    rules.put("c", LoggerPatternConverter.class);
+    rules.put("logger", LoggerPatternConverter.class);
+
+    rules.put("C", ClassNamePatternConverter.class);
+    rules.put("class", ClassNamePatternConverter.class);
+
+    rules.put("d", DatePatternConverter.class);
+    rules.put("date", DatePatternConverter.class);
+
+    rules.put("F", FileLocationPatternConverter.class);
+    rules.put("file", FileLocationPatternConverter.class);
+
+    rules.put("l", FullLocationPatternConverter.class);
+
+    rules.put("L", LineLocationPatternConverter.class);
+    rules.put("line", LineLocationPatternConverter.class);
+
+    rules.put("m", MessagePatternConverter.class);
+    rules.put("message", MessagePatternConverter.class);
+
+    rules.put("n", LineSeparatorPatternConverter.class);
+
+    rules.put("M", MethodLocationPatternConverter.class);
+    rules.put("method", MethodLocationPatternConverter.class);
+
+    rules.put("p", LevelPatternConverter.class);
+    rules.put("level", LevelPatternConverter.class);
+
+    rules.put("r", RelativeTimePatternConverter.class);
+    rules.put("relative", RelativeTimePatternConverter.class);
+
+    rules.put("t", ThreadPatternConverter.class);
+    rules.put("thread", ThreadPatternConverter.class);
+
+    rules.put("x", NDCPatternConverter.class);
+    rules.put("ndc", NDCPatternConverter.class);
+
+    rules.put("X", PropertiesPatternConverter.class);
+    rules.put("properties", PropertiesPatternConverter.class);
+
+    rules.put("sn", SequenceNumberPatternConverter.class);
+    rules.put("sequenceNumber", SequenceNumberPatternConverter.class);
+
+    rules.put("throwable", ThrowableInformationPatternConverter.class);
+    PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);
+
+    Map fnameRules = new HashMap(4);
+    fnameRules.put("d", FileDatePatternConverter.class);
+    fnameRules.put("date", FileDatePatternConverter.class);
+    fnameRules.put("i", IntegerPatternConverter.class);
+    fnameRules.put("index", IntegerPatternConverter.class);
+
+    FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
+  }
+
+  /**
+   * Private constructor.
+   */
+  private PatternParser() {
+  }
+
+  /**
+   * Get standard format specifiers for EnhancedPatternLayout.
+   * @return read-only map of format converter classes keyed by format specifier strings.
+   */
+  public static Map getPatternLayoutRules() {
+    return PATTERN_LAYOUT_RULES;
+  }
+
+  /**
+   * Get standard format specifiers for rolling file appender file specification.
+   * @return read-only map of format converter classes keyed by format specifier strings.
+   */
+  public static Map getFileNamePatternRules() {
+    return FILENAME_PATTERN_RULES;
+  }
+
+  /** Extract the converter identifier found at position i.
+   *
+   * After this function returns, the variable i will point to the
+   * first char after the end of the converter identifier.
+   *
+   * If i points to a char which is not a character acceptable at the
+   * start of a unicode identifier, the value null is returned.
+   *
+   * @param lastChar last processed character.
+   * @param pattern format string.
+   * @param i current index into pattern format.
+   * @param convBuf buffer to receive conversion specifier.
+   * @param currentLiteral literal to be output in case format specifier in unrecognized.
+   * @return position in pattern after converter.
+   */
+  private static int extractConverter(
+    char lastChar, final String pattern, int i, final StringBuffer convBuf,
+    final StringBuffer currentLiteral) {
+    convBuf.setLength(0);
+
+    // When this method is called, lastChar points to the first character of the
+    // conversion word. For example:
+    // For "%hello"     lastChar = 'h'
+    // For "%-5hello"   lastChar = 'h'
+    //System.out.println("lastchar is "+lastChar);
+    if (!Character.isUnicodeIdentifierStart(lastChar)) {
+      return i;
+    }
+
+    convBuf.append(lastChar);
+
+    while (
+      (i < pattern.length())
+        && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
+      convBuf.append(pattern.charAt(i));
+      currentLiteral.append(pattern.charAt(i));
+
+      //System.out.println("conv buffer is now ["+convBuf+"].");
+      i++;
+    }
+
+    return i;
+  }
+
+  /**
+   * Extract options.
+   * @param pattern conversion pattern.
+   * @param i start of options.
+   * @param options array to receive extracted options
+   * @return position in pattern after options.
+   */
+  private static int extractOptions(String pattern, int i, List options) {
+    while ((i < pattern.length()) && (pattern.charAt(i) == '{')) {
+      int end = pattern.indexOf('}', i);
+
+      if (end == -1) {
+        break;
+      }
+
+      String r = pattern.substring(i + 1, end);
+      options.add(r);
+      i = end + 1;
+    }
+
+    return i;
+  }
+
+  /**
+   * Parse a format specifier.
+   * @param pattern pattern to parse.
+   * @param patternConverters list to receive pattern converters.
+   * @param formattingInfos list to receive field specifiers corresponding to pattern converters.
+   * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
+   * @param rules map of stock pattern converters keyed by format specifier.
+   */
+  public static void parse(
+    final String pattern, final List patternConverters,
+    final List formattingInfos, final Map converterRegistry, final Map rules) {
+    if (pattern == null) {
+      throw new NullPointerException("pattern");
+    }
+
+    StringBuffer currentLiteral = new StringBuffer(32);
+
+    int patternLength = pattern.length();
+    int state = LITERAL_STATE;
+    char c;
+    int i = 0;
+    FormattingInfo formattingInfo = FormattingInfo.getDefault();
+
+    while (i < patternLength) {
+      c = pattern.charAt(i++);
+
+      switch (state) {
+      case LITERAL_STATE:
+
+        // In literal state, the last char is always a literal.
+        if (i == patternLength) {
+          currentLiteral.append(c);
+
+          continue;
+        }
+
+        if (c == ESCAPE_CHAR) {
+          // peek at the next char.
+          switch (pattern.charAt(i)) {
+          case ESCAPE_CHAR:
+            currentLiteral.append(c);
+            i++; // move pointer
+
+            break;
+
+          default:
+
+            if (currentLiteral.length() != 0) {
+              patternConverters.add(
+                new LiteralPatternConverter(currentLiteral.toString()));
+              formattingInfos.add(FormattingInfo.getDefault());
+            }
+
+            currentLiteral.setLength(0);
+            currentLiteral.append(c); // append %
+            state = CONVERTER_STATE;
+            formattingInfo = FormattingInfo.getDefault();
+          }
+        } else {
+          currentLiteral.append(c);
+        }
+
+        break;
+
+      case CONVERTER_STATE:
+        currentLiteral.append(c);
+
+        switch (c) {
+        case '-':
+          formattingInfo =
+            new FormattingInfo(
+              true, 
+              formattingInfo.isRightTruncated(),
+              formattingInfo.getMinLength(),
+              formattingInfo.getMaxLength());
+          break;
+
+        case '!':
+          formattingInfo =
+            new FormattingInfo(
+              formattingInfo.isLeftAligned(), 
+              true,
+              formattingInfo.getMinLength(),
+              formattingInfo.getMaxLength());
+          break;
+
+
+        case '.':
+          state = DOT_STATE;
+
+          break;
+
+        default:
+
+          if ((c >= '0') && (c <= '9')) {
+            formattingInfo =
+              new FormattingInfo(
+                formattingInfo.isLeftAligned(), 
+                formattingInfo.isRightTruncated(),
+                c - '0',
+                formattingInfo.getMaxLength());
+            state = MIN_STATE;
+          } else {
+            i = finalizeConverter(
+                c, pattern, i, currentLiteral, formattingInfo,
+                converterRegistry, rules, patternConverters, formattingInfos);
+
+            // Next pattern is assumed to be a literal.
+            state = LITERAL_STATE;
+            formattingInfo = FormattingInfo.getDefault();
+            currentLiteral.setLength(0);
+          }
+        } // switch
+
+        break;
+
+      case MIN_STATE:
+        currentLiteral.append(c);
+
+        if ((c >= '0') && (c <= '9')) {
+          formattingInfo =
+            new FormattingInfo(
+              formattingInfo.isLeftAligned(),
+              formattingInfo.isRightTruncated(),
+              (formattingInfo.getMinLength() * 10) + (c - '0'),
+              formattingInfo.getMaxLength());
+        } else if (c == '.') {
+          state = DOT_STATE;
+        } else {
+          i = finalizeConverter(
+              c, pattern, i, currentLiteral, formattingInfo,
+              converterRegistry, rules, patternConverters, formattingInfos);
+          state = LITERAL_STATE;
+          formattingInfo = FormattingInfo.getDefault();
+          currentLiteral.setLength(0);
+        }
+
+        break;
+
+      case DOT_STATE:
+        currentLiteral.append(c);
+
+        if ((c >= '0') && (c <= '9')) {
+          formattingInfo =
+            new FormattingInfo(
+              formattingInfo.isLeftAligned(), 
+              formattingInfo.isRightTruncated(),
+              formattingInfo.getMinLength(),
+              c - '0');
+          state = MAX_STATE;
+        } else {
+            LogLog.error(
+              "Error occured in position " + i
+              + ".\n Was expecting digit, instead got char \"" + c + "\".");
+
+          state = LITERAL_STATE;
+        }
+
+        break;
+
+      case MAX_STATE:
+        currentLiteral.append(c);
+
+        if ((c >= '0') && (c <= '9')) {
+          formattingInfo =
+            new FormattingInfo(
+              formattingInfo.isLeftAligned(), 
+              formattingInfo.isRightTruncated(),
+              formattingInfo.getMinLength(),
+              (formattingInfo.getMaxLength() * 10) + (c - '0'));
+        } else {
+          i = finalizeConverter(
+              c, pattern, i, currentLiteral, formattingInfo,
+              converterRegistry, rules, patternConverters, formattingInfos);
+          state = LITERAL_STATE;
+          formattingInfo = FormattingInfo.getDefault();
+          currentLiteral.setLength(0);
+        }
+
+        break;
+      } // switch
+    }
+
+    // while
+    if (currentLiteral.length() != 0) {
+      patternConverters.add(
+        new LiteralPatternConverter(currentLiteral.toString()));
+      formattingInfos.add(FormattingInfo.getDefault());
+    }
+  }
+
+  /**
+   * Creates a new PatternConverter.
+   *
+   *
+   * @param converterId converterId.
+   * @param currentLiteral literal to be used if converter is unrecognized or following converter
+   *    if converterId contains extra characters.
+   * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
+   * @param rules map of stock pattern converters keyed by format specifier.
+   * @param options converter options.
+   * @return  converter or null.
+   */
+  private static PatternConverter createConverter(
+    final String converterId, final StringBuffer currentLiteral,
+    final Map converterRegistry, final Map rules, final List options) {
+    String converterName = converterId;
+    Object converterObj = null;
+
+    for (int i = converterId.length(); (i > 0) && (converterObj == null);
+        i--) {
+      converterName = converterName.substring(0, i);
+
+      if (converterRegistry != null) {
+        converterObj = converterRegistry.get(converterName);
+      }
+
+      if ((converterObj == null) && (rules != null)) {
+        converterObj = rules.get(converterName);
+      }
+    }
+
+    if (converterObj == null) {
+        LogLog.error("Unrecognized format specifier [" + converterId + "]");
+
+      return null;
+    }
+
+    Class converterClass = null;
+
+    if (converterObj instanceof Class) {
+      converterClass = (Class) converterObj;
+    } else {
+      if (converterObj instanceof String) {
+        try {
+          converterClass = Loader.loadClass((String) converterObj);
+        } catch (ClassNotFoundException ex) {
+            LogLog.warn(
+              "Class for conversion pattern %" + converterName + " not found",
+              ex);
+
+          return null;
+        }
+      } else {
+          LogLog.warn(
+            "Bad map entry for conversion pattern %" +  converterName + ".");
+
+        return null;
+      }
+    }
+
+    try {
+      Method factory =
+        converterClass.getMethod(
+          "newInstance",
+          new Class[] {
+            Class.forName("[Ljava.lang.String;")
+          });
+      String[] optionsArray = new String[options.size()];
+      optionsArray = (String[]) options.toArray(optionsArray);
+
+      Object newObj =
+        factory.invoke(null, new Object[] { optionsArray });
+
+      if (newObj instanceof PatternConverter) {
+        currentLiteral.delete(
+          0,
+          currentLiteral.length()
+          - (converterId.length() - converterName.length()));
+
+        return (PatternConverter) newObj;
+      } else {
+          LogLog.warn(
+            "Class " + converterClass.getName()
+            + " does not extend PatternConverter.");
+      }
+    } catch (Exception ex) {
+        LogLog.error("Error creating converter for " + converterId, ex);
+
+      try {
+        //
+        //  try default constructor
+        PatternConverter pc = (PatternConverter) converterClass.newInstance();
+        currentLiteral.delete(
+          0,
+          currentLiteral.length()
+          - (converterId.length() - converterName.length()));
+
+        return pc;
+      } catch (Exception ex2) {
+          LogLog.error("Error creating converter for " + converterId, ex2);
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Processes a format specifier sequence.
+   *
+   * @param c initial character of format specifier.
+   * @param pattern conversion pattern
+   * @param i current position in conversion pattern.
+   * @param currentLiteral current literal.
+   * @param formattingInfo current field specifier.
+   * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
+   * @param rules map of stock pattern converters keyed by format specifier.
+   * @param patternConverters list to receive parsed pattern converter.
+   * @param formattingInfos list to receive corresponding field specifier.
+   * @return position after format specifier sequence.
+   */
+  private static int finalizeConverter(
+    char c, String pattern, int i,
+    final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
+    final Map converterRegistry, final Map rules, final List patternConverters,
+    final List formattingInfos) {
+    StringBuffer convBuf = new StringBuffer();
+    i = extractConverter(c, pattern, i, convBuf, currentLiteral);
+
+    String converterId = convBuf.toString();
+
+    List options = new ArrayList();
+    i = extractOptions(pattern, i, options);
+
+    PatternConverter pc =
+      createConverter(
+        converterId, currentLiteral, converterRegistry, rules, options);
+
+    if (pc == null) {
+      StringBuffer msg;
+
+      if ((converterId == null) || (converterId.length() == 0)) {
+        msg =
+          new StringBuffer("Empty conversion specifier starting at position ");
+      } else {
+        msg = new StringBuffer("Unrecognized conversion specifier [");
+        msg.append(converterId);
+        msg.append("] starting at position ");
+      }
+
+      msg.append(Integer.toString(i));
+      msg.append(" in conversion pattern.");
+
+        LogLog.error(msg.toString());
+
+      patternConverters.add(
+        new LiteralPatternConverter(currentLiteral.toString()));
+      formattingInfos.add(FormattingInfo.getDefault());
+    } else {
+      patternConverters.add(pc);
+      formattingInfos.add(formattingInfo);
+
+      if (currentLiteral.length() > 0) {
+        patternConverters.add(
+          new LiteralPatternConverter(currentLiteral.toString()));
+        formattingInfos.add(FormattingInfo.getDefault());
+      }
+    }
+
+    currentLiteral.setLength(0);
+
+    return i;
+  }
+
+  /**
+   * The class wraps another Map but throws exceptions on any attempt to modify the map.
+   */
+  private static class ReadOnlyMap implements Map {
+    /**
+     * Wrapped map.
+     */
+    private final Map map;
+
+    /**
+     * Constructor
+     * @param src source map.
+     */
+    public ReadOnlyMap(Map src) {
+      map = src;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clear() {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsKey(Object key) {
+      return map.containsKey(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsValue(Object value) {
+      return map.containsValue(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set entrySet() {
+      return map.entrySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(Object key) {
+      return map.get(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty() {
+      return map.isEmpty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set keySet() {
+      return map.keySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object put(Object key, Object value) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void putAll(Map t) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(Object key) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int size() {
+      return map.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection values() {
+      return map.values();
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/PropertiesPatternConverter.java b/srcjar/org/apache/log4j/pattern/PropertiesPatternConverter.java
new file mode 100644 (file)
index 0000000..a55cf97
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Iterator;
+import java.util.Set;
+import org.apache.log4j.helpers.*;
+
+
+/**
+ * Able to handle the contents of the LoggingEvent's Property bundle and either
+ * output the entire contents of the properties in a similar format to the
+ * java.util.Hashtable.toString(), or to output the value of a specific key
+ * within the property bundle
+ * when this pattern converter has the option set.
+ *
+ * @author Paul Smith
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class PropertiesPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Name of property to output.
+   */
+  private final String option;
+
+  /**
+   * Private constructor.
+   * @param options options, may be null.
+   */
+  private PropertiesPatternConverter(
+    final String[] options) {
+    super(
+      ((options != null) && (options.length > 0))
+      ? ("Property{" + options[0] + "}") : "Properties", "property");
+
+    if ((options != null) && (options.length > 0)) {
+      option = options[0];
+    } else {
+      option = null;
+    }
+  }
+
+  /**
+   * Obtains an instance of PropertiesPatternConverter.
+   * @param options options, may be null or first element contains name of property to format.
+   * @return instance of PropertiesPatternConverter.
+   */
+  public static PropertiesPatternConverter newInstance(
+    final String[] options) {
+    return new PropertiesPatternConverter(options);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    // if there is no additional options, we output every single
+    // Key/Value pair for the MDC in a similar format to Hashtable.toString()
+    if (option == null) {
+      toAppendTo.append("{");
+
+      try {
+        Set keySet = MDCKeySetExtractor.INSTANCE.getPropertyKeySet(event);
+          if (keySet != null) {
+            for (Iterator i = keySet.iterator(); i.hasNext();) {
+                Object item = i.next();
+                Object val = event.getMDC(item.toString());
+                toAppendTo.append("{").append(item).append(",").append(val).append(
+                "}");
+            }
+          }
+      } catch(Exception ex) {
+              LogLog.error("Unexpected exception while extracting MDC keys", ex);
+      }
+
+      toAppendTo.append("}");
+    } else {
+      // otherwise they just want a single key output
+      Object val = event.getMDC(option);
+
+      if (val != null) {
+        toAppendTo.append(val);
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/RelativeTimePatternConverter.java b/srcjar/org/apache/log4j/pattern/RelativeTimePatternConverter.java
new file mode 100644 (file)
index 0000000..007a29a
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Return the relative time in milliseconds since loading of the LoggingEvent
+ * class.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class RelativeTimePatternConverter extends LoggingEventPatternConverter {
+  /**
+   * Cached formatted timestamp.
+   */
+  private CachedTimestamp lastTimestamp = new CachedTimestamp(0, "");
+
+  /**
+   * Private constructor.
+   */
+  public RelativeTimePatternConverter() {
+    super("Time", "time");
+  }
+
+  /**
+   * Obtains an instance of RelativeTimePatternConverter.
+   * @param options options, currently ignored, may be null.
+   * @return instance of RelativeTimePatternConverter.
+   */
+  public static RelativeTimePatternConverter newInstance(
+    final String[] options) {
+    return new RelativeTimePatternConverter();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    long timestamp = event.timeStamp;
+
+    if (!lastTimestamp.format(timestamp, toAppendTo)) {
+      final String formatted =
+        Long.toString(timestamp - LoggingEvent.getStartTime());
+      toAppendTo.append(formatted);
+      lastTimestamp = new CachedTimestamp(timestamp, formatted);
+    }
+  }
+
+  /**
+   * Cached timestamp and formatted value.
+   */
+  private static final class CachedTimestamp {
+    /**
+     * Cached timestamp.
+     */
+    private final long timestamp;
+
+    /**
+     * Cached formatted timestamp.
+     */
+    private final String formatted;
+
+    /**
+     * Creates a new instance.
+     * @param timestamp timestamp.
+     * @param formatted formatted timestamp.
+     */
+    public CachedTimestamp(long timestamp, final String formatted) {
+      this.timestamp = timestamp;
+      this.formatted = formatted;
+    }
+
+    /**
+     * Appends the cached formatted timestamp to the buffer if timestamps match.
+     * @param newTimestamp requested timestamp.
+     * @param toAppendTo buffer to append formatted timestamp.
+     * @return true if requested timestamp matched cached timestamp.
+     */
+    public boolean format(long newTimestamp, final StringBuffer toAppendTo) {
+      if (newTimestamp == timestamp) {
+        toAppendTo.append(formatted);
+
+        return true;
+      }
+
+      return false;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/SequenceNumberPatternConverter.java b/srcjar/org/apache/log4j/pattern/SequenceNumberPatternConverter.java
new file mode 100644 (file)
index 0000000..8012682
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats the event sequence number.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class SequenceNumberPatternConverter
+  extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final SequenceNumberPatternConverter INSTANCE =
+    new SequenceNumberPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private SequenceNumberPatternConverter() {
+    super("Sequence Number", "sn");
+  }
+
+  /**
+   * Obtains an instance of SequencePatternConverter.
+   * @param options options, currently ignored, may be null.
+   * @return instance of SequencePatternConverter.
+   */
+  public static SequenceNumberPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append("0");
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/ThreadPatternConverter.java b/srcjar/org/apache/log4j/pattern/ThreadPatternConverter.java
new file mode 100644 (file)
index 0000000..6b3e6c3
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Formats the event thread name.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class ThreadPatternConverter extends LoggingEventPatternConverter {
+  /**
+   * Singleton.
+   */
+  private static final ThreadPatternConverter INSTANCE =
+    new ThreadPatternConverter();
+
+  /**
+   * Private constructor.
+   */
+  private ThreadPatternConverter() {
+    super("Thread", "thread");
+  }
+
+  /**
+   * Obtains an instance of ThreadPatternConverter.
+   * @param options options, currently ignored, may be null.
+   * @return instance of ThreadPatternConverter.
+   */
+  public static ThreadPatternConverter newInstance(
+    final String[] options) {
+    return INSTANCE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    toAppendTo.append(event.getThreadName());
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/ThrowableInformationPatternConverter.java b/srcjar/org/apache/log4j/pattern/ThrowableInformationPatternConverter.java
new file mode 100644 (file)
index 0000000..bf9c4b4
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.pattern;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+
+/**
+ * Outputs the ThrowableInformation portion of the LoggingEvent.
+ * By default, outputs the full stack trace.  %throwable{none}
+ * or %throwable{0} suppresses the stack trace. %throwable{short}
+ * or %throwable{1} outputs just the first line.  %throwable{n}
+ * will output n lines for a positive integer or drop the last
+ * -n lines for a negative integer.
+ *
+ * @author Paul Smith
+ *
+ */
+public class ThrowableInformationPatternConverter
+  extends LoggingEventPatternConverter {
+
+  /**
+   * Maximum lines of stack trace to output.
+   */
+  private int maxLines = Integer.MAX_VALUE;
+
+  /**
+   * Private constructor.
+   * @param options options, may be null.
+   */
+  private ThrowableInformationPatternConverter(
+    final String[] options) {
+    super("Throwable", "throwable");
+
+    if ((options != null) && (options.length > 0)) {
+      if("none".equals(options[0])) {
+          maxLines = 0;
+      } else if("short".equals(options[0])) {
+          maxLines = 1;
+      } else {
+          try {
+              maxLines = Integer.parseInt(options[0]);
+          } catch(NumberFormatException ex) {
+          }
+      }
+    }
+  }
+
+  /**
+   * Gets an instance of the class.
+    * @param options pattern options, may be null.  If first element is "short",
+   * only the first line of the throwable will be formatted.
+   * @return instance of class.
+   */
+  public static ThrowableInformationPatternConverter newInstance(
+    final String[] options) {
+    return new ThrowableInformationPatternConverter(options);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
+    if (maxLines != 0) {
+      ThrowableInformation information = event.getThrowableInformation();
+
+      if (information != null) {
+        String[] stringRep = information.getThrowableStrRep();
+
+        int length = stringRep.length;
+        if (maxLines < 0) {
+            length += maxLines;
+        } else if (length > maxLines) {
+            length = maxLines;
+        }
+
+        for (int i = 0; i < length; i++) {
+            String string = stringRep[i];
+            toAppendTo.append(string).append("\n");
+        }
+      }
+    }
+  }
+
+  /**
+   * This converter obviously handles throwables.
+   * @return true.
+   */
+  public boolean handlesThrowable() {
+    return true;
+  }
+}
diff --git a/srcjar/org/apache/log4j/pattern/package.html b/srcjar/org/apache/log4j/pattern/package.html
new file mode 100644 (file)
index 0000000..1db8283
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>Provides classes implementing format specifiers in conversion patterns.</p>
+
+<hr>
+</body> </html>
diff --git a/srcjar/org/apache/log4j/rewrite/MapRewritePolicy.java b/srcjar/org/apache/log4j/rewrite/MapRewritePolicy.java
new file mode 100644 (file)
index 0000000..4fca465
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This policy rewrites events where the message of the
+ * original event implementes java.util.Map.
+ * All other events are passed through unmodified.
+ * If the map contains a "message" entry, the value will be
+ * used as the message for the rewritten event.  The rewritten
+ * event will have a property set that is the combination of the
+ * original property set and the other members of the message map.
+ * If both the original property set and the message map
+ * contain the same entry, the value from the message map
+ * will overwrite the original property set.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the MapFilter from log4j 1.3. 
+ */
+public class MapRewritePolicy implements RewritePolicy {
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        Object msg = source.getMessage();
+        if (msg instanceof Map) {
+            Map props = new HashMap(source.getProperties());
+            Map eventProps = (Map) msg;
+            //
+            //   if the map sent in the logging request
+            //      has "message" entry, use that as the message body
+            //      otherwise, use the entire map.
+            //
+            Object newMsg = eventProps.get("message");
+            if (newMsg == null) {
+                newMsg = msg;
+            }
+
+            for(Iterator iter = eventProps.entrySet().iterator();
+                    iter.hasNext();
+                  ) {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (!("message".equals(entry.getKey()))) {
+                    props.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            return new LoggingEvent(
+                    source.getFQNOfLoggerClass(),
+                    source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()), 
+                    source.getTimeStamp(),
+                    source.getLevel(),
+                    newMsg,
+                    source.getThreadName(),
+                    source.getThrowableInformation(),
+                    source.getNDC(),
+                    source.getLocationInformation(),
+                    props);
+        } else {
+            return source;
+        }
+
+    }
+}
diff --git a/srcjar/org/apache/log4j/rewrite/PropertyRewritePolicy.java b/srcjar/org/apache/log4j/rewrite/PropertyRewritePolicy.java
new file mode 100644 (file)
index 0000000..535736c
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This policy rewrites events by adding
+ * a user-specified list of properties to the event.
+ * Existing properties are not modified.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the PropertyFilter from log4j 1.3.
+ */
+
+public class PropertyRewritePolicy implements RewritePolicy {
+    private Map properties = Collections.EMPTY_MAP;
+    public PropertyRewritePolicy() {
+    }
+
+    /**
+     * Set a string representing the property name/value pairs.
+     * 
+     * Form: propname1=propvalue1,propname2=propvalue2
+     * 
+     * @param props
+     */
+    public void setProperties(String props) {
+        Map hashTable = new HashMap();
+        StringTokenizer pairs = new StringTokenizer(props, ",");
+        while (pairs.hasMoreTokens()) {
+            StringTokenizer entry = new StringTokenizer(pairs.nextToken(), "=");
+            hashTable.put(entry.nextElement().toString().trim(), entry.nextElement().toString().trim());
+        }
+        synchronized(this) {
+            properties = hashTable;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        if (!properties.isEmpty()) {
+            Map rewriteProps = new HashMap(source.getProperties());
+            for(Iterator iter = properties.entrySet().iterator();
+                    iter.hasNext();
+                    ) {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (!rewriteProps.containsKey(entry.getKey())) {
+                    rewriteProps.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            return new LoggingEvent(
+                    source.getFQNOfLoggerClass(),
+                    source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()), 
+                    source.getTimeStamp(),
+                    source.getLevel(),
+                    source.getMessage(),
+                    source.getThreadName(),
+                    source.getThrowableInformation(),
+                    source.getNDC(),
+                    source.getLocationInformation(),
+                    rewriteProps);
+        }
+        return source;
+    }
+
+
+
+}
diff --git a/srcjar/org/apache/log4j/rewrite/ReflectionRewritePolicy.java b/srcjar/org/apache/log4j/rewrite/ReflectionRewritePolicy.java
new file mode 100644 (file)
index 0000000..f1a4cc5
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This policy rewrites events by evaluating any
+ * JavaBean properties on the message object and adding them
+ * to the event properties.  If the message object has a
+ * message property, the value of that property will be
+ * used as the message for the rewritten event and will
+ * not be added to the event properties.  Values from the
+ * JavaBean properties will replace any existing property
+ * with the same name.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the ReflectionFilter from log4j 1.3. 
+ */
+public class ReflectionRewritePolicy implements RewritePolicy {
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        Object msg = source.getMessage();
+        if (!(msg instanceof String)) {
+            Object newMsg = msg;
+            Map rewriteProps = new HashMap(source.getProperties());
+
+            try {
+                PropertyDescriptor[] props = Introspector.getBeanInfo(
+                        msg.getClass(), Object.class).getPropertyDescriptors();
+                if (props.length > 0) {
+                    for (int i=0;i<props.length;i++) {
+                        try {
+                            Object propertyValue =
+                                props[i].getReadMethod().invoke(msg,
+                                        (Object[]) null);
+                            if ("message".equalsIgnoreCase(props[i].getName())) {
+                                newMsg = propertyValue;
+                            } else {
+                                rewriteProps.put(props[i].getName(), propertyValue);
+                            }
+                        } catch (Exception e) {
+                            LogLog.warn("Unable to evaluate property " +
+                                    props[i].getName(), e);
+                        }
+                    }
+                    return new LoggingEvent(
+                            source.getFQNOfLoggerClass(),
+                            source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()),
+                            source.getTimeStamp(),
+                            source.getLevel(),
+                            newMsg,
+                            source.getThreadName(),
+                            source.getThrowableInformation(),
+                            source.getNDC(),
+                            source.getLocationInformation(),
+                            rewriteProps);
+                }
+            } catch (Exception e) {
+                LogLog.warn("Unable to get property descriptors", e);
+            }
+
+        }
+        return source;
+    }
+}
diff --git a/srcjar/org/apache/log4j/rewrite/RewriteAppender.java b/srcjar/org/apache/log4j/rewrite/RewriteAppender.java
new file mode 100644 (file)
index 0000000..368ecf9
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * This appender forwards a logging request to another
+ * appender after possibly rewriting the logging event.
+ *
+ * This appender (with the appropriate policy)
+ * replaces the MapFilter, PropertyFilter and ReflectionFilter
+ * from log4j 1.3.
+ */
+public class RewriteAppender extends AppenderSkeleton
+     implements AppenderAttachable, UnrecognizedElementHandler {
+    /**
+     * Rewrite policy.
+     */
+    private RewritePolicy policy;
+    /**
+     * Nested appenders.
+     */
+    private final AppenderAttachableImpl appenders;
+
+    public RewriteAppender() {
+        appenders = new AppenderAttachableImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void append(final LoggingEvent event) {
+        LoggingEvent rewritten = event;
+        if (policy != null) {
+            rewritten = policy.rewrite(event);
+        }
+        if (rewritten != null) {
+            synchronized (appenders) {
+              appenders.appendLoopOnAppenders(rewritten);
+            }
+        }
+    }
+
+    /**
+     * Add appender.
+     *
+     * @param newAppender appender to add, may not be null.
+     */
+    public void addAppender(final Appender newAppender) {
+      synchronized (appenders) {
+        appenders.addAppender(newAppender);
+      }
+    }
+
+    /**
+     * Get iterator over attached appenders.
+     * @return iterator or null if no attached appenders.
+     */
+    public Enumeration getAllAppenders() {
+      synchronized (appenders) {
+        return appenders.getAllAppenders();
+      }
+    }
+
+    /**
+     * Get appender by name.
+     *
+     * @param name name, may not be null.
+     * @return matching appender or null.
+     */
+    public Appender getAppender(final String name) {
+      synchronized (appenders) {
+        return appenders.getAppender(name);
+      }
+    }
+
+
+    /**
+     * Close this <code>AsyncAppender</code> by interrupting the dispatcher
+     * thread which will process all pending events before exiting.
+     */
+    public void close() {
+      closed = true;
+      //
+      //    close all attached appenders.
+      //
+      synchronized (appenders) {
+        Enumeration iter = appenders.getAllAppenders();
+
+        if (iter != null) {
+          while (iter.hasMoreElements()) {
+            Object next = iter.nextElement();
+
+            if (next instanceof Appender) {
+              ((Appender) next).close();
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Determines if specified appender is attached.
+     * @param appender appender.
+     * @return true if attached.
+     */
+    public boolean isAttached(final Appender appender) {
+      synchronized (appenders) {
+        return appenders.isAttached(appender);
+      }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean requiresLayout() {
+      return false;
+    }
+
+    /**
+     * Removes and closes all attached appenders.
+     */
+    public void removeAllAppenders() {
+      synchronized (appenders) {
+        appenders.removeAllAppenders();
+      }
+    }
+
+    /**
+     * Removes an appender.
+     * @param appender appender to remove.
+     */
+    public void removeAppender(final Appender appender) {
+      synchronized (appenders) {
+        appenders.removeAppender(appender);
+      }
+    }
+
+    /**
+     * Remove appender by name.
+     * @param name name.
+     */
+    public void removeAppender(final String name) {
+      synchronized (appenders) {
+        appenders.removeAppender(name);
+      }
+    }
+
+
+    public void setRewritePolicy(final RewritePolicy rewritePolicy) {
+        policy = rewritePolicy;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public boolean parseUnrecognizedElement(final Element element,
+                                            final Properties props) throws Exception {
+        final String nodeName = element.getNodeName();
+        if ("rewritePolicy".equals(nodeName)) {
+            Object rewritePolicy =
+                    org.apache.log4j.xml.DOMConfigurator.parseElement(
+                            element, props, RewritePolicy.class);
+            if (rewritePolicy != null) {
+                if (rewritePolicy instanceof OptionHandler) {
+                    ((OptionHandler) rewritePolicy).activateOptions();
+                }
+                this.setRewritePolicy((RewritePolicy) rewritePolicy);
+            }
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/rewrite/RewritePolicy.java b/srcjar/org/apache/log4j/rewrite/RewritePolicy.java
new file mode 100644 (file)
index 0000000..bb40507
--- /dev/null
@@ -0,0 +1,37 @@
+package org.apache.log4j.rewrite;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/**
+ * This interface is implemented to provide a rewrite
+ * strategy for RewriteAppender.  RewriteAppender will
+ * call the rewrite method with a source logging event.
+ * The strategy may return that event, create a new event
+ * or return null to suppress the logging request.
+ */
+public interface RewritePolicy {
+    /**
+     * Rewrite a logging event.
+     * @param source a logging event that may be returned or
+     * used to create a new logging event.
+     * @return a logging event or null to suppress processing.
+     */
+    LoggingEvent rewrite(final LoggingEvent source);
+}
diff --git a/srcjar/org/apache/log4j/spi/AppenderAttachable.java b/srcjar/org/apache/log4j/spi/AppenderAttachable.java
new file mode 100644 (file)
index 0000000..89d7ef4
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import java.util.Enumeration;
+
+/**
+   Interface for attaching appenders to objects.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.1 */
+public interface AppenderAttachable {
+  
+  /**
+     Add an appender.
+   */
+  public
+  void addAppender(Appender newAppender);
+
+  /**
+     Get all previously added appenders as an Enumeration.  */
+  public
+  Enumeration getAllAppenders();
+
+  /**
+     Get an appender by name.
+   */
+  public
+  Appender getAppender(String name);
+
+  
+  /**
+     Returns <code>true</code> if the specified appender is in list of
+     attached attached, <code>false</code> otherwise.
+
+     @since 1.2 */
+  public 
+  boolean isAttached(Appender appender);
+
+  /**
+     Remove all previously added appenders.
+  */
+  void removeAllAppenders();
+
+
+  /**
+     Remove the appender passed as parameter from the list of appenders.
+  */
+   void removeAppender(Appender appender);
+
+
+ /**
+    Remove the appender with the name passed as parameter from the
+    list of appenders.  
+  */
+ void
+ removeAppender(String name);   
+}
+
diff --git a/srcjar/org/apache/log4j/spi/Configurator.java b/srcjar/org/apache/log4j/spi/Configurator.java
new file mode 100644 (file)
index 0000000..75c84b3
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+   Implemented by classes capable of configuring log4j using a URL.
+   
+   @since 1.0
+   @author Anders Kristensen
+ */
+public interface Configurator {
+
+  /**
+     Special level value signifying inherited behaviour. The current
+     value of this string constant is <b>inherited</b>. {@link #NULL}
+     is a synonym.  */
+  public static final String INHERITED = "inherited";
+
+  /**
+     Special level signifying inherited behaviour, same as {@link
+     #INHERITED}. The current value of this string constant is
+     <b>null</b>. */
+  public static final String NULL = "null";
+
+
+
+  /**
+    Interpret a resource pointed by a InputStream and set up log4j accordingly.
+
+    The configuration is done relative to the <code>hierarchy</code>
+    parameter.
+
+    @param inputStream The InputStream to parse
+    @param repository The hierarchy to operation upon.
+
+    @since 1.2.17
+   */
+  void doConfigure(InputStream inputStream, LoggerRepository repository);
+
+  /**
+     Interpret a resource pointed by a URL and set up log4j accordingly.
+
+     The configuration is done relative to the <code>hierarchy</code>
+     parameter.
+
+     @param url The URL to parse
+     @param repository The hierarchy to operation upon.
+   */
+  void doConfigure(URL url, LoggerRepository repository);
+}
diff --git a/srcjar/org/apache/log4j/spi/DefaultRepositorySelector.java b/srcjar/org/apache/log4j/spi/DefaultRepositorySelector.java
new file mode 100644 (file)
index 0000000..4b30752
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.log4j.spi;
+
+
+public class DefaultRepositorySelector implements RepositorySelector {
+
+  final LoggerRepository repository;
+
+  public
+  DefaultRepositorySelector(LoggerRepository repository) {
+    this.repository = repository;
+  }
+
+  public
+  LoggerRepository getLoggerRepository() {
+    return repository;
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/spi/ErrorCode.java b/srcjar/org/apache/log4j/spi/ErrorCode.java
new file mode 100644 (file)
index 0000000..b0e57f1
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+/**
+   This interface defines commonly encoutered error codes.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0
+ */
+public interface ErrorCode {
+
+  public final int GENERIC_FAILURE = 0;
+  public final int WRITE_FAILURE = 1;
+  public final int FLUSH_FAILURE = 2;
+  public final int CLOSE_FAILURE = 3;
+  public final int FILE_OPEN_FAILURE = 4;
+  public final int MISSING_LAYOUT = 5;
+  public final int ADDRESS_PARSE_FAILURE = 6;
+}
diff --git a/srcjar/org/apache/log4j/spi/ErrorHandler.java b/srcjar/org/apache/log4j/spi/ErrorHandler.java
new file mode 100644 (file)
index 0000000..d629a2d
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+
+/**
+   Appenders may delegate their error handling to
+   <code>ErrorHandlers</code>.
+
+   <p>Error handling is a particularly tedious to get right because by
+   definition errors are hard to predict and to reproduce. 
+
+
+   <p>Please take the time to contact the author in case you discover
+   that errors are not properly handled. You are most welcome to
+   suggest new error handling policies or criticize existing policies.
+
+
+   @author Ceki G&uuml;lc&uuml;
+   
+*/
+public interface ErrorHandler extends OptionHandler {
+
+  /**
+     Add a reference to a logger to which the failing appender might
+     be attached to. The failing appender will be searched and
+     replaced only in the loggers you add through this method.
+
+     @param logger One of the loggers that will be searched for the failing
+     appender in view of replacement.
+     
+     @since 1.2 */
+  void setLogger(Logger logger);
+
+
+  /**
+     Equivalent to the {@link #error(String, Exception, int,
+     LoggingEvent event)} with the the event parameteter set to
+     <code>null</code>.
+     
+  */
+  void error(String message, Exception e, int errorCode);
+
+  /**
+     This method is normally used to just print the error message
+     passed as a parameter.       
+  */
+  void error(String message);
+
+  /**
+     This method is invoked to handle the error.
+
+     @param message The message assoicated with the error.
+     @param e The Exption that was thrown when the error occured.
+     @param errorCode The error code associated with the error. 
+     @param event The logging event that the failing appender is asked
+            to log.
+
+     @since 1.2 */
+  void error(String message, Exception e, int errorCode, LoggingEvent event);
+  
+  /**
+     Set the appender for which errors are handled. This method is
+     usually called when the error handler is configured.
+     
+     @since 1.2 */
+  void setAppender(Appender appender);
+
+  /**
+     Set the appender to falkback upon in case of failure.
+     
+     @since 1.2 */
+  void setBackupAppender(Appender appender);
+}
diff --git a/srcjar/org/apache/log4j/spi/Filter.java b/srcjar/org/apache/log4j/spi/Filter.java
new file mode 100644 (file)
index 0000000..7bddbe8
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+
+/**
+   Users should extend this class to implement customized logging
+   event filtering. Note that {@link org.apache.log4j.Category} and {@link
+   org.apache.log4j.AppenderSkeleton}, the parent class of all standard
+   appenders, have built-in filtering rules. It is suggested that you
+   first use and understand the built-in rules before rushing to write
+   your own custom filters.
+
+   <p>This abstract class assumes and also imposes that filters be
+   organized in a linear chain. The {@link #decide
+   decide(LoggingEvent)} method of each filter is called sequentially,
+   in the order of their addition to the chain.
+
+   <p>The {@link #decide decide(LoggingEvent)} method must return one
+   of the integer constants {@link #DENY}, {@link #NEUTRAL} or {@link
+   #ACCEPT}.
+
+   <p>If the value {@link #DENY} is returned, then the log event is
+   dropped immediately without consulting with the remaining
+   filters. 
+
+   <p>If the value {@link #NEUTRAL} is returned, then the next filter
+   in the chain is consulted. If there are no more filters in the
+   chain, then the log event is logged. Thus, in the presence of no
+   filters, the default behaviour is to log all logging events.
+
+   <p>If the value {@link #ACCEPT} is returned, then the log
+   event is logged without consulting the remaining filters. 
+
+   <p>The philosophy of log4j filters is largely inspired from the
+   Linux ipchains. 
+
+   <p>Note that filtering is only supported by the {@link
+   org.apache.log4j.xml.DOMConfigurator DOMConfigurator}. The {@link
+   org.apache.log4j.PropertyConfigurator PropertyConfigurator} does not
+   support filters.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0 */
+public abstract class Filter implements OptionHandler {
+
+  /**
+     Points to the next filter in the filter chain.
+
+     @deprecated As of 1.2.12, use {@link #getNext} and {@link #setNext} instead
+   */
+  public Filter next;
+
+  /**
+     The log event must be dropped immediately without consulting
+     with the remaining filters, if any, in the chain.  */
+  public static final int DENY    = -1;
+  
+  /**
+     This filter is neutral with respect to the log event. The
+     remaining filters, if any, should be consulted for a final decision.
+  */
+  public static final int NEUTRAL = 0;
+
+  /**
+     The log event must be logged immediately without consulting with
+     the remaining filters, if any, in the chain.  */
+  public static final int ACCEPT  = 1;
+
+
+  /**
+     Usually filters options become active when set. We provide a
+     default do-nothing implementation for convenience.
+  */
+  public
+  void activateOptions() {
+  }
+
+
+
+  /**     
+     <p>If the decision is <code>DENY</code>, then the event will be
+     dropped. If the decision is <code>NEUTRAL</code>, then the next
+     filter, if any, will be invoked. If the decision is ACCEPT then
+     the event will be logged without consulting with other filters in
+     the chain.
+
+     @param event The LoggingEvent to decide upon.
+     @return decision The decision of the filter.  */
+  abstract
+  public
+  int decide(LoggingEvent event);
+
+  /**
+   * Set the next filter pointer.
+   */ 
+  public void setNext(Filter next) {
+    this.next = next;
+  }
+  /**
+   * Return the pointer to the next filter;
+   */ 
+  public Filter getNext() {
+        return next;
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/spi/HierarchyEventListener.java b/srcjar/org/apache/log4j/spi/HierarchyEventListener.java
new file mode 100644 (file)
index 0000000..77a0efd
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+
+/**
+   Listen to events occuring within a {@link
+   org.apache.log4j.Hierarchy Hierarchy}.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2
+   
+ */
+public interface HierarchyEventListener {
+
+  //public
+  //void categoryCreationEvent(Category cat);
+
+
+  public
+  void addAppenderEvent(Category cat, Appender appender);
+
+  public
+  void removeAppenderEvent(Category cat, Appender appender);
+
+
+}
diff --git a/srcjar/org/apache/log4j/spi/LocationInfo.java b/srcjar/org/apache/log4j/spi/LocationInfo.java
new file mode 100644 (file)
index 0000000..2da5486
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors: Mathias Rupprecht <mmathias.rupprecht@fja.com>
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.LogLog;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.InterruptedIOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+   The internal representation of caller location information.
+
+   @since 0.8.3
+*/
+public class LocationInfo implements java.io.Serializable {
+
+  /**
+     Caller's line number.
+  */
+  transient String lineNumber;
+  /**
+     Caller's file name.
+  */
+  transient String fileName;
+  /**
+     Caller's fully qualified class name.
+  */
+  transient String className;
+  /**
+     Caller's method name.
+  */
+  transient String methodName;
+  /**
+     All available caller information, in the format
+     <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code>
+    */
+  public String fullInfo;
+
+  private static StringWriter sw = new StringWriter();
+  private static PrintWriter pw = new PrintWriter(sw);
+
+  private static Method getStackTraceMethod;
+  private static Method getClassNameMethod;
+  private static Method getMethodNameMethod;
+  private static Method getFileNameMethod;
+  private static Method getLineNumberMethod;
+
+
+  /**
+     When location information is not available the constant
+     <code>NA</code> is returned. Current value of this string
+     constant is <b>?</b>.  */
+  public final static String NA = "?";
+
+  static final long serialVersionUID = -1325822038990805636L;
+
+    /**
+     * NA_LOCATION_INFO is provided for compatibility with log4j 1.3.
+     * @since 1.2.15
+     */
+    public static final LocationInfo NA_LOCATION_INFO =
+            new LocationInfo(NA, NA, NA, NA);
+
+
+
+  // Check if we are running in IBM's visual age.
+  static boolean inVisualAge = false;
+  static {
+    try {
+      inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null;
+      LogLog.debug("Detected IBM VisualAge environment.");
+    } catch(Throwable e) {
+      // nothing to do
+    }
+      try {
+          Class[] noArgs = null;
+          getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
+          Class stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
+          getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
+          getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
+          getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
+          getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
+      } catch(ClassNotFoundException ex) {
+          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
+      } catch(NoSuchMethodException ex) {
+          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
+      }
+  }
+
+  /**
+     Instantiate location information based on a Throwable. We
+     expect the Throwable <code>t</code>, to be in the format
+
+       <pre>
+        java.lang.Throwable
+        ...
+          at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
+          at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
+        at org.apache.log4j.Category.callAppenders(Category.java:131)
+        at org.apache.log4j.Category.log(Category.java:512)
+        at callers.fully.qualified.className.methodName(FileName.java:74)
+       ...
+       </pre>
+
+       <p>However, we can also deal with JIT compilers that "lose" the
+       location information, especially between the parentheses.
+        @param t throwable used to determine location, may be null.
+        @param fqnOfCallingClass class name of first class considered part of
+           the logging framework.  Location will be site that calls a method on this class.
+
+    */
+    public LocationInfo(Throwable t, String fqnOfCallingClass) {
+      if(t == null || fqnOfCallingClass == null) {
+        return;
+    }
+      if (getLineNumberMethod != null) {
+          try {
+              Object[] noArgs = null;
+              Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
+              String prevClass = NA;
+              for(int i = elements.length - 1; i >= 0; i--) {
+                  String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
+                  if(fqnOfCallingClass.equals(thisClass)) {
+                      int caller = i + 1;
+                      if (caller < elements.length) {
+                          className = prevClass;
+                          methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
+                          fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
+                          if (fileName == null) {
+                              fileName = NA;
+                          }
+                          int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
+                          if (line < 0) {
+                              lineNumber = NA;
+                          } else {
+                              lineNumber = String.valueOf(line);
+                          }
+                          StringBuffer buf = new StringBuffer();
+                          buf.append(className);
+                          buf.append(".");
+                          buf.append(methodName);
+                          buf.append("(");
+                          buf.append(fileName);
+                          buf.append(":");
+                          buf.append(lineNumber);
+                          buf.append(")");
+                          this.fullInfo = buf.toString();
+                      }
+                      return;
+                  }
+                  prevClass = thisClass;
+              }
+              return;
+          } catch(IllegalAccessException ex) {
+              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
+          } catch(InvocationTargetException ex) {
+              if (ex.getTargetException() instanceof InterruptedException
+                      || ex.getTargetException() instanceof InterruptedIOException) {
+                  Thread.currentThread().interrupt();
+              }
+              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
+          } catch(RuntimeException ex) {
+              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
+          }
+      }
+
+      String s;
+      // Protect against multiple access to sw.
+      synchronized(sw) {
+       t.printStackTrace(pw);
+       s = sw.toString();
+       sw.getBuffer().setLength(0);
+      }
+      //System.out.println("s is ["+s+"].");
+      int ibegin, iend;
+
+      // Given the current structure of the package, the line
+      // containing "org.apache.log4j.Category." should be printed just
+      // before the caller.
+
+      // This method of searching may not be fastest but it's safer
+      // than counting the stack depth which is not guaranteed to be
+      // constant across JVM implementations.
+      ibegin = s.lastIndexOf(fqnOfCallingClass);
+      if(ibegin == -1) {
+        return;
+    }
+
+      //
+      //   if the next character after the class name exists
+      //       but is not a period, see if the classname is
+      //       followed by a period earlier in the trace.
+      //       Minimizes mistakeningly matching on a class whose
+      //       name is a substring of the desired class.
+      //       See bug 44888.
+      if (ibegin + fqnOfCallingClass.length() < s.length() &&
+              s.charAt(ibegin + fqnOfCallingClass.length()) != '.') {
+          int i = s.lastIndexOf(fqnOfCallingClass + ".");
+          if (i != -1) {
+              ibegin = i;
+          }
+      }
+
+
+      ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
+      if(ibegin == -1) {
+        return;
+    }
+      ibegin+= Layout.LINE_SEP_LEN;
+
+      // determine end of line
+      iend = s.indexOf(Layout.LINE_SEP, ibegin);
+      if(iend == -1) {
+        return;
+    }
+
+      // VA has a different stack trace format which doesn't
+      // need to skip the inital 'at'
+      if(!inVisualAge) {
+       // back up to first blank character
+       ibegin = s.lastIndexOf("at ", iend);
+       if(ibegin == -1) {
+        return;
+    }
+       // Add 3 to skip "at ";
+       ibegin += 3;
+      }
+      // everything between is the requested stack item
+      this.fullInfo = s.substring(ibegin, iend);
+    }
+
+    /**
+     *   Appends a location fragment to a buffer to build the 
+     *     full location info.
+     *    @param buf StringBuffer to receive content.
+     *    @param fragment fragment of location (class, method, file, line),
+     *        if null the value of NA will be appended.
+     *    @since 1.2.15
+     */
+    private static final void appendFragment(final StringBuffer buf,
+                                             final String fragment) {
+          if (fragment == null) {
+             buf.append(NA);
+          } else {
+             buf.append(fragment);
+          }
+    }
+
+    /**
+     * Create new instance.
+     * @param file source file name
+     * @param classname class name
+     * @param method method
+     * @param line source line number
+     *
+     * @since 1.2.15
+     */
+    public LocationInfo(
+      final String file,
+      final String classname,
+      final String method,
+      final String line) {
+      this.fileName = file;
+      this.className = classname;
+      this.methodName = method;
+      this.lineNumber = line;
+      StringBuffer buf = new StringBuffer();
+         appendFragment(buf, classname);
+         buf.append(".");
+         appendFragment(buf, method);
+         buf.append("(");
+         appendFragment(buf, file);
+         buf.append(":");
+         appendFragment(buf, line);
+         buf.append(")");
+         this.fullInfo = buf.toString();
+    }
+
+    /**
+       Return the fully qualified class name of the caller making the
+       logging request.
+    */
+    public
+    String getClassName() {
+      if(fullInfo == null) {
+        return NA;
+    }
+      if(className == null) {
+       // Starting the search from '(' is safer because there is
+       // potentially a dot between the parentheses.
+       int iend = fullInfo.lastIndexOf('(');
+       if(iend == -1) {
+        className = NA;
+    } else {
+         iend =fullInfo.lastIndexOf('.', iend);
+
+         // This is because a stack trace in VisualAge looks like:
+
+          //java.lang.RuntimeException
+         //  java.lang.Throwable()
+         //  java.lang.Exception()
+         //  java.lang.RuntimeException()
+         //  void test.test.B.print()
+         //  void test.test.A.printIndirect()
+         //  void test.test.Run.main(java.lang.String [])
+          int ibegin = 0;
+         if (inVisualAge) {
+           ibegin = fullInfo.lastIndexOf(' ', iend)+1;
+          }
+
+         if(iend == -1) {
+        className = NA;
+    } else {
+        className = this.fullInfo.substring(ibegin, iend);
+    }
+       }
+      }
+      return className;
+    }
+
+    /**
+       Return the file name of the caller.
+
+       <p>This information is not always available.
+    */
+    public
+    String getFileName() {
+      if(fullInfo == null) {
+        return NA;
+    }
+
+      if(fileName == null) {
+       int iend = fullInfo.lastIndexOf(':');
+       if(iend == -1) {
+        fileName = NA;
+    } else {
+         int ibegin = fullInfo.lastIndexOf('(', iend - 1);
+         fileName = this.fullInfo.substring(ibegin + 1, iend);
+       }
+      }
+      return fileName;
+    }
+
+    /**
+       Returns the line number of the caller.
+
+       <p>This information is not always available.
+    */
+    public
+    String getLineNumber() {
+      if(fullInfo == null) {
+        return NA;
+    }
+
+      if(lineNumber == null) {
+       int iend = fullInfo.lastIndexOf(')');
+       int ibegin = fullInfo.lastIndexOf(':', iend -1);
+       if(ibegin == -1) {
+        lineNumber = NA;
+    } else {
+        lineNumber = this.fullInfo.substring(ibegin + 1, iend);
+    }
+      }
+      return lineNumber;
+    }
+
+    /**
+       Returns the method name of the caller.
+    */
+    public
+    String getMethodName() {
+      if(fullInfo == null) {
+        return NA;
+    }
+      if(methodName == null) {
+       int iend = fullInfo.lastIndexOf('(');
+       int ibegin = fullInfo.lastIndexOf('.', iend);
+       if(ibegin == -1) {
+        methodName = NA;
+    } else {
+        methodName = this.fullInfo.substring(ibegin + 1, iend);
+    }
+      }
+      return methodName;
+    }
+}
diff --git a/srcjar/org/apache/log4j/spi/LoggerFactory.java b/srcjar/org/apache/log4j/spi/LoggerFactory.java
new file mode 100644 (file)
index 0000000..568c41f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Logger;
+
+/**
+   
+  Implement this interface to create new instances of Logger or
+  a sub-class of Logger.
+
+  <p>See <code>examples/subclass/MyLogger.java</code> for an example.
+
+  @author Ceki G&uuml;lc&uuml;
+  @since version 0.8.5
+   
+ */
+public interface LoggerFactory {
+
+  public
+  Logger makeNewLoggerInstance(String name);
+
+}
diff --git a/srcjar/org/apache/log4j/spi/LoggerRepository.java b/srcjar/org/apache/log4j/spi/LoggerRepository.java
new file mode 100644 (file)
index 0000000..9ca156b
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import java.util.Enumeration;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+/**
+   A <code>LoggerRepository</code> is used to create and retrieve
+   <code>Loggers</code>. The relation between loggers in a repository
+   depends on the repository but typically loggers are arranged in a
+   named hierarchy.
+
+   <p>In addition to the creational methods, a
+   <code>LoggerRepository</code> can be queried for existing loggers,
+   can act as a point of registry for events related to loggers.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2 */
+public interface LoggerRepository {
+
+  /**
+     Add a {@link HierarchyEventListener} event to the repository.
+  */
+  public
+  void addHierarchyEventListener(HierarchyEventListener listener);
+
+  /**
+     Returns whether this repository is disabled for a given
+     level. The answer depends on the repository threshold and the
+     <code>level</code> parameter. See also {@link #setThreshold}
+     method.  */
+  boolean isDisabled(int level);
+
+  /**
+     Set the repository-wide threshold. All logging requests below the
+     threshold are immediately dropped. By default, the threshold is
+     set to <code>Level.ALL</code> which has the lowest possible rank.  */
+  public
+  void setThreshold(Level level);
+
+  /**
+      Another form of {@link #setThreshold(Level)} accepting a string
+      parameter instead of a <code>Level</code>. */
+  public
+  void setThreshold(String val);
+
+  public
+  void emitNoAppenderWarning(Category cat);
+
+  /**
+     Get the repository-wide threshold. See {@link
+     #setThreshold(Level)} for an explanation. */
+  public
+  Level getThreshold();
+
+  public
+  Logger getLogger(String name);
+
+  public
+  Logger getLogger(String name, LoggerFactory factory);
+
+  public
+  Logger getRootLogger();
+
+  public
+  abstract
+  Logger exists(String name);
+
+  public
+  abstract
+  void shutdown();
+
+  public
+  Enumeration getCurrentLoggers();
+
+  /**
+     Deprecated. Please use {@link #getCurrentLoggers} instead.  */
+  public
+  Enumeration getCurrentCategories();
+
+
+  public
+  abstract
+  void fireAddAppenderEvent(Category logger, Appender appender);
+
+  public
+  abstract
+  void resetConfiguration();
+
+}
diff --git a/srcjar/org/apache/log4j/spi/LoggingEvent.java b/srcjar/org/apache/log4j/spi/LoggingEvent.java
new file mode 100644 (file)
index 0000000..0475b67
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import java.io.InterruptedIOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.MDC;
+import org.apache.log4j.NDC;
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.LogLog;
+
+// Contributors:   Nelson Minar <nelson@monkey.org>
+//                 Wolf Siberski
+//                 Anders Kristensen <akristensen@dynamicsoft.com>
+
+/**
+   The internal representation of logging events. When an affirmative
+   decision is made to log then a <code>LoggingEvent</code> instance
+   is created. This instance is passed around to the different log4j
+   components.
+
+   <p>This class is of concern to those wishing to extend log4j.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author James P. Cakalic
+
+   @since 0.8.2 */
+public class LoggingEvent implements java.io.Serializable {
+
+  private static long startTime = System.currentTimeMillis();
+
+  /** Fully qualified name of the calling category class. */
+  transient public final String fqnOfCategoryClass;
+
+  /** 
+   * The category of the logging event. This field is not serialized
+   * for performance reasons.
+   *
+   * <p>It is set by the LoggingEvent constructor or set by a remote
+   * entity after deserialization.
+   * 
+   * @deprecated This field will be marked as private or be completely
+   * removed in future releases. Please do not use it.
+   * */
+  transient private Category logger;
+
+  /** 
+   * <p>The category (logger) name.
+   *   
+   * @deprecated This field will be marked as private in future
+   * releases. Please do not access it directly. Use the {@link
+   * #getLoggerName} method instead.
+
+   * */
+  final public String categoryName;
+
+  /** 
+   * Level of logging event. Level cannot be serializable because it
+   * is a flyweight.  Due to its special seralization it cannot be
+   * declared final either.
+   *   
+   * <p> This field should not be accessed directly. You shoud use the
+   * {@link #getLevel} method instead.
+   *
+   * @deprecated This field will be marked as private in future
+   * releases. Please do not access it directly. Use the {@link
+   * #getLevel} method instead.
+   * */
+  transient public Priority level;
+
+  /** The nested diagnostic context (NDC) of logging event. */
+  private String ndc;
+
+  /** The mapped diagnostic context (MDC) of logging event. */
+  private Hashtable mdcCopy;
+
+
+  /** Have we tried to do an NDC lookup? If we did, there is no need
+   *  to do it again.  Note that its value is always false when
+   *  serialized. Thus, a receiving SocketNode will never use it's own
+   *  (incorrect) NDC. See also writeObject method. */
+  private boolean ndcLookupRequired = true;
+
+
+  /** Have we tried to do an MDC lookup? If we did, there is no need
+   *  to do it again.  Note that its value is always false when
+   *  serialized. See also the getMDC and getMDCCopy methods.  */
+  private boolean mdcCopyLookupRequired = true;
+
+  /** The application supplied message of logging event. */
+  transient private Object message;
+
+  /** The application supplied message rendered through the log4j
+      objet rendering mechanism.*/
+  private String renderedMessage;
+
+  /** The name of thread in which this logging event was generated. */
+  private String threadName;
+
+
+  /** This
+      variable contains information about this event's throwable
+  */
+  private ThrowableInformation throwableInfo;
+
+  /** The number of milliseconds elapsed from 1/1/1970 until logging event
+      was created. */
+  public final long timeStamp;
+  /** Location information for the caller. */
+  private LocationInfo locationInfo;
+
+  // Serialization
+  static final long serialVersionUID = -868428216207166145L;
+
+  static final Integer[] PARAM_ARRAY = new Integer[1];
+  static final String TO_LEVEL = "toLevel";
+  static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class};
+  static final Hashtable methodCache = new Hashtable(3); // use a tiny table
+
+  /**
+     Instantiate a LoggingEvent from the supplied parameters.
+
+     <p>Except {@link #timeStamp} all the other fields of
+     <code>LoggingEvent</code> are filled when actually needed.
+     <p>
+     @param logger The logger generating this event.
+     @param level The level of this event.
+     @param message  The message of this event.
+     @param throwable The throwable of this event.  */
+  public LoggingEvent(String fqnOfCategoryClass, Category logger,
+                     Priority level, Object message, Throwable throwable) {
+    this.fqnOfCategoryClass = fqnOfCategoryClass;
+    this.logger = logger;
+    this.categoryName = logger.getName();
+    this.level = level;
+    this.message = message;
+    if(throwable != null) {
+      this.throwableInfo = new ThrowableInformation(throwable, logger);
+    }
+    timeStamp = System.currentTimeMillis();
+  }
+
+  /**
+     Instantiate a LoggingEvent from the supplied parameters.
+
+     <p>Except {@link #timeStamp} all the other fields of
+     <code>LoggingEvent</code> are filled when actually needed.
+     <p>
+     @param logger The logger generating this event.
+     @param timeStamp the timestamp of this logging event
+     @param level The level of this event.
+     @param message  The message of this event.
+     @param throwable The throwable of this event.  */
+  public LoggingEvent(String fqnOfCategoryClass, Category logger,
+                     long timeStamp, Priority level, Object message,
+                     Throwable throwable) {
+    this.fqnOfCategoryClass = fqnOfCategoryClass;
+    this.logger = logger;
+    this.categoryName = logger.getName();
+    this.level = level;
+    this.message = message;
+    if(throwable != null) {
+      this.throwableInfo = new ThrowableInformation(throwable, logger);
+    }
+
+    this.timeStamp = timeStamp;
+  }
+
+    /**
+       Create new instance.
+       @since 1.2.15
+       @param fqnOfCategoryClass Fully qualified class name
+                 of Logger implementation.
+       @param logger The logger generating this event.
+       @param timeStamp the timestamp of this logging event
+       @param level The level of this event.
+       @param message  The message of this event.
+       @param threadName thread name
+       @param throwable The throwable of this event.
+       @param ndc Nested diagnostic context
+       @param info Location info
+       @param properties MDC properties
+     */
+    public LoggingEvent(final String fqnOfCategoryClass,
+                        final Category logger,
+                        final long timeStamp,
+                        final Level level,
+                        final Object message,
+                        final String threadName,
+                        final ThrowableInformation throwable,
+                        final String ndc,
+                        final LocationInfo info,
+                        final java.util.Map properties) {
+      super();
+      this.fqnOfCategoryClass = fqnOfCategoryClass;
+      this.logger = logger;
+      if (logger != null) {
+          categoryName = logger.getName();
+      } else {
+          categoryName = null;
+      }
+      this.level = level;
+      this.message = message;
+      if(throwable != null) {
+        this.throwableInfo = throwable;
+      }
+
+      this.timeStamp = timeStamp;
+      this.threadName = threadName;
+      ndcLookupRequired = false;
+      this.ndc = ndc;
+      this.locationInfo = info;
+      mdcCopyLookupRequired = false;
+      if (properties != null) {
+        mdcCopy = new java.util.Hashtable(properties);
+      }
+    }
+
+
+  /**
+     Set the location information for this logging event. The collected
+     information is cached for future use.
+   */
+  public LocationInfo getLocationInformation() {
+    if(locationInfo == null) {
+      locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass);
+    }
+    return locationInfo;
+  }
+
+  /**
+   * Return the level of this event. Use this form instead of directly
+   * accessing the <code>level</code> field.  */
+  public Level getLevel() {
+    return (Level) level;
+  }
+
+  /**
+   * Return the name of the logger. Use this form instead of directly
+   * accessing the <code>categoryName</code> field.  
+   */
+  public String getLoggerName() {
+    return categoryName;
+  }
+
+    /**
+     * Gets the logger of the event.
+     * Use should be restricted to cloning events.
+     * @since 1.2.15
+     */
+    public Category getLogger() {
+      return logger;
+    }
+
+  /**
+     Return the message for this logging event.
+
+     <p>Before serialization, the returned object is the message
+     passed by the user to generate the logging event. After
+     serialization, the returned value equals the String form of the
+     message possibly after object rendering.
+
+     @since 1.1 */
+  public
+  Object getMessage() {
+    if(message != null) {
+      return message;
+    } else {
+      return getRenderedMessage();
+    }
+  }
+
+  /**
+   * This method returns the NDC for this event. It will return the
+   * correct content even if the event was generated in a different
+   * thread or even on a different machine. The {@link NDC#get} method
+   * should <em>never</em> be called directly.  */
+  public
+  String getNDC() {
+    if(ndcLookupRequired) {
+      ndcLookupRequired = false;
+      ndc = NDC.get();
+    }
+    return ndc;
+  }
+
+
+  /**
+      Returns the the context corresponding to the <code>key</code>
+      parameter. If there is a local MDC copy, possibly because we are
+      in a logging server or running inside AsyncAppender, then we
+      search for the key in MDC copy, if a value is found it is
+      returned. Otherwise, if the search in MDC copy returns a null
+      result, then the current thread's <code>MDC</code> is used.
+      
+      <p>Note that <em>both</em> the local MDC copy and the current
+      thread's MDC are searched.
+
+  */
+  public
+  Object getMDC(String key) {
+    Object r;
+    // Note the mdcCopy is used if it exists. Otherwise we use the MDC
+    // that is associated with the thread.
+    if(mdcCopy != null) {
+      r = mdcCopy.get(key);
+      if(r != null) {
+        return r;
+      }
+    }
+    return MDC.get(key);
+  }
+
+  /**
+     Obtain a copy of this thread's MDC prior to serialization or
+     asynchronous logging.  
+  */
+  public
+  void getMDCCopy() {
+    if(mdcCopyLookupRequired) {
+      mdcCopyLookupRequired = false;
+      // the clone call is required for asynchronous logging.
+      // See also bug #5932.
+      Hashtable t = MDC.getContext();
+      if(t != null) {
+       mdcCopy = (Hashtable) t.clone();
+      }
+    }
+  }
+
+  public
+  String getRenderedMessage() {
+     if(renderedMessage == null && message != null) {
+       if(message instanceof String) {
+        renderedMessage = (String) message;
+    } else {
+        LoggerRepository repository = logger.getLoggerRepository();
+
+        if(repository instanceof RendererSupport) {
+          RendererSupport rs = (RendererSupport) repository;
+          renderedMessage= rs.getRendererMap().findAndRender(message);
+        } else {
+          renderedMessage = message.toString();
+        }
+       }
+     }
+     return renderedMessage;
+  }
+
+  /**
+     Returns the time when the application started, in milliseconds
+     elapsed since 01.01.1970.  */
+  public static long getStartTime() {
+    return startTime;
+  }
+
+  public
+  String getThreadName() {
+    if(threadName == null) {
+        threadName = (Thread.currentThread()).getName();
+    }
+    return threadName;
+  }
+
+  /**
+     Returns the throwable information contained within this
+     event. May be <code>null</code> if there is no such information.
+
+     <p>Note that the {@link Throwable} object contained within a
+     {@link ThrowableInformation} does not survive serialization.
+
+     @since 1.1 */
+  public
+  ThrowableInformation getThrowableInformation() {
+    return throwableInfo;
+  }
+
+  /**
+     Return this event's throwable's string[] representaion.
+  */
+  public
+  String[] getThrowableStrRep() {
+
+    if(throwableInfo ==  null) {
+        return null;
+    } else {
+        return throwableInfo.getThrowableStrRep();
+    }
+  }
+
+
+  private
+  void readLevel(ObjectInputStream ois)
+                      throws java.io.IOException, ClassNotFoundException {
+
+    int p = ois.readInt();
+    try {
+      String className = (String) ois.readObject();
+      if(className == null) {
+       level = Level.toLevel(p);
+      } else {
+       Method m = (Method) methodCache.get(className);
+       if(m == null) {
+         Class clazz = Loader.loadClass(className);
+         // Note that we use Class.getDeclaredMethod instead of
+         // Class.getMethod. This assumes that the Level subclass
+         // implements the toLevel(int) method which is a
+         // requirement. Actually, it does not make sense for Level
+         // subclasses NOT to implement this method. Also note that
+         // only Level can be subclassed and not Priority.
+         m = clazz.getDeclaredMethod(TO_LEVEL, TO_LEVEL_PARAMS);
+         methodCache.put(className, m);
+       }
+       level = (Level) m.invoke(null,  new Integer[] { new Integer(p) } );
+      }
+    } catch(InvocationTargetException e) {
+        if (e.getTargetException() instanceof InterruptedException
+                || e.getTargetException() instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+    LogLog.warn("Level deserialization failed, reverting to default.", e);
+       level = Level.toLevel(p);
+    } catch(NoSuchMethodException e) {
+       LogLog.warn("Level deserialization failed, reverting to default.", e);
+       level = Level.toLevel(p);
+    } catch(IllegalAccessException e) {
+       LogLog.warn("Level deserialization failed, reverting to default.", e);
+       level = Level.toLevel(p);
+    } catch(RuntimeException e) {
+       LogLog.warn("Level deserialization failed, reverting to default.", e);
+       level = Level.toLevel(p);
+    }
+  }
+
+  private void readObject(ObjectInputStream ois)
+                        throws java.io.IOException, ClassNotFoundException {
+    ois.defaultReadObject();
+    readLevel(ois);
+
+    // Make sure that no location info is available to Layouts
+    if(locationInfo == null) {
+        locationInfo = new LocationInfo(null, null);
+    }
+  }
+
+  private
+  void writeObject(ObjectOutputStream oos) throws java.io.IOException {
+    // Aside from returning the current thread name the wgetThreadName
+    // method sets the threadName variable.
+    this.getThreadName();
+
+    // This sets the renders the message in case it wasn't up to now.
+    this.getRenderedMessage();
+
+    // This call has a side effect of setting this.ndc and
+    // setting ndcLookupRequired to false if not already false.
+    this.getNDC();
+
+    // This call has a side effect of setting this.mdcCopy and
+    // setting mdcLookupRequired to false if not already false.
+    this.getMDCCopy();
+
+    // This sets the throwable sting representation of the event throwable.
+    this.getThrowableStrRep();
+
+    oos.defaultWriteObject();
+
+    // serialize this event's level
+    writeLevel(oos);
+  }
+
+  private
+  void writeLevel(ObjectOutputStream oos) throws java.io.IOException {
+
+    oos.writeInt(level.toInt());
+
+    Class clazz = level.getClass();
+    if(clazz == Level.class) {
+      oos.writeObject(null);
+    } else {
+      // writing directly the Class object would be nicer, except that
+      // serialized a Class object can not be read back by JDK
+      // 1.1.x. We have to resort to this hack instead.
+      oos.writeObject(clazz.getName());
+    }
+  }
+
+    /**
+     * Set value for MDC property.
+     * This adds the specified MDC property to the event.
+     * Access to the MDC is not synchronized, so this
+     * method should only be called when it is known that
+     * no other threads are accessing the MDC.
+     * @since 1.2.15
+     * @param propName
+     * @param propValue
+     */
+  public final void setProperty(final String propName,
+                          final String propValue) {
+        if (mdcCopy == null) {
+            getMDCCopy();
+        }
+        if (mdcCopy == null) {
+            mdcCopy = new Hashtable();
+        }
+        mdcCopy.put(propName, propValue);      
+  }
+
+    /**
+     * Return a property for this event. The return value can be null.
+     *
+     * Equivalent to getMDC(String) in log4j 1.2.  Provided
+     * for compatibility with log4j 1.3.
+     *
+     * @param key property name
+     * @return property value or null if property not set
+     * @since 1.2.15
+     */
+    public final String getProperty(final String key) {
+        Object value = getMDC(key);
+        String retval = null;
+        if (value != null) {
+            retval = value.toString();
+        }
+        return retval;
+    }
+
+    /**
+     * Check for the existence of location information without creating it
+     * (a byproduct of calling getLocationInformation).
+     * @return true if location information has been extracted.
+     * @since 1.2.15
+     */
+    public final boolean locationInformationExists() {
+      return (locationInfo != null);
+    }
+
+    /**
+     * Getter for the event's time stamp. The time stamp is calculated starting
+     * from 1970-01-01 GMT.
+     * @return timestamp
+     *
+     * @since 1.2.15
+     */
+    public final long getTimeStamp() {
+      return timeStamp;
+    }
+
+    /**
+     * Returns the set of the key values in the properties
+     * for the event.
+     *
+     * The returned set is unmodifiable by the caller.
+     *
+     * Provided for compatibility with log4j 1.3
+     *
+     * @return Set an unmodifiable set of the property keys.
+     * @since 1.2.15
+     */
+    public Set getPropertyKeySet() {
+      return getProperties().keySet();
+    }
+
+    /**
+     * Returns the set of properties
+     * for the event.
+     *
+     * The returned set is unmodifiable by the caller.
+     *
+     * Provided for compatibility with log4j 1.3
+     *
+     * @return Set an unmodifiable map of the properties.
+     * @since 1.2.15
+     */
+    public Map getProperties() {
+      getMDCCopy();
+      Map properties;
+      if (mdcCopy == null) {
+         properties = new HashMap();
+      } else {
+         properties = mdcCopy;
+      }
+      return Collections.unmodifiableMap(properties);
+    }
+
+    /**
+     * Get the fully qualified name of the calling logger sub-class/wrapper.
+     * Provided for compatibility with log4j 1.3
+     * @return fully qualified class name, may be null.
+     * @since 1.2.15
+     */
+    public String getFQNOfLoggerClass() {
+      return fqnOfCategoryClass;
+    }
+
+
+    /**
+     * This removes the specified MDC property from the event.
+     * Access to the MDC is not synchronized, so this
+     * method should only be called when it is known that
+     * no other threads are accessing the MDC.
+     * @param propName the property name to remove
+     * @since 1.2.16
+     */
+    public Object removeProperty(String propName) {
+        if (mdcCopy == null) {
+            getMDCCopy();
+        }
+        if (mdcCopy == null) {
+            mdcCopy = new Hashtable();
+        }
+        return mdcCopy.remove(propName);
+    }
+}
diff --git a/srcjar/org/apache/log4j/spi/NOPLogger.java b/srcjar/org/apache/log4j/spi/NOPLogger.java
new file mode 100644 (file)
index 0000000..38b1514
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Priority;
+
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+import java.util.Vector;
+
+/**
+ * No-operation implementation of Logger used by NOPLoggerRepository.
+ * @since 1.2.15
+ */
+public final class NOPLogger extends Logger {
+    /**
+     * Create instance of Logger.
+     * @param repo repository, may not be null.
+     * @param name name, may not be null, use "root" for root logger.
+     */
+    public NOPLogger(NOPLoggerRepository repo, final String name) {
+        super(name);
+        this.repository = repo;
+        this.level = Level.OFF;
+        this.parent = this;
+    }
+
+    /** {@inheritDoc} */
+    public void addAppender(final Appender newAppender) {
+    }
+
+    /** {@inheritDoc} */
+    public void assertLog(final boolean assertion, final String msg) {
+    }
+
+
+    /** {@inheritDoc} */
+    public void callAppenders(final LoggingEvent event) {
+    }
+
+    /** {@inheritDoc} */
+    void closeNestedAppenders() {
+    }
+
+    /** {@inheritDoc} */
+    public void debug(final Object message) {
+    }
+
+
+    /** {@inheritDoc} */
+    public void debug(final Object message, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void error(final Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void error(final Object message, final Throwable t) {
+    }
+
+
+    /** {@inheritDoc} */
+    public void fatal(final Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void fatal(final Object message, final Throwable t) {
+    }
+
+
+    /** {@inheritDoc} */
+    public Enumeration getAllAppenders() {
+      return new Vector().elements();
+    }
+
+    /** {@inheritDoc} */
+    public Appender getAppender(final String name) {
+       return null;
+    }
+
+    /** {@inheritDoc} */
+    public Level getEffectiveLevel() {
+      return Level.OFF;
+    }
+
+    /** {@inheritDoc} */
+    public Priority getChainedPriority() {
+      return getEffectiveLevel();
+    }
+
+    /** {@inheritDoc} */
+    public ResourceBundle getResourceBundle() {
+      return null;
+    }
+
+
+    /** {@inheritDoc} */
+    public void info(final Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void info(final Object message, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public boolean isAttached(Appender appender) {
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isDebugEnabled() {
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isEnabledFor(final Priority level) {
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isInfoEnabled() {
+      return false;
+    }
+
+
+    /** {@inheritDoc} */
+    public void l7dlog(final Priority priority, final String key, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void l7dlog(final Priority priority, final String key,  final Object[] params, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void log(final Priority priority, final Object message, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void log(final Priority priority, final Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void log(final String callerFQCN, final Priority level, final Object message, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void removeAllAppenders() {
+    }
+
+
+    /** {@inheritDoc} */
+    public void removeAppender(Appender appender) {
+    }
+
+    /** {@inheritDoc} */
+    public void removeAppender(final String name) {
+    }
+
+    /** {@inheritDoc} */
+    public void setLevel(final Level level) {
+    }
+
+
+    /** {@inheritDoc} */
+    public void setPriority(final Priority priority) {
+    }
+
+    /** {@inheritDoc} */
+    public void setResourceBundle(final ResourceBundle bundle) {
+    }
+
+    /** {@inheritDoc} */
+    public void warn(final Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void warn(final Object message, final Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public void trace(Object message) {
+    }
+
+    /** {@inheritDoc} */
+    public void trace(Object message, Throwable t) {
+    }
+
+    /** {@inheritDoc} */
+    public boolean isTraceEnabled() {
+        return false;
+    }
+
+
+}
diff --git a/srcjar/org/apache/log4j/spi/NOPLoggerRepository.java b/srcjar/org/apache/log4j/spi/NOPLoggerRepository.java
new file mode 100644 (file)
index 0000000..bdb6ed9
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Category;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Appender;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ *  No-operation implementation of LoggerRepository which is used when
+ *  LogManager.repositorySelector is erroneously nulled during class reloading.
+ *  @since 1.2.15
+ */
+public final class NOPLoggerRepository implements LoggerRepository {
+    /**
+     * {@inheritDoc}
+    */
+    public void addHierarchyEventListener(final HierarchyEventListener listener) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isDisabled(final int level) {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setThreshold(final Level level) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setThreshold(final String val) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void emitNoAppenderWarning(final Category cat) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Level getThreshold() {
+        return Level.OFF;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Logger getLogger(final String name) {
+        return new NOPLogger(this, name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Logger getLogger(final String name, final LoggerFactory factory) {
+        return new NOPLogger(this, name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Logger getRootLogger() {
+        return new NOPLogger(this, "root");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Logger exists(final String name) {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void shutdown() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration getCurrentLoggers() {
+        return new Vector().elements();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration getCurrentCategories() {
+        return getCurrentLoggers();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public  void fireAddAppenderEvent(Category logger, Appender appender) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void resetConfiguration() {
+    }
+}
diff --git a/srcjar/org/apache/log4j/spi/NullWriter.java b/srcjar/org/apache/log4j/spi/NullWriter.java
new file mode 100644 (file)
index 0000000..a578910
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+import java.io.Writer;
+
+/**
+  * NullWriter is an obsolete class provided only for
+  *  binary compatibility with earlier versions of log4j and should not be used.
+  *
+  * @deprecated
+  */
+class NullWriter extends Writer {
+
+  public void close() {
+    // blank
+  }
+
+  public void flush() {
+    // blank
+  }
+
+  public void write(char[] cbuf, int off, int len) {
+    // blank
+  }
+}
diff --git a/srcjar/org/apache/log4j/spi/OptionHandler.java b/srcjar/org/apache/log4j/spi/OptionHandler.java
new file mode 100644 (file)
index 0000000..2c90226
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+/**
+   A string based interface to configure package components.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Anders Kristensen
+   @since 0.8.1
+ */
+public interface OptionHandler {
+
+  /**
+     Activate the options that were previously set with calls to option
+     setters.
+
+     <p>This allows to defer activiation of the options until all
+     options have been set. This is required for components which have
+     related options that remain ambigous until all are set.
+
+     <p>For example, the FileAppender has the {@link
+     org.apache.log4j.FileAppender#setFile File} and {@link
+     org.apache.log4j.FileAppender#setAppend Append} options both of
+     which are ambigous until the other is also set.  */
+  void activateOptions();
+
+  /**
+     Return list of strings that the OptionHandler instance recognizes.
+
+     @deprecated We now use JavaBeans style getters/setters.
+   */
+  //  String[] getOptionStrings();
+
+  /**
+     Set <code>option</code> to <code>value</code>.
+
+     <p>The handling of each option depends on the OptionHandler
+     instance. Some options may become active immediately whereas
+     other may be activated only when {@link #activateOptions} is
+     called.
+
+     @deprecated We now use JavaBeans style getters/setters.
+  */
+  //void setOption(String option, String value);
+}
diff --git a/srcjar/org/apache/log4j/spi/RendererSupport.java b/srcjar/org/apache/log4j/spi/RendererSupport.java
new file mode 100644 (file)
index 0000000..9d69faa
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererMap;
+
+
+public interface RendererSupport {
+
+  public
+  RendererMap getRendererMap();
+
+  public
+  void setRenderer(Class renderedClass, ObjectRenderer renderer);
+
+}
diff --git a/srcjar/org/apache/log4j/spi/RepositorySelector.java b/srcjar/org/apache/log4j/spi/RepositorySelector.java
new file mode 100644 (file)
index 0000000..9a70d62
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.log4j.spi;
+
+
+/**
+
+   The <code>LogManager</code> uses one (and only one)
+   <code>RepositorySelector</code> implementation to select the
+   {@link LoggerRepository} for a particular application context.
+
+   <p>It is the responsability of the <code>RepositorySelector</code>
+   implementation to track the application context. Log4j makes no
+   assumptions about the application context or on its management.
+
+   <p>See also {@link org.apache.log4j.LogManager LogManager}.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2
+
+ */
+public interface RepositorySelector {
+
+  /**
+     Returns a {@link LoggerRepository} depending on the
+     context. Implementors must make sure that a valid (non-null)
+     LoggerRepository is returned.
+  */
+  public
+  LoggerRepository getLoggerRepository();
+}
+
diff --git a/srcjar/org/apache/log4j/spi/RootCategory.java b/srcjar/org/apache/log4j/spi/RootCategory.java
new file mode 100644 (file)
index 0000000..9954791
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.LogLog;
+
+// Contibutors: Mathias Bogaert
+
+/**
+ * @deprecated Replaced by {@link RootLogger}.  
+ */
+final public class RootCategory extends Logger {
+
+  /**
+     The root category names itself as "root". However, the root
+     category cannot be retrieved by name.  
+  */
+  public
+  RootCategory(Level level) {
+    super("root");
+    setLevel(level);
+  }
+
+  
+  /**
+     Return the assigned level value without walking the category
+     hierarchy.
+  */
+  final
+  public 
+  Level getChainedLevel() {
+    return level;
+  }
+
+  /**
+     Setting a null value to the level of the root category may have catastrophic
+     results. We prevent this here.
+
+     @since 0.8.3 */
+  final  
+  public
+  void setLevel(Level level) {
+    if(level == null) {
+      LogLog.error("You have tried to set a null level to root.",
+                  new Throwable());
+    }
+    else {
+      this.level = level;
+    }
+  }
+
+  final  
+  public
+  void setPriority(Level level) {
+    setLevel(level);
+  }
+
+  
+}
diff --git a/srcjar/org/apache/log4j/spi/RootLogger.java b/srcjar/org/apache/log4j/spi/RootLogger.java
new file mode 100644 (file)
index 0000000..a9ffd74
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.LogLog;
+
+
+// Contibutors: Mathias Bogaert
+
+/**
+   RootLogger sits at the top of the logger hierachy. It is a
+   regular logger except that it provides several guarantees.
+
+   <p>First, it cannot be assigned a <code>null</code>
+   level. Second, since root logger cannot have a parent, the
+   {@link #getChainedLevel} method always returns the value of the
+   level field without walking the hierarchy.
+
+   @author Ceki G&uuml;lc&uuml;
+
+ */
+public final class RootLogger extends Logger {
+  /**
+     The root logger names itself as "root". However, the root
+     logger cannot be retrieved by name.
+  */
+  public RootLogger(Level level) {
+    super("root");
+    setLevel(level);
+  }
+
+  /**
+     Return the assigned level value without walking the logger
+     hierarchy.
+  */
+  public final Level getChainedLevel() {
+    return level;
+  }
+
+  /**
+     Setting a null value to the level of the root logger may have catastrophic
+     results. We prevent this here.
+
+     @since 0.8.3 */
+  public final void setLevel(Level level) {
+    if (level == null) {
+      LogLog.error(
+        "You have tried to set a null level to root.", new Throwable());
+    } else {
+      this.level = level;
+    }
+  }
+
+}
diff --git a/srcjar/org/apache/log4j/spi/ThrowableInformation.java b/srcjar/org/apache/log4j/spi/ThrowableInformation.java
new file mode 100644 (file)
index 0000000..033f18b
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.DefaultThrowableRenderer;
+
+/**
+  * ThrowableInformation is log4j's internal representation of
+  * throwables. It essentially consists of a string array, called
+  * 'rep', where the first element, that is rep[0], represents the
+  * string representation of the throwable (i.e. the value you get
+  * when you do throwable.toString()) and subsequent elements
+  * correspond the stack trace with the top most entry of the stack
+  * corresponding to the second entry of the 'rep' array that is
+  * rep[1].
+  *
+  * @author Ceki G&uuml;lc&uuml;
+  *
+  * */
+public class ThrowableInformation implements java.io.Serializable {
+
+  static final long serialVersionUID = -4748765566864322735L;
+
+  private transient Throwable throwable;
+  private transient Category category;
+  private String[] rep;
+
+  public
+  ThrowableInformation(Throwable throwable) {
+    this.throwable = throwable;
+  }
+
+    /**
+     * Create a new instance.
+     * @param throwable throwable, may not be null.
+     * @param category category used to obtain ThrowableRenderer, may be null.
+     * @since 1.2.16
+     */
+  public ThrowableInformation(Throwable throwable, Category category) {
+      this.throwable = throwable;
+      this.category = category;
+  }
+
+    /**
+     * Create new instance.
+     * @since 1.2.15
+     * @param r String representation of throwable.
+     */
+  public ThrowableInformation(final String[] r) {
+      if (r != null) {
+        rep = (String[]) r.clone();
+      }
+  }
+
+
+  public
+  Throwable getThrowable() {
+    return throwable;
+  }
+
+  public synchronized String[] getThrowableStrRep() {
+    if(rep == null) {
+      ThrowableRenderer renderer = null;
+      if (category != null) {
+          LoggerRepository repo = category.getLoggerRepository();
+          if (repo instanceof ThrowableRendererSupport) {
+              renderer = ((ThrowableRendererSupport) repo).getThrowableRenderer();
+          }
+      }
+      if (renderer == null) {
+          rep = DefaultThrowableRenderer.render(throwable);
+      } else {
+          rep = renderer.doRender(throwable);
+      }
+    }
+    return (String[]) rep.clone();
+  }
+}
+
+
diff --git a/srcjar/org/apache/log4j/spi/ThrowableRenderer.java b/srcjar/org/apache/log4j/spi/ThrowableRenderer.java
new file mode 100644 (file)
index 0000000..1b83db2
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+/**
+ * Implemented by classes that render instances of
+ * java.lang.Throwable (exceptions and errors)
+ * into a string representation.
+ *
+ * @since 1.2.16
+ */
+public interface ThrowableRenderer {
+    /**
+     * Render Throwable.
+     * @param t throwable, may not be null.
+     * @return String representation.
+     */
+    public String[] doRender(Throwable t);
+}
diff --git a/srcjar/org/apache/log4j/spi/ThrowableRendererSupport.java b/srcjar/org/apache/log4j/spi/ThrowableRendererSupport.java
new file mode 100644 (file)
index 0000000..08f02a0
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+/**
+ * Implemented by logger repositories that support configurable
+ * rendering of Throwables.
+ *
+ * @since 1.2.16
+ */
+public interface ThrowableRendererSupport {
+    /**
+     * Get throwable renderer.
+     * @return throwable renderer, may be null.
+     */
+    ThrowableRenderer getThrowableRenderer();
+
+    /**
+     * Set throwable renderer.
+     * @param renderer renderer, may be null.
+     */
+    void setThrowableRenderer(ThrowableRenderer renderer);
+}
diff --git a/srcjar/org/apache/log4j/spi/TriggeringEventEvaluator.java b/srcjar/org/apache/log4j/spi/TriggeringEventEvaluator.java
new file mode 100644 (file)
index 0000000..67f4fcd
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+/**
+   
+   Implementions of this interface allow certain appenders to decide
+   when to perform an appender specific action.
+
+  <p>For example the {@link org.apache.log4j.net.SMTPAppender} sends
+  an email when the {@link #isTriggeringEvent} method returns
+  <code>true</code> and adds the event to an internal buffer when the
+  returned result is <code>false</code>.
+
+  @author Ceki G&uuml;lc&uuml;
+  @since version 1.0
+   
+ */
+public interface TriggeringEventEvaluator {
+  
+  /**
+     Is this the triggering event?
+   */
+  public boolean isTriggeringEvent(LoggingEvent event);
+}
diff --git a/srcjar/org/apache/log4j/spi/VectorWriter.java b/srcjar/org/apache/log4j/spi/VectorWriter.java
new file mode 100644 (file)
index 0000000..3e28860
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import java.io.PrintWriter;
+import java.util.Vector;
+
+/**
+  * VectorWriter is an obsolete class provided only for
+  *  binary compatibility with earlier versions of log4j and should not be used.
+  *
+  * @deprecated
+  */
+class VectorWriter extends PrintWriter {
+
+  private Vector v;
+
+    /**
+     * @deprecated
+     */
+  VectorWriter() {
+    super(new NullWriter());
+    v = new Vector();
+  }
+
+  public void print(Object o) {
+    v.addElement(String.valueOf(o));
+  }
+
+  public void print(char[] chars) {
+    v.addElement(new String(chars));
+  }
+
+  public void print(String s) {
+    v.addElement(s);
+  }
+
+  public void println(Object o) {
+    v.addElement(String.valueOf(o));
+  }
+
+  // JDK 1.1.x apprenly uses this form of println while in
+  // printStackTrace()
+  public
+  void println(char[] chars) {
+    v.addElement(new String(chars));
+  }
+
+  public
+  void println(String s) {
+    v.addElement(s);
+  }
+
+  public void write(char[] chars) {
+    v.addElement(new String(chars));
+  }
+
+  public void write(char[] chars, int off, int len) {
+    v.addElement(new String(chars, off, len));
+  }
+
+  public void write(String s, int off, int len) {
+    v.addElement(s.substring(off, off+len));
+  }
+
+  public void write(String s) {
+     v.addElement(s);
+  }
+
+  public String[] toStringArray() {
+    int len = v.size();
+    String[] sa = new String[len];
+    for(int i = 0; i < len; i++) {
+      sa[i] = (String) v.elementAt(i);
+    }
+    return sa;
+  }
+
+}
+
diff --git a/srcjar/org/apache/log4j/spi/package.html b/srcjar/org/apache/log4j/spi/package.html
new file mode 100644 (file)
index 0000000..1205833
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html>
+<head>
+</head>
+
+<body>
+
+Contains part of the System Programming Interface (SPI) needed to
+extend log4j.
+
+
+</body>
+</html>
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/varia/DenyAllFilter.java b/srcjar/org/apache/log4j/varia/DenyAllFilter.java
new file mode 100644 (file)
index 0000000..6c9e949
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+   This filter drops all logging events. 
+
+   <p>You can add this filter to the end of a filter chain to
+   switch from the default "accept all unless instructed otherwise"
+   filtering behaviour to a "deny all unless instructed otherwise"
+   behaviour.
+
+
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.9.0 */
+public class DenyAllFilter extends Filter {
+
+  /**
+     Returns <code>null</code> as there are no options.
+     
+     @deprecated We now use JavaBeans introspection to configure
+     components. Options strings are no longer needed.
+  */
+  public
+  String[] getOptionStrings() {
+    return null;
+  }
+
+  
+  /**
+     No options to set.
+     
+     @deprecated Use the setter method for the option directly instead
+     of the generic <code>setOption</code> method. 
+  */
+  public
+  void setOption(String key, String value) {
+  }
+  
+  /**
+     Always returns the integer constant {@link Filter#DENY}
+     regardless of the {@link LoggingEvent} parameter.
+
+     @param event The LoggingEvent to filter.
+     @return Always returns {@link Filter#DENY}.
+  */
+  public
+  int decide(LoggingEvent event) {
+    return Filter.DENY;
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/varia/ExternallyRolledFileAppender.java b/srcjar/org/apache/log4j/varia/ExternallyRolledFileAppender.java
new file mode 100644 (file)
index 0000000..26e7d84
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.apache.log4j.RollingFileAppender;
+import org.apache.log4j.helpers.LogLog;
+
+/**
+   This appender listens on a socket on the port specified by the
+   <b>Port</b> property for a "RollOver" message. When such a message
+   is received, the underlying log file is rolled over and an
+   acknowledgment message is sent back to the process initiating the
+   roll over.
+
+   <p>This method of triggering roll over has the advantage of being
+   operating system independent, fast and reliable.
+
+   <p>A simple application {@link Roller} is provided to initiate the
+   roll over.
+
+   <p>Note that the initiator is not authenticated. Anyone can trigger
+   a rollover. In production environments, it is recommended that you
+   add some form of protection to prevent undesired rollovers.
+
+
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.9.0 */
+public class ExternallyRolledFileAppender extends RollingFileAppender {
+
+  /**
+     The string constant sent to initiate a roll over.   Current value of
+     this string constant is <b>RollOver</b>.
+  */
+  static final public String ROLL_OVER = "RollOver";
+
+  /**
+     The string constant sent to acknowledge a roll over.   Current value of
+      this string constant is <b>OK</b>.
+  */
+  static final public String OK = "OK";
+
+  int port = 0;
+  HUP hup;
+
+  /**
+     The default constructor does nothing but calls its super-class
+     constructor.  */
+  public
+  ExternallyRolledFileAppender() {
+  }
+
+  /**
+     The <b>Port</b> [roperty is used for setting the port for
+     listening to external roll over messages.
+  */
+  public
+  void setPort(int port) {
+    this.port = port;
+  }
+
+  /**
+     Returns value of the <b>Port</b> option.
+   */
+  public
+  int getPort() {
+    return port;
+  }
+
+  /**
+     Start listening on the port specified by a preceding call to
+     {@link #setPort}.  */
+  public
+  void activateOptions() {
+    super.activateOptions();
+    if(port != 0) {
+      if(hup != null) {
+       hup.interrupt();
+      }
+      hup = new HUP(this, port);
+      hup.setDaemon(true);
+      hup.start();
+    }
+  }
+}
+
+
+class HUP extends Thread {
+
+  int port;
+  ExternallyRolledFileAppender er;
+
+  HUP(ExternallyRolledFileAppender er, int port) {
+    this.er = er;
+    this.port = port;
+  }
+
+  public
+  void run() {
+    while(!isInterrupted()) {
+      try {
+       ServerSocket serverSocket = new ServerSocket(port);
+       while(true) {
+         Socket socket = serverSocket.accept();
+         LogLog.debug("Connected to client at " + socket.getInetAddress());
+         new Thread(new HUPNode(socket, er), "ExternallyRolledFileAppender-HUP").start();
+       }
+      } catch(InterruptedIOException e) {
+        Thread.currentThread().interrupt();
+           e.printStackTrace();
+      } catch(IOException e) {
+           e.printStackTrace();
+      } catch(RuntimeException e) {
+           e.printStackTrace();
+      }
+    }
+  }
+}
+
+class HUPNode implements Runnable {
+
+  Socket socket;
+  DataInputStream dis;
+  DataOutputStream dos;
+  ExternallyRolledFileAppender er;
+
+  public
+  HUPNode(Socket socket, ExternallyRolledFileAppender er) {
+    this.socket = socket;
+    this.er = er;
+    try {
+      dis = new DataInputStream(socket.getInputStream());
+      dos = new DataOutputStream(socket.getOutputStream());
+    } catch(InterruptedIOException e) {
+      Thread.currentThread().interrupt();
+      e.printStackTrace();
+    } catch(IOException e) {
+      e.printStackTrace();
+    } catch(RuntimeException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public void run() {
+    try {
+      String line = dis.readUTF();
+      LogLog.debug("Got external roll over signal.");
+      if(ExternallyRolledFileAppender.ROLL_OVER.equals(line)) {
+       synchronized(er) {
+         er.rollOver();
+       }
+       dos.writeUTF(ExternallyRolledFileAppender.OK);
+      }
+      else {
+       dos.writeUTF("Expecting [RollOver] string.");
+      }
+      dos.close();
+    } catch(InterruptedIOException e) {
+      Thread.currentThread().interrupt();
+      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
+    } catch(IOException e) {
+      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
+    } catch(RuntimeException e) {
+      LogLog.error("Unexpected exception. Exiting HUPNode.", e);
+    }
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/varia/FallbackErrorHandler.java b/srcjar/org/apache/log4j/varia/FallbackErrorHandler.java
new file mode 100644 (file)
index 0000000..7cbc87d
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.varia;
+
+import  org.apache.log4j.spi.ErrorHandler;
+import  org.apache.log4j.spi.LoggingEvent;
+import  org.apache.log4j.Appender;
+import  org.apache.log4j.Logger;
+import  org.apache.log4j.helpers.LogLog;
+import java.util.Vector;
+import java.io.InterruptedIOException;
+
+/**
+  *
+  * The <code>FallbackErrorHandler</code> implements the ErrorHandler
+  * interface such that a secondary appender may be specified.  This
+  * secondary appender takes over if the primary appender fails for
+  * whatever reason.
+  *
+  * <p>The error message is printed on <code>System.err</code>, and
+  * logged in the new secondary appender.
+  *
+  * @author Ceki G&uuml;c&uuml;
+  * */
+public class FallbackErrorHandler implements ErrorHandler {
+
+
+  Appender backup;
+  Appender primary;
+  Vector loggers;
+
+  public FallbackErrorHandler() {
+  }
+  
+
+  /**
+     <em>Adds</em> the logger passed as parameter to the list of
+     loggers that we need to search for in case of appender failure.
+  */
+  public 
+  void setLogger(Logger logger) {
+    LogLog.debug("FB: Adding logger [" + logger.getName() + "].");
+    if(loggers == null) {
+      loggers = new Vector();
+    }
+    loggers.addElement(logger);
+  }
+
+
+  /**
+     No options to activate.
+  */
+  public 
+  void activateOptions() {
+  }
+
+
+  /**
+     Prints the message and the stack trace of the exception on
+     <code>System.err</code>.  */
+  public
+  void error(String message, Exception e, int errorCode) { 
+    error(message, e, errorCode, null);
+  }
+
+  /**
+     Prints the message and the stack trace of the exception on
+     <code>System.err</code>.
+   */
+  public
+  void error(String message, Exception e, int errorCode, LoggingEvent event) {
+    if (e instanceof InterruptedIOException) {
+        Thread.currentThread().interrupt();
+    }
+    LogLog.debug("FB: The following error reported: " + message, e);
+    LogLog.debug("FB: INITIATING FALLBACK PROCEDURE.");
+    if (loggers != null) {
+       for(int i = 0; i < loggers.size(); i++) {
+               Logger l = (Logger) loggers.elementAt(i);
+               LogLog.debug("FB: Searching for ["+primary.getName()+"] in logger ["
+                               +l.getName() + "].");
+               LogLog.debug("FB: Replacing ["+primary.getName()+"] by ["
+                               + backup.getName() + "] in logger ["+ l.getName() +"].");
+               l.removeAppender(primary);
+               LogLog.debug("FB: Adding appender ["+backup.getName()+"] to logger "
+                               +  l.getName());
+               l.addAppender(backup);
+        }
+    }    
+  }
+
+
+  /**
+     Print a the error message passed as parameter on
+     <code>System.err</code>.  
+  */
+  public 
+  void error(String message) {
+    //if(firstTime) {
+    //LogLog.error(message);
+    //firstTime = false;
+    //}
+  }
+  
+  /**
+     The appender to which this error handler is attached.
+   */
+  public
+  void setAppender(Appender primary) {
+    LogLog.debug("FB: Setting primary appender to [" + primary.getName() + "].");
+    this.primary = primary;
+  }
+
+  /**
+     Set the backup appender.
+   */
+  public
+  void setBackupAppender(Appender backup) {
+    LogLog.debug("FB: Setting backup appender to [" + backup.getName() + "].");
+    this.backup = backup;
+  }
+  
+}
diff --git a/srcjar/org/apache/log4j/varia/LevelMatchFilter.java b/srcjar/org/apache/log4j/varia/LevelMatchFilter.java
new file mode 100644 (file)
index 0000000..f5b3788
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.OptionConverter;
+
+/**
+   This is a very simple filter based on level matching.
+
+   <p>The filter admits two options <b>LevelToMatch</b> and
+   <b>AcceptOnMatch</b>. If there is an exact match between the value
+   of the <b>LevelToMatch</b> option and the level of the {@link
+   LoggingEvent}, then the {@link #decide} method returns {@link
+   Filter#ACCEPT} in case the <b>AcceptOnMatch</b> option value is set
+   to <code>true</code>, if it is <code>false</code> then {@link
+   Filter#DENY} is returned. If there is no match, {@link
+   Filter#NEUTRAL} is returned.
+
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 1.2 */
+public class LevelMatchFilter extends Filter {
+  
+  /**
+     Do we return ACCEPT when a match occurs. Default is
+     <code>true</code>.  */
+  boolean acceptOnMatch = true;
+
+  /**
+   */
+  Level levelToMatch;
+
+  public
+  void setLevelToMatch(String level) {
+    levelToMatch = OptionConverter.toLevel(level, null);
+  }
+  
+  public
+  String getLevelToMatch() {
+    return levelToMatch == null ? null : levelToMatch.toString();
+  }
+  
+  public
+  void setAcceptOnMatch(boolean acceptOnMatch) {
+    this.acceptOnMatch = acceptOnMatch;
+  }
+  
+  public
+  boolean getAcceptOnMatch() {
+    return acceptOnMatch;
+  }
+  
+
+  /**
+     Return the decision of this filter.
+
+     Returns {@link Filter#NEUTRAL} if the <b>LevelToMatch</b> option
+     is not set or if there is not match.  Otherwise, if there is a
+     match, then the returned decision is {@link Filter#ACCEPT} if the
+     <b>AcceptOnMatch</b> property is set to <code>true</code>. The
+     returned decision is {@link Filter#DENY} if the
+     <b>AcceptOnMatch</b> property is set to false.
+
+  */
+  public
+  int decide(LoggingEvent event) {
+    if(this.levelToMatch == null) {
+      return Filter.NEUTRAL;
+    }
+    
+    boolean matchOccured = false;
+    if(this.levelToMatch.equals(event.getLevel())) {
+      matchOccured = true;
+    } 
+
+    if(matchOccured) {  
+      if(this.acceptOnMatch) {
+        return Filter.ACCEPT;
+    } else {
+        return Filter.DENY;
+    }
+    } else {
+      return Filter.NEUTRAL;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/varia/LevelRangeFilter.java b/srcjar/org/apache/log4j/varia/LevelRangeFilter.java
new file mode 100644 (file)
index 0000000..9dfacb1
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   This is a very simple filter based on level matching, which can be
+   used to reject messages with priorities outside a certain range.
+   
+   <p>The filter admits three options <b>LevelMin</b>, <b>LevelMax</b>
+   and <b>AcceptOnMatch</b>.
+
+   <p>If the level of the {@link LoggingEvent} is not between Min and Max
+   (inclusive), then {@link Filter#DENY} is returned.
+   
+   <p> If the Logging event level is within the specified range, then if
+   <b>AcceptOnMatch</b> is true, {@link Filter#ACCEPT} is returned, and if
+   <b>AcceptOnMatch</b> is false, {@link Filter#NEUTRAL} is returned.
+   
+   <p>If <code>LevelMin</code>w is not defined, then there is no
+   minimum acceptable level (ie a level is never rejected for
+   being too "low"/unimportant).  If <code>LevelMax</code> is not
+   defined, then there is no maximum acceptable level (ie a
+   level is never rejected for beeing too "high"/important).
+
+   <p>Refer to the {@link
+   org.apache.log4j.AppenderSkeleton#setThreshold setThreshold} method
+   available to <code>all</code> appenders extending {@link
+   org.apache.log4j.AppenderSkeleton} for a more convenient way to
+   filter out events by level.
+
+   @author Simon Kitching
+   @author based on code by Ceki G&uuml;lc&uuml; 
+*/
+public class LevelRangeFilter extends Filter {
+
+  /**
+     Do we return ACCEPT when a match occurs. Default is
+     <code>false</code>, so that later filters get run by default  */
+  boolean acceptOnMatch = false;
+
+  Level levelMin;
+  Level levelMax;
+
+  /**
+     Return the decision of this filter.
+   */
+  public
+  int decide(LoggingEvent event) {
+    if(this.levelMin != null) {
+      if (event.getLevel().isGreaterOrEqual(levelMin) == false) {
+        // level of event is less than minimum
+        return Filter.DENY;
+      }
+    }
+
+    if(this.levelMax != null) {
+      if (event.getLevel().toInt() > levelMax.toInt()) {
+        // level of event is greater than maximum
+        // Alas, there is no Level.isGreater method. and using
+        // a combo of isGreaterOrEqual && !Equal seems worse than
+        // checking the int values of the level objects..
+        return Filter.DENY;
+      }
+    }
+
+    if (acceptOnMatch) {
+      // this filter set up to bypass later filters and always return
+      // accept if level in range
+      return Filter.ACCEPT;
+    }
+    else {
+      // event is ok for this filter; allow later filters to have a look..
+      return Filter.NEUTRAL;
+    }
+  }
+
+ /**
+     Get the value of the <code>LevelMax</code> option.  */
+  public
+  Level getLevelMax() {
+    return levelMax;
+  }
+
+
+  /**
+     Get the value of the <code>LevelMin</code> option.  */
+  public
+  Level getLevelMin() {
+    return levelMin;
+  }
+
+  /**
+     Get the value of the <code>AcceptOnMatch</code> option.
+   */
+  public
+  boolean getAcceptOnMatch() {
+    return acceptOnMatch;
+  }
+
+  /**
+     Set the <code>LevelMax</code> option.
+   */
+  public
+  void setLevelMax(Level levelMax) {
+    this.levelMax =  levelMax;
+  }
+
+  /**
+     Set the <code>LevelMin</code> option.
+   */
+  public
+  void setLevelMin(Level levelMin) {
+    this.levelMin =  levelMin;
+  }
+
+  /**
+     Set the <code>AcceptOnMatch</code> option.
+   */  
+  public 
+  void setAcceptOnMatch(boolean acceptOnMatch) {
+    this.acceptOnMatch = acceptOnMatch;
+  }
+}
+
diff --git a/srcjar/org/apache/log4j/varia/NullAppender.java b/srcjar/org/apache/log4j/varia/NullAppender.java
new file mode 100644 (file)
index 0000000..778b024
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+  * A NullAppender merely exists, it never outputs a message to any
+  * device.  
+  * @author Ceki G&uuml;lc&uml;
+  */
+public class NullAppender extends AppenderSkeleton {
+
+  private static NullAppender instance = new NullAppender();
+
+  public NullAppender() {
+  }
+
+  /** 
+   * There are no options to acticate.
+   * */
+  public void activateOptions() {
+  }
+
+  /**
+   * Whenever you can, use this method to retreive an instance instead
+   * of instantiating a new one with <code>new</code>.
+   * @deprecated Use getNullAppender instead.  getInstance should have been static.
+   * */
+  public NullAppender getInstance() {
+    return instance;
+  }
+
+    /**
+     * Whenever you can, use this method to retreive an instance instead
+     * of instantiating a new one with <code>new</code>.
+     * */
+  public static NullAppender getNullAppender() {
+      return instance;
+  }
+
+  public void close() {
+  }
+
+  /**
+   * Does not do anything. 
+   * */
+  public void doAppend(LoggingEvent event) {
+  }
+
+  /**
+   * Does not do anything. 
+   * */
+  protected void append(LoggingEvent event) {
+  }
+
+  /**
+    * NullAppenders do not need a layout.  
+    * */
+  public boolean requiresLayout() {
+    return false;
+  }
+}
diff --git a/srcjar/org/apache/log4j/varia/ReloadingPropertyConfigurator.java b/srcjar/org/apache/log4j/varia/ReloadingPropertyConfigurator.java
new file mode 100644 (file)
index 0000000..ac76577
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.LoggerRepository;
+
+public class ReloadingPropertyConfigurator implements Configurator {
+
+    PropertyConfigurator delegate = new PropertyConfigurator();
+
+    public ReloadingPropertyConfigurator() {
+    }
+
+   /**
+    * @since 1.2.17
+    */
+    public void doConfigure(InputStream inputStream, LoggerRepository repository) {
+    }
+
+    public void doConfigure(URL url, LoggerRepository repository) {
+    }
+
+}
diff --git a/srcjar/org/apache/log4j/varia/Roller.java b/srcjar/org/apache/log4j/varia/Roller.java
new file mode 100644 (file)
index 0000000..4fdf896
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.BasicConfigurator;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.net.Socket;
+
+/**
+   A simple application to send roll over messages to a potentially
+   remote {@link ExternallyRolledFileAppender}. 
+
+   <p>It takes two arguments, the <code>host_name</code> and
+   <code>port_number</code> where the
+   <code>ExternallyRolledFileAppender</code> is listening.
+   
+
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.9.0 */
+public class Roller {
+
+  static Logger cat = Logger.getLogger(Roller.class);
+  
+
+  static String host;
+  static int port;
+
+  // Static class.
+  Roller() {
+  }
+
+  /**
+     Send a "RollOver" message to
+     <code>ExternallyRolledFileAppender</code> on <code>host</code>
+     and <code>port</code>.
+
+   */
+  public 
+  static 
+  void main(String argv[]) {
+
+    BasicConfigurator.configure();
+
+    if(argv.length == 2) {
+        init(argv[0], argv[1]);
+    } else {
+        usage("Wrong number of arguments.");
+    }
+    
+    roll();
+  }
+
+  static
+  void usage(String msg) {
+    System.err.println(msg);
+    System.err.println( "Usage: java " + Roller.class.getName() +
+                       "host_name port_number");
+    System.exit(1);
+  }
+
+  static 
+  void init(String hostArg, String portArg) {
+    host = hostArg;
+    try {
+      port =  Integer.parseInt(portArg);
+    }
+    catch(java.lang.NumberFormatException e) {
+      usage("Second argument "+portArg+" is not a valid integer.");
+    }
+  }
+
+  static
+  void roll() {
+    try {
+      Socket socket = new Socket(host, port);
+      DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
+      DataInputStream dis = new DataInputStream(socket.getInputStream());
+      dos.writeUTF(ExternallyRolledFileAppender.ROLL_OVER);
+      String rc = dis.readUTF();
+      if(ExternallyRolledFileAppender.OK.equals(rc)) {
+       cat.info("Roll over signal acknowledged by remote appender.");
+      } else {
+       cat.warn("Unexpected return code "+rc+" from remote entity.");
+       System.exit(2);
+      }
+    } catch(IOException e) {
+      cat.error("Could not send roll signal on host "+host+" port "+port+" .",
+               e);
+      System.exit(2);
+    }
+    System.exit(0);
+  }
+}
diff --git a/srcjar/org/apache/log4j/varia/StringMatchFilter.java b/srcjar/org/apache/log4j/varia/StringMatchFilter.java
new file mode 100644 (file)
index 0000000..9d161c6
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.OptionConverter;
+
+/**
+ * This is a very simple filter based on string matching.
+ *
+ * <p>The filter admits two options <b>StringToMatch</b> and
+ * <b>AcceptOnMatch</b>. If there is a match between the value of the
+ * StringToMatch option and the message of the {@link org.apache.log4j.spi.LoggingEvent},
+ * then the {@link #decide(LoggingEvent)} method returns {@link org.apache.log4j.spi.Filter#ACCEPT} if
+ * the <b>AcceptOnMatch</b> option value is true, if it is false then
+ * {@link org.apache.log4j.spi.Filter#DENY} is returned. If there is no match, {@link
+ * org.apache.log4j.spi.Filter#NEUTRAL} is returned.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @since 0.9.0 
+ */
+public class StringMatchFilter extends Filter {
+  
+  /**
+     @deprecated Options are now handled using the JavaBeans paradigm.
+     This constant is not longer needed and will be removed in the
+     <em>near</em> term.
+   */
+  public static final String STRING_TO_MATCH_OPTION = "StringToMatch";
+
+  /**
+     @deprecated Options are now handled using the JavaBeans paradigm.
+     This constant is not longer needed and will be removed in the
+     <em>near</em> term.
+   */
+  public static final String ACCEPT_ON_MATCH_OPTION = "AcceptOnMatch";
+  
+  boolean acceptOnMatch = true;
+  String stringToMatch;
+  
+  /**
+     @deprecated We now use JavaBeans introspection to configure
+     components. Options strings are no longer needed.
+  */
+  public
+  String[] getOptionStrings() {
+    return new String[] {STRING_TO_MATCH_OPTION, ACCEPT_ON_MATCH_OPTION};
+  }
+
+  /**
+     @deprecated Use the setter method for the option directly instead
+     of the generic <code>setOption</code> method. 
+  */
+  public
+  void setOption(String key, String value) { 
+    
+    if(key.equalsIgnoreCase(STRING_TO_MATCH_OPTION)) {
+      stringToMatch = value;
+    } else if (key.equalsIgnoreCase(ACCEPT_ON_MATCH_OPTION)) {
+      acceptOnMatch = OptionConverter.toBoolean(value, acceptOnMatch);
+    }
+  }
+  
+  public
+  void setStringToMatch(String s) {
+    stringToMatch = s;
+  }
+  
+  public
+  String getStringToMatch() {
+    return stringToMatch;
+  }
+  
+  public
+  void setAcceptOnMatch(boolean acceptOnMatch) {
+    this.acceptOnMatch = acceptOnMatch;
+  }
+  
+  public
+  boolean getAcceptOnMatch() {
+    return acceptOnMatch;
+  }
+
+  /**
+     Returns {@link Filter#NEUTRAL} is there is no string match.
+   */
+  public
+  int decide(LoggingEvent event) {
+    String msg = event.getRenderedMessage();
+
+    if(msg == null ||  stringToMatch == null) {
+        return Filter.NEUTRAL;
+    }
+    
+
+    if( msg.indexOf(stringToMatch) == -1 ) {
+      return Filter.NEUTRAL;
+    } else { // we've got a match
+      if(acceptOnMatch) {
+       return Filter.ACCEPT;
+      } else {
+       return Filter.DENY;
+      }
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/varia/package.html b/srcjar/org/apache/log4j/varia/package.html
new file mode 100644 (file)
index 0000000..3495db2
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+<body>
+<p>Contains various appenders, filters and other odds and ends.
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Tue Mar 21 20:28:14 MET 2000
+<!-- hhmts end -->
+</body> </html>
diff --git a/srcjar/org/apache/log4j/xml/DOMConfigurator.java b/srcjar/org/apache/log4j/xml/DOMConfigurator.java
new file mode 100644 (file)
index 0000000..d3b6644
--- /dev/null
@@ -0,0 +1,1129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.FileWatchdog;
+import org.apache.log4j.helpers.Loader;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Hashtable;
+import java.util.Properties;
+
+// Contributors:   Mark Womack
+//                 Arun Katkere 
+
+/**
+   Use this class to initialize the log4j environment using a DOM tree.
+
+   <p>The DTD is specified in <a
+   href="doc-files/log4j.dtd"><b>log4j.dtd</b></a>.
+
+   <p>Sometimes it is useful to see how log4j is reading configuration
+   files. You can enable log4j internal logging by defining the
+   <b>log4j.debug</b> variable on the java command
+   line. Alternatively, set the <code>debug</code> attribute in the
+   <code>log4j:configuration</code> element. As in
+<pre>
+   &lt;log4j:configuration <b>debug="true"</b> xmlns:log4j="http://jakarta.apache.org/log4j/">
+   ...
+   &lt;/log4j:configuration>
+</pre>
+
+   <p>There are sample XML files included in the package.
+   
+   @author Christopher Taylor
+   @author Ceki G&uuml;lc&uuml;
+   @author Anders Kristensen
+
+   @since 0.8.3 */
+public class DOMConfigurator implements Configurator {
+
+  static final String CONFIGURATION_TAG = "log4j:configuration";
+  static final String OLD_CONFIGURATION_TAG = "configuration";
+  static final String RENDERER_TAG      = "renderer";
+  private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
+  static final String APPENDER_TAG     = "appender";
+  static final String APPENDER_REF_TAG         = "appender-ref";  
+  static final String PARAM_TAG        = "param";
+  static final String LAYOUT_TAG       = "layout";
+  static final String CATEGORY         = "category";
+  static final String LOGGER           = "logger";
+  static final String LOGGER_REF       = "logger-ref";
+  static final String CATEGORY_FACTORY_TAG  = "categoryFactory";
+  static final String LOGGER_FACTORY_TAG  = "loggerFactory";
+  static final String NAME_ATTR                = "name";
+  static final String CLASS_ATTR        = "class";
+  static final String VALUE_ATTR       = "value";
+  static final String ROOT_TAG         = "root";
+  static final String ROOT_REF         = "root-ref";
+  static final String LEVEL_TAG                = "level";
+  static final String PRIORITY_TAG      = "priority";
+  static final String FILTER_TAG       = "filter";
+  static final String ERROR_HANDLER_TAG        = "errorHandler";
+  static final String REF_ATTR         = "ref";
+  static final String ADDITIVITY_ATTR    = "additivity";  
+  static final String THRESHOLD_ATTR       = "threshold";
+  static final String CONFIG_DEBUG_ATTR  = "configDebug";
+  static final String INTERNAL_DEBUG_ATTR  = "debug";
+  private static final String RESET_ATTR  = "reset";
+  static final String RENDERING_CLASS_ATTR = "renderingClass";
+  static final String RENDERED_CLASS_ATTR = "renderedClass";
+
+  static final String EMPTY_STR = "";
+  static final Class[] ONE_STRING_PARAM = new Class[] {String.class};
+
+  final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
+
+  
+  // key: appenderName, value: appender
+  Hashtable appenderBag;
+
+  Properties props;
+  LoggerRepository repository;
+
+  protected LoggerFactory catFactory = null;
+
+  /**
+     No argument constructor.
+  */
+  public
+  DOMConfigurator () { 
+    appenderBag = new Hashtable();
+  }
+
+  /**
+     Used internally to parse appenders by IDREF name.
+  */
+  protected
+  Appender findAppenderByName(Document doc, String appenderName)  {      
+    Appender appender = (Appender) appenderBag.get(appenderName);
+
+    if(appender != null) {
+      return appender;
+    } else {
+      // Doesn't work on DOM Level 1 :
+      // Element element = doc.getElementById(appenderName);
+                        
+      // Endre's hack:
+      Element element = null;
+      NodeList list = doc.getElementsByTagName("appender");
+      for (int t=0; t < list.getLength(); t++) {
+       Node node = list.item(t);
+       NamedNodeMap map= node.getAttributes();
+       Node attrNode = map.getNamedItem("name");
+       if (appenderName.equals(attrNode.getNodeValue())) {
+         element = (Element) node;
+         break;
+       }
+      }
+      // Hack finished.
+
+      if(element == null) {
+       LogLog.error("No appender named ["+appenderName+"] could be found."); 
+       return null;
+      } else {
+             appender = parseAppender(element);
+          if (appender != null) {
+            appenderBag.put(appenderName, appender);
+          }
+    return appender;
+      }
+    } 
+  }
+  /**
+     Used internally to parse appenders by IDREF element.
+   */
+  protected
+  Appender findAppenderByReference(Element appenderRef) {    
+    String appenderName = subst(appenderRef.getAttribute(REF_ATTR));    
+    Document doc = appenderRef.getOwnerDocument();
+    return findAppenderByName(doc, appenderName);
+  }
+
+    /**
+     * Delegates unrecognized content to created instance if
+     * it supports UnrecognizedElementParser.
+     * @since 1.2.15
+     * @param instance instance, may be null.
+     * @param element element, may not be null.
+     * @param props properties
+     * @throws IOException thrown if configuration of owner object
+     * should be abandoned.
+     */
+  private static void parseUnrecognizedElement(final Object instance,
+                                        final Element element,
+                                        final Properties props) throws Exception {
+      boolean recognized = false;
+      if (instance instanceof UnrecognizedElementHandler) {
+          recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
+                  element, props);
+      }
+      if (!recognized) {
+          LogLog.warn("Unrecognized element " + element.getNodeName());
+      }
+  }
+
+    /**
+      * Delegates unrecognized content to created instance if
+      * it supports UnrecognizedElementParser and catches and
+     *  logs any exception.
+      * @since 1.2.15
+      * @param instance instance, may be null.
+      * @param element element, may not be null.
+      * @param props properties
+      */
+   private static void quietParseUnrecognizedElement(final Object instance,
+                                          final Element element,
+                                          final Properties props) {
+      try {
+          parseUnrecognizedElement(instance, element, props);
+      } catch (Exception ex) {
+          if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+          LogLog.error("Error in extension content: ", ex);
+      }
+  }
+
+  /**
+     Used internally to parse an appender element.
+   */
+  protected
+  Appender parseAppender (Element appenderElement) {
+    String className = subst(appenderElement.getAttribute(CLASS_ATTR));
+    LogLog.debug("Class name: [" + className+']');    
+    try {
+      Object instance  = Loader.loadClass(className).newInstance();
+      Appender appender        = (Appender)instance;
+      PropertySetter propSetter = new PropertySetter(appender);
+
+      appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
+      
+      NodeList children        = appenderElement.getChildNodes();
+      final int length         = children.getLength();
+
+      for (int loop = 0; loop < length; loop++) {
+       Node currentNode = children.item(loop);
+
+       /* We're only interested in Elements */
+       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+         Element currentElement = (Element)currentNode;
+
+         // Parse appender parameters 
+         if (currentElement.getTagName().equals(PARAM_TAG)) {
+            setParameter(currentElement, propSetter);
+         }
+         // Set appender layout
+         else if (currentElement.getTagName().equals(LAYOUT_TAG)) {
+           appender.setLayout(parseLayout(currentElement));
+         }
+         // Add filters
+         else if (currentElement.getTagName().equals(FILTER_TAG)) {
+           parseFilters(currentElement, appender);
+         }
+         else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) {
+           parseErrorHandler(currentElement, appender);
+         }
+         else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) {
+           String refName = subst(currentElement.getAttribute(REF_ATTR));
+           if(appender instanceof AppenderAttachable) {
+             AppenderAttachable aa = (AppenderAttachable) appender;
+             LogLog.debug("Attaching appender named ["+ refName+
+                          "] to appender named ["+ appender.getName()+"].");
+             aa.addAppender(findAppenderByReference(currentElement));
+           } else {
+             LogLog.error("Requesting attachment of appender named ["+
+                          refName+ "] to appender named ["+ appender.getName()+
+                "] which does not implement org.apache.log4j.spi.AppenderAttachable.");
+           }
+         } else {
+          parseUnrecognizedElement(instance, currentElement, props);
+      }
+       }
+      }
+      propSetter.activate();
+      return appender;
+    }
+    /* Yes, it's ugly.  But all of these exceptions point to the same
+       problem: we can't create an Appender */
+    catch (Exception oops) {
+        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+      LogLog.error("Could not create an Appender. Reported error follows.",
+                  oops);
+      return null;
+    }
+  }
+
+  /**
+     Used internally to parse an {@link ErrorHandler} element.
+   */
+  protected
+  void parseErrorHandler(Element element, Appender appender) {
+    ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
+                                       subst(element.getAttribute(CLASS_ATTR)),
+                                       org.apache.log4j.spi.ErrorHandler.class, 
+                                      null);
+    
+    if(eh != null) {
+      eh.setAppender(appender);
+
+      PropertySetter propSetter = new PropertySetter(eh);
+      NodeList children = element.getChildNodes();
+      final int length         = children.getLength();
+
+      for (int loop = 0; loop < length; loop++) {
+       Node currentNode = children.item(loop);
+       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+         Element currentElement = (Element) currentNode;
+         String tagName = currentElement.getTagName();
+         if(tagName.equals(PARAM_TAG)) {
+            setParameter(currentElement, propSetter);
+         } else if(tagName.equals(APPENDER_REF_TAG)) {
+           eh.setBackupAppender(findAppenderByReference(currentElement));
+         } else if(tagName.equals(LOGGER_REF)) {
+           String loggerName = currentElement.getAttribute(REF_ATTR);      
+           Logger logger = (catFactory == null) ? repository.getLogger(loggerName)
+                : repository.getLogger(loggerName, catFactory);
+           eh.setLogger(logger);
+         } else if(tagName.equals(ROOT_REF)) {
+           Logger root = repository.getRootLogger();
+           eh.setLogger(root);
+         } else {
+          quietParseUnrecognizedElement(eh, currentElement, props);
+      }
+       }
+      }
+      propSetter.activate();
+      appender.setErrorHandler(eh);
+    }
+  }
+  
+  /**
+     Used internally to parse a filter element.
+   */
+  protected
+  void parseFilters(Element element, Appender appender) {
+    String clazz = subst(element.getAttribute(CLASS_ATTR));
+    Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz,
+                                                Filter.class, null);
+    
+    if(filter != null) {
+      PropertySetter propSetter = new PropertySetter(filter);
+      NodeList children = element.getChildNodes();
+      final int length         = children.getLength();
+
+      for (int loop = 0; loop < length; loop++) {
+       Node currentNode = children.item(loop);
+       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+         Element currentElement = (Element) currentNode;
+         String tagName = currentElement.getTagName();
+         if(tagName.equals(PARAM_TAG)) {
+            setParameter(currentElement, propSetter);
+         } else {
+            quietParseUnrecognizedElement(filter, currentElement, props);
+      }
+       }
+      }
+      propSetter.activate();
+      LogLog.debug("Adding filter of type ["+filter.getClass()
+                  +"] to appender named ["+appender.getName()+"].");
+      appender.addFilter(filter);
+    }    
+  }
+  
+  /**
+     Used internally to parse an category element.
+  */
+  protected
+  void parseCategory (Element loggerElement) {
+    // Create a new org.apache.log4j.Category object from the <category> element.
+    String catName = subst(loggerElement.getAttribute(NAME_ATTR));
+
+    Logger cat;    
+
+    String className = subst(loggerElement.getAttribute(CLASS_ATTR));
+
+
+    if(EMPTY_STR.equals(className)) {
+      LogLog.debug("Retreiving an instance of org.apache.log4j.Logger.");
+      cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory);
+    }
+    else {
+      LogLog.debug("Desired logger sub-class: ["+className+']');
+       try {    
+        Class clazz = Loader.loadClass(className);
+        Method getInstanceMethod = clazz.getMethod("getLogger", 
+                                                   ONE_STRING_PARAM);
+        cat = (Logger) getInstanceMethod.invoke(null, new Object[] {catName});
+       } catch (InvocationTargetException oops) {
+          if (oops.getTargetException() instanceof InterruptedException
+                  || oops.getTargetException() instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+          LogLog.error("Could not retrieve category ["+catName+
+                     "]. Reported error follows.", oops);
+             return;
+       } catch (Exception oops) {
+             LogLog.error("Could not retrieve category ["+catName+
+                     "]. Reported error follows.", oops);
+             return;
+       }
+    }
+
+    // Setting up a category needs to be an atomic operation, in order
+    // to protect potential log operations while category
+    // configuration is in progress.
+    synchronized(cat) {
+      boolean additivity = OptionConverter.toBoolean(
+                           subst(loggerElement.getAttribute(ADDITIVITY_ATTR)),
+                          true);
+    
+      LogLog.debug("Setting ["+cat.getName()+"] additivity to ["+additivity+"].");
+      cat.setAdditivity(additivity);
+      parseChildrenOfLoggerElement(loggerElement, cat, false);
+    }
+  }
+
+
+  /**
+     Used internally to parse the category factory element.
+  */
+  protected
+  void parseCategoryFactory(Element factoryElement) {
+    String className = subst(factoryElement.getAttribute(CLASS_ATTR));
+
+    if(EMPTY_STR.equals(className)) {
+      LogLog.error("Category Factory tag " + CLASS_ATTR + " attribute not found.");
+      LogLog.debug("No Category Factory configured.");
+    }
+    else {
+      LogLog.debug("Desired category factory: ["+className+']');
+      Object factory = OptionConverter.instantiateByClassName(className,
+                                                                 LoggerFactory.class, 
+                                                                 null);
+      if (factory instanceof LoggerFactory) {
+          catFactory = (LoggerFactory) factory;
+      } else {
+          LogLog.error("Category Factory class " + className + " does not implement org.apache.log4j.LoggerFactory");
+      }
+      PropertySetter propSetter = new PropertySetter(factory);
+
+      Element  currentElement = null;
+      Node     currentNode    = null;
+      NodeList children       = factoryElement.getChildNodes();
+      final int length        = children.getLength();
+
+      for (int loop=0; loop < length; loop++) {
+        currentNode = children.item(loop);
+       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+         currentElement = (Element)currentNode;
+         if (currentElement.getTagName().equals(PARAM_TAG)) {
+           setParameter(currentElement, propSetter);
+         } else {
+           quietParseUnrecognizedElement(factory, currentElement, props);
+      }
+       }
+      }
+    }
+  }
+
+
+  /**
+     Used internally to parse the roor category element.
+  */
+  protected
+  void parseRoot (Element rootElement) {
+    Logger root = repository.getRootLogger();
+    // category configuration needs to be atomic
+    synchronized(root) {    
+      parseChildrenOfLoggerElement(rootElement, root, true);
+    }
+  }
+
+
+  /**
+     Used internally to parse the children of a category element.
+  */
+  protected
+  void parseChildrenOfLoggerElement(Element catElement,
+                                     Logger cat, boolean isRoot) {
+    
+    PropertySetter propSetter = new PropertySetter(cat);
+    
+    // Remove all existing appenders from cat. They will be
+    // reconstructed if need be.
+    cat.removeAllAppenders();
+
+
+    NodeList children  = catElement.getChildNodes();
+    final int length   = children.getLength();
+    
+    for (int loop = 0; loop < length; loop++) {
+      Node currentNode = children.item(loop);
+
+      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+       Element currentElement = (Element) currentNode;
+       String tagName = currentElement.getTagName();
+       
+       if (tagName.equals(APPENDER_REF_TAG)) {
+         Element appenderRef = (Element) currentNode;
+         Appender appender = findAppenderByReference(appenderRef);
+         String refName =  subst(appenderRef.getAttribute(REF_ATTR));
+         if(appender != null) {
+        LogLog.debug("Adding appender named ["+ refName+ 
+                        "] to category ["+cat.getName()+"].");
+    } else {
+        LogLog.debug("Appender named ["+ refName + "] not found.");
+    }
+           
+         cat.addAppender(appender);
+         
+       } else if(tagName.equals(LEVEL_TAG)) {
+         parseLevel(currentElement, cat, isRoot);      
+       } else if(tagName.equals(PRIORITY_TAG)) {
+         parseLevel(currentElement, cat, isRoot);
+       } else if(tagName.equals(PARAM_TAG)) {
+          setParameter(currentElement, propSetter);
+       } else {
+        quietParseUnrecognizedElement(cat, currentElement, props);
+    }
+      }
+    }
+    propSetter.activate();
+  }
+
+  /**
+     Used internally to parse a layout element.
+  */  
+  protected
+  Layout parseLayout (Element layout_element) {
+    String className = subst(layout_element.getAttribute(CLASS_ATTR));
+    LogLog.debug("Parsing layout of class: \""+className+"\"");                 
+    try {
+      Object instance  = Loader.loadClass(className).newInstance();
+      Layout layout    = (Layout)instance;
+      PropertySetter propSetter = new PropertySetter(layout);
+      
+      NodeList params  = layout_element.getChildNodes();
+      final int length         = params.getLength();
+
+      for (int loop = 0; loop < length; loop++) {
+       Node currentNode = params.item(loop);
+       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+         Element currentElement = (Element) currentNode;
+         String tagName = currentElement.getTagName();
+         if(tagName.equals(PARAM_TAG)) {
+            setParameter(currentElement, propSetter);
+         } else {
+          parseUnrecognizedElement(instance, currentElement, props);
+      }
+       }
+      }
+      
+      propSetter.activate();
+      return layout;
+    }
+    catch (Exception oops) {
+        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+      LogLog.error("Could not create the Layout. Reported error follows.",
+                  oops);
+      return null;
+    }
+  }
+
+  protected 
+  void parseRenderer(Element element) {
+    String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR));
+    String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR));
+    if(repository instanceof RendererSupport) {
+      RendererMap.addRenderer((RendererSupport) repository, renderedClass, 
+                             renderingClass);
+    }
+  }
+
+    /**
+     * Parses throwable renderer.
+     * @param element throwableRenderer element.
+     * @return configured throwable renderer.
+     * @since 1.2.16.
+     */
+    protected ThrowableRenderer parseThrowableRenderer(final Element element) {
+        String className = subst(element.getAttribute(CLASS_ATTR));
+        LogLog.debug("Parsing throwableRenderer of class: \""+className+"\"");
+        try {
+          Object instance      = Loader.loadClass(className).newInstance();
+          ThrowableRenderer tr         = (ThrowableRenderer)instance;
+          PropertySetter propSetter = new PropertySetter(tr);
+
+          NodeList params      = element.getChildNodes();
+          final int length     = params.getLength();
+
+          for (int loop = 0; loop < length; loop++) {
+                Node currentNode = params.item(loop);
+                if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+                    Element currentElement = (Element) currentNode;
+                    String tagName = currentElement.getTagName();
+                    if(tagName.equals(PARAM_TAG)) {
+                        setParameter(currentElement, propSetter);
+                    } else {
+                        parseUnrecognizedElement(instance, currentElement, props);
+                    }
+                }
+          }
+
+          propSetter.activate();
+          return tr;
+        }
+        catch (Exception oops) {
+            if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not create the ThrowableRenderer. Reported error follows.",
+               oops);
+          return null;
+        }
+    }
+
+  /**
+     Used internally to parse a level  element.
+  */
+  protected
+  void parseLevel(Element element, Logger logger, boolean isRoot) {
+    String catName = logger.getName();
+    if(isRoot) {
+      catName = "root";
+    }
+
+    String priStr = subst(element.getAttribute(VALUE_ATTR));
+    LogLog.debug("Level value for "+catName+" is  ["+priStr+"].");
+    
+    if(INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
+      if(isRoot) {
+       LogLog.error("Root level cannot be inherited. Ignoring directive.");
+      } else {
+       logger.setLevel(null);
+      }
+    } else {
+      String className = subst(element.getAttribute(CLASS_ATTR));      
+      if(EMPTY_STR.equals(className)) {        
+       logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG));
+      } else {
+       LogLog.debug("Desired Level sub-class: ["+className+']');
+       try {    
+         Class clazz = Loader.loadClass(className);
+         Method toLevelMethod = clazz.getMethod("toLevel", 
+                                                   ONE_STRING_PARAM);
+         Level pri = (Level) toLevelMethod.invoke(null, 
+                                                   new Object[] {priStr});
+         logger.setLevel(pri);
+       } catch (Exception oops) {
+        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+         LogLog.error("Could not create level ["+priStr+
+                      "]. Reported error follows.", oops);
+         return;
+       }
+      }
+    }
+    LogLog.debug(catName + " level set to " + logger.getLevel());    
+  }
+
+  protected
+  void setParameter(Element elem, PropertySetter propSetter) {
+      String name = subst(elem.getAttribute(NAME_ATTR));
+      String value = (elem.getAttribute(VALUE_ATTR));
+      value = subst(OptionConverter.convertSpecialChars(value));
+      propSetter.setProperty(name, value);
+  }
+
+
+  /**
+     Configure log4j using a <code>configuration</code> element as
+     defined in the log4j.dtd. 
+
+  */
+  static
+  public
+  void configure (Element element) {
+    DOMConfigurator configurator = new DOMConfigurator();
+    configurator.doConfigure(element,  LogManager.getLoggerRepository());
+  }
+
+ /**
+     Like {@link #configureAndWatch(String, long)} except that the
+     default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
+     used. 
+
+     @param configFilename A log4j configuration file in XML format.
+
+  */
+  static
+  public
+  void configureAndWatch(String configFilename) {
+    configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
+  }
+
+  /**
+     Read the configuration file <code>configFilename</code> if it
+     exists. Moreover, a thread will be created that will periodically
+     check if <code>configFilename</code> has been created or
+     modified. The period is determined by the <code>delay</code>
+     argument. If a change or file creation is detected, then
+     <code>configFilename</code> is read to configure log4j.  
+
+      @param configFilename A log4j configuration file in XML format.
+      @param delay The delay in milliseconds to wait between each check.
+  */
+  static
+  public
+  void configureAndWatch(String configFilename, long delay) {
+    XMLWatchdog xdog = new XMLWatchdog(configFilename);
+    xdog.setDelay(delay);
+    xdog.start();
+  }
+  
+  private interface ParseAction {
+      Document parse(final DocumentBuilder parser) throws SAXException, IOException;
+  }
+
+
+  public
+  void doConfigure(final String filename, LoggerRepository repository) {
+    ParseAction action = new ParseAction() {
+          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+              return parser.parse(new File(filename));
+          }
+          public String toString() { 
+              return "file [" + filename + "]"; 
+          }
+    };
+    doConfigure(action, repository);
+  }
+  
+
+  public
+  void doConfigure(final URL url, LoggerRepository repository) {
+      ParseAction action = new ParseAction() {
+          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+              URLConnection uConn = url.openConnection();
+              uConn.setUseCaches(false);
+              InputStream stream = uConn.getInputStream();
+              try {
+                InputSource src = new InputSource(stream);
+                src.setSystemId(url.toString());
+                return parser.parse(src);
+              } finally {
+                stream.close();
+              }
+          }
+          public String toString() { 
+              return "url [" + url.toString() + "]"; 
+          }
+      };
+      doConfigure(action, repository);
+  }
+
+  /**
+     Configure log4j by reading in a log4j.dtd compliant XML
+     configuration file.
+
+  */
+  public
+  void doConfigure(final InputStream inputStream, LoggerRepository repository) 
+                                          throws FactoryConfigurationError {
+      ParseAction action = new ParseAction() {
+          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+              InputSource inputSource = new InputSource(inputStream);
+              inputSource.setSystemId("dummy://log4j.dtd");
+              return parser.parse(inputSource);
+          }
+          public String toString() { 
+              return "input stream [" + inputStream.toString() + "]"; 
+          }
+      };
+      doConfigure(action, repository);
+  }
+
+  /**
+     Configure log4j by reading in a log4j.dtd compliant XML
+     configuration file.
+
+  */
+  public
+  void doConfigure(final Reader reader, LoggerRepository repository) 
+                                          throws FactoryConfigurationError {
+      ParseAction action = new ParseAction() {
+          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+              InputSource inputSource = new InputSource(reader);
+              inputSource.setSystemId("dummy://log4j.dtd");
+              return parser.parse(inputSource);
+          }
+          public String toString() { 
+              return "reader [" + reader.toString() + "]"; 
+          }
+      };
+    doConfigure(action, repository);
+  }
+
+  /**
+     Configure log4j by reading in a log4j.dtd compliant XML
+     configuration file.
+
+  */
+  protected
+  void doConfigure(final InputSource inputSource, LoggerRepository repository) 
+                                          throws FactoryConfigurationError {
+      if (inputSource.getSystemId() == null) {
+          inputSource.setSystemId("dummy://log4j.dtd");
+      }
+      ParseAction action = new ParseAction() {
+          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+              return parser.parse(inputSource);
+          }
+          public String toString() { 
+              return "input source [" + inputSource.toString() + "]"; 
+          }
+      };
+      doConfigure(action, repository);
+    }
+    
+    
+  private final void doConfigure(final ParseAction action, final LoggerRepository repository)
+         throws FactoryConfigurationError {
+    DocumentBuilderFactory dbf = null;
+    this.repository = repository;
+    try { 
+      LogLog.debug("System property is :"+
+                               OptionConverter.getSystemProperty(dbfKey, 
+                                                                 null)); 
+      dbf = DocumentBuilderFactory.newInstance();
+      LogLog.debug("Standard DocumentBuilderFactory search succeded.");
+      LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
+    } catch(FactoryConfigurationError fce) {
+      Exception e = fce.getException();
+      LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
+      throw fce;
+    }
+      
+    try {
+      dbf.setValidating(true);
+
+      DocumentBuilder docBuilder = dbf.newDocumentBuilder();
+
+      docBuilder.setErrorHandler(new SAXErrorHandler());      
+      docBuilder.setEntityResolver(new Log4jEntityResolver());
+         
+      Document doc = action.parse(docBuilder);     
+      parse(doc.getDocumentElement());
+    } catch (Exception e) {
+        if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+      // I know this is miserable...
+      LogLog.error("Could not parse "+ action.toString() + ".", e);
+    }
+  }
+
+  /**
+     Configure by taking in an DOM element. 
+  */
+  public void doConfigure(Element element, LoggerRepository repository) {
+    this.repository = repository;
+    parse(element);
+  }
+
+  
+  /**
+     A static version of {@link #doConfigure(String, LoggerRepository)}.  */
+  static
+  public
+  void configure(String filename) throws FactoryConfigurationError {
+    new DOMConfigurator().doConfigure(filename, 
+                                     LogManager.getLoggerRepository());
+  }
+
+  /**
+     A static version of {@link #doConfigure(URL, LoggerRepository)}.
+   */
+  static
+  public
+  void configure(URL url) throws FactoryConfigurationError {
+    new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
+  }
+
+  /**
+     Used internally to configure the log4j framework by parsing a DOM
+     tree of XML elements based on <a
+     href="doc-files/log4j.dtd">log4j.dtd</a>.
+     
+  */
+  protected
+  void parse(Element element) {
+
+    String rootElementName = element.getTagName();
+
+    if (!rootElementName.equals(CONFIGURATION_TAG)) {
+      if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
+       LogLog.warn("The <"+OLD_CONFIGURATION_TAG+
+                    "> element has been deprecated.");
+       LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
+      } else {
+       LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
+       return;
+      }
+    }
+
+
+    String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
+      
+    LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
+    // if the log4j.dtd is not specified in the XML file, then the
+    // "debug" attribute is returned as the empty string.
+    if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {      
+      LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
+    } else {
+      LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
+    }
+
+      //
+      //   reset repository before configuration if reset="true"
+      //       on configuration element.
+      //
+    String resetAttrib = subst(element.getAttribute(RESET_ATTR));
+    LogLog.debug("reset attribute= \"" + resetAttrib +"\".");
+    if(!("".equals(resetAttrib))) {
+         if (OptionConverter.toBoolean(resetAttrib, false)) {
+             repository.resetConfiguration();
+         }
+    }
+
+
+
+    String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
+    if(!confDebug.equals("") && !confDebug.equals("null")) {      
+      LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated.");
+      LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead.");
+      LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
+    }
+
+    String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
+    LogLog.debug("Threshold =\"" + thresholdStr +"\".");
+    if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
+      repository.setThreshold(thresholdStr);
+    }
+
+    //Hashtable appenderBag = new Hashtable(11);
+
+    /* Building Appender objects, placing them in a local namespace
+       for future reference */
+
+    // First configure each category factory under the root element.
+    // Category factories need to be configured before any of
+    // categories they support.
+    //
+    String   tagName = null;
+    Element  currentElement = null;
+    Node     currentNode = null;
+    NodeList children = element.getChildNodes();
+    final int length = children.getLength();
+
+    for (int loop = 0; loop < length; loop++) {
+      currentNode = children.item(loop);
+      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+       currentElement = (Element) currentNode;
+       tagName = currentElement.getTagName();
+
+       if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
+         parseCategoryFactory(currentElement);
+       }
+      }
+    }
+    
+    for (int loop = 0; loop < length; loop++) {
+      currentNode = children.item(loop);
+      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+       currentElement = (Element) currentNode;
+       tagName = currentElement.getTagName();
+
+       if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
+         parseCategory(currentElement);
+       } else if (tagName.equals(ROOT_TAG)) {
+         parseRoot(currentElement);
+       } else if(tagName.equals(RENDERER_TAG)) {
+         parseRenderer(currentElement);
+    } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
+        if (repository instanceof ThrowableRendererSupport) {
+            ThrowableRenderer tr = parseThrowableRenderer(currentElement);
+            if (tr != null) {
+                ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
+            }
+        }
+    } else if (!(tagName.equals(APPENDER_TAG)
+            || tagName.equals(CATEGORY_FACTORY_TAG)
+            || tagName.equals(LOGGER_FACTORY_TAG))) {
+        quietParseUnrecognizedElement(repository, currentElement, props);
+    }
+      }
+    }
+  }
+
+  
+  protected
+  String subst(final String value) {
+      return subst(value, props);
+  }
+
+    /**
+     * Substitutes property value for any references in expression.
+     *
+     * @param value value from configuration file, may contain
+     *              literal text, property references or both
+     * @param props properties.
+     * @return evaluated expression, may still contain expressions
+     *         if unable to expand.
+     * @since 1.2.15
+     */
+    public static String subst(final String value, final Properties props) {
+        try {
+            return OptionConverter.substVars(value, props);
+        } catch (IllegalArgumentException e) {
+            LogLog.warn("Could not perform variable substitution.", e);
+            return value;
+        }
+    }
+
+
+    /**
+     * Sets a parameter based from configuration file content.
+     *
+     * @param elem       param element, may not be null.
+     * @param propSetter property setter, may not be null.
+     * @param props      properties
+     * @since 1.2.15
+     */
+    public static void setParameter(final Element elem,
+                                    final PropertySetter propSetter,
+                                    final Properties props) {
+        String name = subst(elem.getAttribute("name"), props);
+        String value = (elem.getAttribute("value"));
+        value = subst(OptionConverter.convertSpecialChars(value), props);
+        propSetter.setProperty(name, value);
+    }
+
+    /**
+     * Creates an object and processes any nested param elements
+     * but does not call activateOptions.  If the class also supports
+     * UnrecognizedElementParser, the parseUnrecognizedElement method
+     * will be call for any child elements other than param.
+     *
+     * @param element       element, may not be null.
+     * @param props         properties
+     * @param expectedClass interface or class expected to be implemented
+     *                      by created class
+     * @return created class or null.
+     * @throws Exception thrown if the contain object should be abandoned.
+     * @since 1.2.15
+     */
+    public static Object parseElement(final Element element,
+                                             final Properties props,
+                                             final Class expectedClass) throws Exception {
+        String clazz = subst(element.getAttribute("class"), props);
+        Object instance = OptionConverter.instantiateByClassName(clazz,
+                expectedClass, null);
+
+        if (instance != null) {
+            PropertySetter propSetter = new PropertySetter(instance);
+            NodeList children = element.getChildNodes();
+            final int length = children.getLength();
+
+            for (int loop = 0; loop < length; loop++) {
+                Node currentNode = children.item(loop);
+                if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+                    Element currentElement = (Element) currentNode;
+                    String tagName = currentElement.getTagName();
+                    if (tagName.equals("param")) {
+                        setParameter(currentElement, propSetter, props);
+                    } else {
+                         parseUnrecognizedElement(instance, currentElement, props);
+                    }
+                }
+            }
+            return instance;
+        }
+        return null;
+    }
+
+}
+
+
+class XMLWatchdog extends FileWatchdog {
+
+    XMLWatchdog(String filename) {
+    super(filename);
+  }
+
+  /**
+     Call {@link DOMConfigurator#configure(String)} with the
+     <code>filename</code> to reconfigure log4j. */
+  public
+  void doOnChange() {
+    new DOMConfigurator().doConfigure(filename, 
+                                     LogManager.getLoggerRepository());
+  }
+}
diff --git a/srcjar/org/apache/log4j/xml/Log4jEntityResolver.java b/srcjar/org/apache/log4j/xml/Log4jEntityResolver.java
new file mode 100644 (file)
index 0000000..94125a6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import org.apache.log4j.helpers.LogLog;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+
+/**
+ * An {@link EntityResolver} specifically designed to return
+ * <code>log4j.dtd</code> which is embedded within the log4j jar
+ * file. 
+ *
+ * @author Paul Austin
+ * */
+public class Log4jEntityResolver implements EntityResolver {
+  private static final String PUBLIC_ID = "-//APACHE//DTD LOG4J 1.2//EN";
+
+  public InputSource resolveEntity (String publicId, String systemId) {
+    if (systemId.endsWith("log4j.dtd")  || PUBLIC_ID.equals(publicId)) {
+      Class clazz = getClass();
+      InputStream in = clazz.getResourceAsStream("/org/apache/log4j/xml/log4j.dtd");
+      if (in == null) {
+           LogLog.warn("Could not find [log4j.dtd] using [" + clazz.getClassLoader()
+                    + "] class loader, parsed without DTD.");
+        in = new ByteArrayInputStream(new byte[0]);
+      }
+         return new InputSource(in);
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/srcjar/org/apache/log4j/xml/SAXErrorHandler.java b/srcjar/org/apache/log4j/xml/SAXErrorHandler.java
new file mode 100644 (file)
index 0000000..43e851b
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXParseException;
+import org.apache.log4j.helpers.LogLog;
+
+public class SAXErrorHandler implements ErrorHandler {
+
+  public
+  void error(final SAXParseException ex) {
+    emitMessage("Continuable parsing error ", ex);
+  }
+  
+  public
+  void fatalError(final SAXParseException ex) {
+    emitMessage("Fatal parsing error ", ex);
+  }
+   
+  public
+  void warning(final SAXParseException ex) {
+    emitMessage("Parsing warning ", ex);
+  }
+  
+  private static void emitMessage(final String msg, final SAXParseException ex) {
+    LogLog.warn(msg +ex.getLineNumber()+" and column "
+                +ex.getColumnNumber());
+    LogLog.warn(ex.getMessage(), ex.getException());
+  }
+}
diff --git a/srcjar/org/apache/log4j/xml/UnrecognizedElementHandler.java b/srcjar/org/apache/log4j/xml/UnrecognizedElementHandler.java
new file mode 100644 (file)
index 0000000..463d5d9
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.xml;
+
+import org.w3c.dom.Element;
+import java.util.Properties;
+
+/**
+ * When implemented by an object configured by DOMConfigurator,
+ * the handle method will be called when an unrecognized child
+ * element is encountered.  Unrecognized child elements of
+ * the log4j:configuration element will be dispatched to
+ * the logger repository if it supports this interface.
+ *
+ * @since 1.2.15
+ */
+public interface UnrecognizedElementHandler {
+    /**
+     * Called to inform a configured object when
+     * an unrecognized child element is encountered.
+     * @param element element, may not be null.
+     * @param props properties in force, may be null.
+     * @return true if configured object recognized the element
+     * @throws Exception throw an exception to prevent activation
+     * of the configured object.
+     */
+    boolean parseUnrecognizedElement(Element element, Properties props) throws Exception;
+}
\ No newline at end of file
diff --git a/srcjar/org/apache/log4j/xml/XMLLayout.java b/srcjar/org/apache/log4j/xml/XMLLayout.java
new file mode 100644 (file)
index 0000000..2062a80
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors:   Mathias Bogaert
+
+package org.apache.log4j.xml;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.Transform;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Set;
+import java.util.Arrays;
+
+/**
+ * The output of the XMLLayout consists of a series of log4j:event
+ * elements as defined in the <a
+ * href="log4j.dtd">log4j.dtd</a>. It does not output a
+ * complete well-formed XML file. The output is designed to be
+ * included as an <em>external entity</em> in a separate file to form
+ * a correct XML file.
+ *
+ * <p>For example, if <code>abc</code> is the name of the file where
+ * the XMLLayout ouput goes, then a well-formed XML file would be:
+ *
+  <pre>
+   &lt;?xml version="1.0" ?&gt;
+  &lt;!DOCTYPE log4j:eventSet PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd" [&lt;!ENTITY data SYSTEM "abc"&gt;]&gt;
+  &lt;log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;
+       &nbsp;&nbsp;&data;
+  &lt;/log4j:eventSet&gt;
+  </pre>
+ * <p>This approach enforces the independence of the XMLLayout and the
+ * appender where it is embedded.
+ *
+ * <p>The <code>version</code> attribute helps components to correctly
+ * intrepret output generated by XMLLayout. The value of this
+ * attribute should be "1.1" for output generated by log4j versions
+ * prior to log4j 1.2 (final release) and "1.2" for relase 1.2 and
+ * later.
+ *
+ * Appenders using this layout should have their encoding
+ * set to UTF-8 or UTF-16, otherwise events containing
+ * non ASCII characters could result in corrupted
+ * log files. 
+ *
+ * @author Ceki  G&uuml;lc&uuml;
+ * @since 0.9.0 
+ * */
+public class XMLLayout extends Layout {
+
+  private  final int DEFAULT_SIZE = 256;
+  private final int UPPER_LIMIT = 2048;
+
+  private StringBuffer buf = new StringBuffer(DEFAULT_SIZE);
+  private boolean locationInfo = false;
+  private boolean properties = false;
+  /**
+   * The <b>LocationInfo</b> option takes a boolean value. By default,
+   * it is set to false which means there will be no location
+   * information output by this layout. If the the option is set to
+   * true, then the file name and line number of the statement at the
+   * origin of the log statement will be output.
+   *
+   * <p>If you are embedding this layout within an {@link
+   * org.apache.log4j.net.SMTPAppender} then make sure to set the
+   * <b>LocationInfo</b> option of that appender as well.
+   * */
+  public void setLocationInfo(boolean flag) {
+    locationInfo = flag;
+  }
+  
+  /**
+     Returns the current value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+    /**
+     * Sets whether MDC key-value pairs should be output, default false.
+     * @param flag new value.
+     * @since 1.2.15
+     */
+  public void setProperties(final boolean flag) {
+      properties = flag;
+  }
+
+    /**
+     * Gets whether MDC key-value pairs should be output.
+     * @return true if MDC key-value pairs are output.
+     * @since 1.2.15
+     */
+  public boolean getProperties() {
+      return properties;
+  }
+
+  /** No options to activate. */
+  public void activateOptions() {
+  }
+
+
+  /**
+   * Formats a {@link org.apache.log4j.spi.LoggingEvent} in conformance with the log4j.dtd.
+   * */
+  public String format(final LoggingEvent event) {
+
+    // Reset working buffer. If the buffer is too large, then we need a new
+    // one in order to avoid the penalty of creating a large array.
+    if(buf.capacity() > UPPER_LIMIT) {
+      buf = new StringBuffer(DEFAULT_SIZE);
+    } else {
+      buf.setLength(0);
+    }
+    
+    // We yield to the \r\n heresy.
+
+    buf.append("<log4j:event logger=\"");
+    buf.append(Transform.escapeTags(event.getLoggerName()));
+    buf.append("\" timestamp=\"");
+    buf.append(event.timeStamp);
+    buf.append("\" level=\"");
+    buf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
+    buf.append("\" thread=\"");
+    buf.append(Transform.escapeTags(event.getThreadName()));
+    buf.append("\">\r\n");
+
+    buf.append("<log4j:message><![CDATA[");
+    // Append the rendered message. Also make sure to escape any
+    // existing CDATA sections.
+    Transform.appendEscapingCDATA(buf, event.getRenderedMessage());
+    buf.append("]]></log4j:message>\r\n");       
+    
+    String ndc = event.getNDC();
+    if(ndc != null) {
+      buf.append("<log4j:NDC><![CDATA[");
+      Transform.appendEscapingCDATA(buf, ndc);
+      buf.append("]]></log4j:NDC>\r\n");       
+    }
+    
+    String[] s = event.getThrowableStrRep();
+    if(s != null) {
+      buf.append("<log4j:throwable><![CDATA[");
+      for(int i = 0; i < s.length; i++) {
+          Transform.appendEscapingCDATA(buf, s[i]);
+             buf.append("\r\n");
+      }
+      buf.append("]]></log4j:throwable>\r\n");
+    }
+    
+    if(locationInfo) { 
+      LocationInfo locationInfo = event.getLocationInformation();      
+      buf.append("<log4j:locationInfo class=\"");
+      buf.append(Transform.escapeTags(locationInfo.getClassName()));
+      buf.append("\" method=\"");
+      buf.append(Transform.escapeTags(locationInfo.getMethodName()));
+      buf.append("\" file=\"");
+      buf.append(Transform.escapeTags(locationInfo.getFileName()));
+      buf.append("\" line=\"");
+      buf.append(locationInfo.getLineNumber());
+      buf.append("\"/>\r\n");
+    }
+
+    if (properties) {
+        Set keySet = event.getPropertyKeySet();
+        if (keySet.size() > 0) {
+            buf.append("<log4j:properties>\r\n");
+            Object[] keys = keySet.toArray();
+            Arrays.sort(keys);
+            for (int i = 0; i < keys.length; i++) {
+                String key = keys[i].toString();
+                Object val = event.getMDC(key);
+                if (val != null) {
+                    buf.append("<log4j:data name=\"");
+                    buf.append(Transform.escapeTags(key));
+                    buf.append("\" value=\"");
+                    buf.append(Transform.escapeTags(String.valueOf(val)));
+                    buf.append("\"/>\r\n");
+                }
+            }
+            buf.append("</log4j:properties>\r\n");
+        }
+    }
+    
+    buf.append("</log4j:event>\r\n\r\n");
+    
+    return buf.toString();
+  }
+  
+  /**
+     The XMLLayout prints and does not ignore exceptions. Hence the
+     return value <code>false</code>.
+  */
+  public boolean ignoresThrowable() {
+    return false;
+  }
+}
diff --git a/srcjar/org/apache/log4j/xml/package.html b/srcjar/org/apache/log4j/xml/package.html
new file mode 100644 (file)
index 0000000..8a9ed6e
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>XML based components.
+
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Mon Mar 27 21:17:13 MDT 2000
+<!-- hhmts end -->
+</body> </html>