[Schmitzm-commits] r2 - in trunk: . lib lib/Adagios lib/geotoolsArcGrid lib/gt2-2.4.4 lib/jFreeChart lib/jini lib/jini/lib lib/log4j-1.2.14 src src/META-INF src/appl src/appl/data src/appl/util src/org src/org/geotools src/org/geotools/coverage src/org/geotools/feature src/org/geotools/feature/collection src/org/geotools/gui src/org/geotools/gui/swing src/org/geotools/renderer src/org/geotools/renderer/lite src/org/geotools/renderer/lite/gridcoverage2d src/schmitzm src/schmitzm/data src/schmitzm/data/event src/schmitzm/data/property src/schmitzm/data/resource src/schmitzm/data/resource/locales src/schmitzm/geotools src/schmitzm/geotools/feature src/schmitzm/geotools/grid src/schmitzm/geotools/gui src/schmitzm/geotools/gui/resource src/schmitzm/geotools/gui/resource/icons src/schmitzm/geotools/gui/resource/locales src/schmitzm/geotools/io src/schmitzm/geotools/map src/schmitzm/geotools/map/event src/schmitzm/geotools/styling src/schmitzm/io src/schmitzm/io/dyntxt src/schmitzm/jfree src/schmitzm/jfree/resource src/schmitzm/jfree/resource/locales src/schmitzm/lang src/schmitzm/lang/tree src/schmitzm/swing src/schmitzm/swing/event src/schmitzm/swing/log4j src/schmitzm/swing/menu src/schmitzm/swing/resource src/schmitzm/swing/resource/cursor src/schmitzm/swing/resource/icons src/schmitzm/swing/resource/icons/large src/schmitzm/swing/resource/icons/small src/schmitzm/swing/resource/locales src/schmitzm/swing/table src/schmitzm/swing/tree src/schmitzm/temp src/schmitzm/xml src/skrueger src/skrueger/geotools src/skrueger/geotools/io src/skrueger/i8n src/skrueger/swing src/skrueger/swing/resource src/skrueger/swing/resource/locales src/skrueger/swing/small

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Tue Feb 24 23:47:02 CET 2009


Author: mojays
Date: 2009-02-24 23:43:52 +0100 (Tue, 24 Feb 2009)
New Revision: 2

Added:
   trunk/.classpath
   trunk/.project
   trunk/doc/
   trunk/lib/
   trunk/lib/Adagios/
   trunk/lib/Adagios/AdagiosJavaLib.jar
   trunk/lib/geotoolsArcGrid/
   trunk/lib/geotoolsArcGrid/commons-io-1.4.ja_
   trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.2.0.ja_
   trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.3.0-M0.jar
   trunk/lib/geotoolsArcGrid/gt2-render-2.4.2.jar
   trunk/lib/geotoolsArcGrid/gt2-render-2.4.4.ja_
   trunk/lib/geotoolsArcGrid/jmock-1.1.0RC1.ja_
   trunk/lib/geotoolsArcGrid/junit-3.8.1.ja_
   trunk/lib/geotoolsArcGrid/junit-4.4.jar
   trunk/lib/geotoolsArcGrid/readme.txt
   trunk/lib/geotoolsArcGrid/swingx-0.9.2.ja_
   trunk/lib/gt2-2.4.4/
   trunk/lib/gt2-2.4.4/commons-beanutils-1.7.0.jar
   trunk/lib/gt2-2.4.4/commons-logging-1.1.1.jar
   trunk/lib/gt2-2.4.4/geoapi-nogenerics-2.1.0.jar
   trunk/lib/gt2-2.4.4/gt2-api-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-arcgrid-2.3.0-M0.jar
   trunk/lib/gt2-2.4.4/gt2-arcgrid-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-arcsde-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-brewer-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-coverage-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-coverageio-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-epsg-hsql-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-epsg-wkt-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-geotiff-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-image-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-imagemosaic-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-imagepyramid-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-main-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-mappane-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-metadata-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-referencing-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-render-2.4.2.jar
   trunk/lib/gt2-2.4.4/gt2-shapefile-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-shapefile-renderer-2.4.4.jar
   trunk/lib/gt2-2.4.4/gt2-widgets-swing-2.4.4.jar
   trunk/lib/gt2-2.4.4/jaxb-impl-1.3.jar
   trunk/lib/gt2-2.4.4/jdom-1.0.jar
   trunk/lib/gt2-2.4.4/jsr108-0.01.jar
   trunk/lib/gt2-2.4.4/jts-1.8.jar
   trunk/lib/gt2-2.4.4/vecmath-1.3.1.jar
   trunk/lib/jFreeChart/
   trunk/lib/jFreeChart/jcommon-1.0.10.jar
   trunk/lib/jFreeChart/jfreechart-1.0.6.jar
   trunk/lib/jini/
   trunk/lib/jini/lib/
   trunk/lib/jini/lib/jini-ext.jar
   trunk/lib/log4j-1.2.14/
   trunk/lib/log4j-1.2.14/NTEventLogAppender.dll
   trunk/lib/log4j-1.2.14/log4j-1.2.14.jar
   trunk/src/
   trunk/src/META-INF/
   trunk/src/META-INF/PREFERRED.LIST
   trunk/src/appl/
   trunk/src/appl/data/
   trunk/src/appl/data/LateLoadable.java
   trunk/src/appl/data/LoadingException.java
   trunk/src/appl/util/
   trunk/src/appl/util/RasterMetaData.java
   trunk/src/org/
   trunk/src/org/geotools/
   trunk/src/org/geotools/coverage/
   trunk/src/org/geotools/coverage/CoverageUtil.java
   trunk/src/org/geotools/coverage/package.html
   trunk/src/org/geotools/feature/
   trunk/src/org/geotools/feature/collection/
   trunk/src/org/geotools/feature/collection/SubFeatureCollection.GT2-2.3.4
   trunk/src/org/geotools/feature/collection/SubFeatureCollection.java
   trunk/src/org/geotools/gui/
   trunk/src/org/geotools/gui/swing/
   trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.1
   trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.4
   trunk/src/org/geotools/gui/swing/JMapPane.java
   trunk/src/org/geotools/gui/swing/MouseSelectionTracker_Public.java
   trunk/src/org/geotools/gui/swing/package.html
   trunk/src/org/geotools/renderer/
   trunk/src/org/geotools/renderer/lite/
   trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.1
   trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.4
   trunk/src/org/geotools/renderer/lite/gridcoverage2d/
   trunk/src/org/geotools/renderer/lite/gridcoverage2d/GridCoverageRenderer.gt2-2.4.4+Changes
   trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.gt2-2.4.4+Changes
   trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.java
   trunk/src/overview_schmitzm.html
   trunk/src/schmitzm/
   trunk/src/schmitzm/data/
   trunk/src/schmitzm/data/AbstractReadableGrid.java
   trunk/src/schmitzm/data/AbstractWritableGrid.java
   trunk/src/schmitzm/data/DataUtil.java
   trunk/src/schmitzm/data/ObjectStructure.java
   trunk/src/schmitzm/data/ObjectStructureUtil.java
   trunk/src/schmitzm/data/RasterCalculator.java
   trunk/src/schmitzm/data/RasterFilter.java
   trunk/src/schmitzm/data/RasterOperationTree.071011.double
   trunk/src/schmitzm/data/RasterOperationTree.java
   trunk/src/schmitzm/data/RasterOperationTreeParser.071011.double
   trunk/src/schmitzm/data/RasterOperationTreeParser.java
   trunk/src/schmitzm/data/ReadableGrid.java
   trunk/src/schmitzm/data/WritableGrid.java
   trunk/src/schmitzm/data/WritableGridArray.java
   trunk/src/schmitzm/data/WritableGridRaster.java
   trunk/src/schmitzm/data/event/
   trunk/src/schmitzm/data/event/AbstractObjectEvent.java
   trunk/src/schmitzm/data/event/AbstractObjectTraceable.java
   trunk/src/schmitzm/data/event/GeneralObjectChangeEvent.java
   trunk/src/schmitzm/data/event/Invoker.java
   trunk/src/schmitzm/data/event/NameChangeEvent.java
   trunk/src/schmitzm/data/event/ObjectChangeEvent.java
   trunk/src/schmitzm/data/event/ObjectCloseEvent.java
   trunk/src/schmitzm/data/event/ObjectEvent.java
   trunk/src/schmitzm/data/event/ObjectListener.java
   trunk/src/schmitzm/data/event/ObjectTraceable.java
   trunk/src/schmitzm/data/event/package.html
   trunk/src/schmitzm/data/package.html
   trunk/src/schmitzm/data/property/
   trunk/src/schmitzm/data/property/Access.java
   trunk/src/schmitzm/data/property/AccessViolationException.java
   trunk/src/schmitzm/data/property/Accessible.java
   trunk/src/schmitzm/data/property/DynamicProperties.java
   trunk/src/schmitzm/data/property/ListProperty.java
   trunk/src/schmitzm/data/property/ListPropertyReadAccess.java
   trunk/src/schmitzm/data/property/ListPropertyWriteAccess.java
   trunk/src/schmitzm/data/property/MatrixProperty.java
   trunk/src/schmitzm/data/property/Properties.java
   trunk/src/schmitzm/data/property/Property.java
   trunk/src/schmitzm/data/property/PropertyException.java
   trunk/src/schmitzm/data/property/PropertyReadAccess.java
   trunk/src/schmitzm/data/property/PropertySet.java
   trunk/src/schmitzm/data/property/PropertyType.java
   trunk/src/schmitzm/data/property/PropertyUtil.java
   trunk/src/schmitzm/data/property/PropertyWriteAccess.java
   trunk/src/schmitzm/data/property/ScalarProperty.java
   trunk/src/schmitzm/data/property/ValueProperty.java
   trunk/src/schmitzm/data/property/ValuePropertyAccessParameters.java
   trunk/src/schmitzm/data/property/ValuePropertyType.java
   trunk/src/schmitzm/data/property/package.html
   trunk/src/schmitzm/data/resource/
   trunk/src/schmitzm/data/resource/locales/
   trunk/src/schmitzm/data/resource/locales/DataResourceBundle.properties
   trunk/src/schmitzm/data/resource/locales/DataResourceBundle_de.properties
   trunk/src/schmitzm/geotools/
   trunk/src/schmitzm/geotools/FilterUtil.java
   trunk/src/schmitzm/geotools/GTUtil.java
   trunk/src/schmitzm/geotools/JTSUtil.java
   trunk/src/schmitzm/geotools/feature/
   trunk/src/schmitzm/geotools/feature/AbstractAutoValueGenerator.java
   trunk/src/schmitzm/geotools/feature/AttributeFilter.java
   trunk/src/schmitzm/geotools/feature/AttributeTypeFilter.java
   trunk/src/schmitzm/geotools/feature/AutoValueGenerator.java
   trunk/src/schmitzm/geotools/feature/FeatureCollectionReader.java
   trunk/src/schmitzm/geotools/feature/FeatureOperationTree.java
   trunk/src/schmitzm/geotools/feature/FeatureOperationTreeFilter.java
   trunk/src/schmitzm/geotools/feature/FeatureOperationTreeParser.java
   trunk/src/schmitzm/geotools/feature/FeatureTableModel.java
   trunk/src/schmitzm/geotools/feature/FeatureTypeBuilderTableModel.java
   trunk/src/schmitzm/geotools/feature/FeatureTypeTableModel.java
   trunk/src/schmitzm/geotools/feature/FeatureUtil.java
   trunk/src/schmitzm/geotools/feature/NumberValueGenerator.java
   trunk/src/schmitzm/geotools/feature/package.html
   trunk/src/schmitzm/geotools/grid/
   trunk/src/schmitzm/geotools/grid/GridStatistic.java
   trunk/src/schmitzm/geotools/grid/GridUtil.java
   trunk/src/schmitzm/geotools/grid/GridZoneStatistic.java
   trunk/src/schmitzm/geotools/grid/ReadableGridCoverage.java
   trunk/src/schmitzm/geotools/grid/WritableGridCoverage.java
   trunk/src/schmitzm/geotools/grid/package.html
   trunk/src/schmitzm/geotools/gui/
   trunk/src/schmitzm/geotools/gui/CRSSelectionDialog.java
   trunk/src/schmitzm/geotools/gui/ColorMapTable.java
   trunk/src/schmitzm/geotools/gui/FeatureCollectionFilterPanel.java
   trunk/src/schmitzm/geotools/gui/FeatureCollectionFrame.java
   trunk/src/schmitzm/geotools/gui/FeatureCollectionPane.java
   trunk/src/schmitzm/geotools/gui/FeatureFilterPanel.java
   trunk/src/schmitzm/geotools/gui/FeatureInputOption.java
   trunk/src/schmitzm/geotools/gui/FeatureLayerFilterDialog.java
   trunk/src/schmitzm/geotools/gui/FeatureTypeInputOption.java
   trunk/src/schmitzm/geotools/gui/GTResource.20080424
   trunk/src/schmitzm/geotools/gui/GTResource_de.20080424
   trunk/src/schmitzm/geotools/gui/GeoMapPane.java
   trunk/src/schmitzm/geotools/gui/GeoPositionLabel.java
   trunk/src/schmitzm/geotools/gui/GeotoolsGUIUtil.java
   trunk/src/schmitzm/geotools/gui/GridPanel.java
   trunk/src/schmitzm/geotools/gui/JEditorPane.java
   trunk/src/schmitzm/geotools/gui/JEditorToolBar.java
   trunk/src/schmitzm/geotools/gui/JMapPane.20080418_eigenesPanning
   trunk/src/schmitzm/geotools/gui/JMapPane.java
   trunk/src/schmitzm/geotools/gui/LayeredEditorFrame.java
   trunk/src/schmitzm/geotools/gui/LayeredMapFrame.java
   trunk/src/schmitzm/geotools/gui/LayeredMapPane.java
   trunk/src/schmitzm/geotools/gui/MapActionControlPane.java
   trunk/src/schmitzm/geotools/gui/MapContextControlPane.java
   trunk/src/schmitzm/geotools/gui/MapPaneStatusBar.java
   trunk/src/schmitzm/geotools/gui/RasterPositionLabel.java
   trunk/src/schmitzm/geotools/gui/ScalePane.java
   trunk/src/schmitzm/geotools/gui/ScalePanel.java
   trunk/src/schmitzm/geotools/gui/StyleToolBar.java
   trunk/src/schmitzm/geotools/gui/package.html
   trunk/src/schmitzm/geotools/gui/resource/
   trunk/src/schmitzm/geotools/gui/resource/icons/
   trunk/src/schmitzm/geotools/gui/resource/icons/edit_clear.png
   trunk/src/schmitzm/geotools/gui/resource/icons/edit_finish.png
   trunk/src/schmitzm/geotools/gui/resource/icons/edit_redo.png
   trunk/src/schmitzm/geotools/gui/resource/icons/edit_undo.png
   trunk/src/schmitzm/geotools/gui/resource/icons/layer_cancel.png
   trunk/src/schmitzm/geotools/gui/resource/icons/layer_finish.png
   trunk/src/schmitzm/geotools/gui/resource/icons/layer_new.png
   trunk/src/schmitzm/geotools/gui/resource/locales/
   trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle.properties
   trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle_de.properties
   trunk/src/schmitzm/geotools/io/
   trunk/src/schmitzm/geotools/io/GeoExportUtil.java
   trunk/src/schmitzm/geotools/io/GeoImportUtil.java
   trunk/src/schmitzm/geotools/io/package.html
   trunk/src/schmitzm/geotools/map/
   trunk/src/schmitzm/geotools/map/event/
   trunk/src/schmitzm/geotools/map/event/FeatureModifiedEvent.java
   trunk/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java
   trunk/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java
   trunk/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java
   trunk/src/schmitzm/geotools/map/event/GridCoverageValueSelectedEvent.java
   trunk/src/schmitzm/geotools/map/event/JEditorPaneEvent.java
   trunk/src/schmitzm/geotools/map/event/JMapPaneEvent.java
   trunk/src/schmitzm/geotools/map/event/JMapPaneListener.java
   trunk/src/schmitzm/geotools/map/event/LayerEditCanceledEvent.java
   trunk/src/schmitzm/geotools/map/event/LayerEditFinishedEvent.java
   trunk/src/schmitzm/geotools/map/event/LayerEditStartedEvent.java
   trunk/src/schmitzm/geotools/map/event/MapAreaChangedEvent.java
   trunk/src/schmitzm/geotools/map/event/MapContextSynchronizer.java
   trunk/src/schmitzm/geotools/map/event/MapLayerAdapter.java
   trunk/src/schmitzm/geotools/map/event/MapLayerListAdapter.java
   trunk/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java
   trunk/src/schmitzm/geotools/map/event/ScaleChangedEvent.java
   trunk/src/schmitzm/geotools/package.html
   trunk/src/schmitzm/geotools/styling/
   trunk/src/schmitzm/geotools/styling/ColorMapManager.java
   trunk/src/schmitzm/geotools/styling/StylingUtil.java
   trunk/src/schmitzm/io/
   trunk/src/schmitzm/io/BinaryInputBuffer.java
   trunk/src/schmitzm/io/BinaryInputStream.java
   trunk/src/schmitzm/io/BinaryUtil.java
   trunk/src/schmitzm/io/CombinedInputStream.java
   trunk/src/schmitzm/io/FileInputStream.java
   trunk/src/schmitzm/io/FileOutputStream.java
   trunk/src/schmitzm/io/IOUtil.java
   trunk/src/schmitzm/io/InputBuffer.java
   trunk/src/schmitzm/io/dyntxt/
   trunk/src/schmitzm/io/dyntxt/DynamicBlock.java
   trunk/src/schmitzm/io/dyntxt/DynamicElement.java
   trunk/src/schmitzm/io/dyntxt/DynamicField.java
   trunk/src/schmitzm/io/dyntxt/DynamicInputProvider.java
   trunk/src/schmitzm/io/dyntxt/DynamicLoop.java
   trunk/src/schmitzm/io/dyntxt/DynamicTextGenerator.java
   trunk/src/schmitzm/io/dyntxt/StaticText.java
   trunk/src/schmitzm/io/package.html
   trunk/src/schmitzm/jfree/
   trunk/src/schmitzm/jfree/JFreeChartUtil.java
   trunk/src/schmitzm/jfree/resource/
   trunk/src/schmitzm/jfree/resource/locales/
   trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties
   trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties
   trunk/src/schmitzm/lang/
   trunk/src/schmitzm/lang/AbstractNamedObject.java
   trunk/src/schmitzm/lang/AlreadyHandledException.java
   trunk/src/schmitzm/lang/ComparableObject.java
   trunk/src/schmitzm/lang/DefaultComparator.java
   trunk/src/schmitzm/lang/DuplicateException.java
   trunk/src/schmitzm/lang/HashtableResourceBundle.java
   trunk/src/schmitzm/lang/HashtableWithCollisionList.java
   trunk/src/schmitzm/lang/Indent.java
   trunk/src/schmitzm/lang/LangUtil.java
   trunk/src/schmitzm/lang/LimitedVector.java
   trunk/src/schmitzm/lang/LocaleComparator.java
   trunk/src/schmitzm/lang/MultiDimArray.java
   trunk/src/schmitzm/lang/NamedObject.java
   trunk/src/schmitzm/lang/OperationCanceledException.java
   trunk/src/schmitzm/lang/PushbackStringTokenizer.java
   trunk/src/schmitzm/lang/ResourceProvider.java
   trunk/src/schmitzm/lang/SortableVector.java
   trunk/src/schmitzm/lang/StepThread.java
   trunk/src/schmitzm/lang/WorkingThread.java
   trunk/src/schmitzm/lang/WorkingThreadAdapter.java
   trunk/src/schmitzm/lang/WorkingThreadListener.java
   trunk/src/schmitzm/lang/package.html
   trunk/src/schmitzm/lang/tree/
   trunk/src/schmitzm/lang/tree/BinaryTreeNode.java
   trunk/src/schmitzm/lang/tree/OperationTree.071011.double
   trunk/src/schmitzm/lang/tree/OperationTree.java
   trunk/src/schmitzm/lang/tree/OperationTreeParser.071011.double
   trunk/src/schmitzm/lang/tree/OperationTreeParser.java
   trunk/src/schmitzm/lang/tree/TreeNode.java
   trunk/src/schmitzm/swing/
   trunk/src/schmitzm/swing/BooleanInputOption.java
   trunk/src/schmitzm/swing/BrowseInputOption.java
   trunk/src/schmitzm/swing/ButtonGroup.java
   trunk/src/schmitzm/swing/CaptionsChangeable.java
   trunk/src/schmitzm/swing/CaptionsChangeablePanel.java
   trunk/src/schmitzm/swing/CircleIcon.java
   trunk/src/schmitzm/swing/ColorInputOption.java
   trunk/src/schmitzm/swing/Compass.java
   trunk/src/schmitzm/swing/ExceptionDialog.java
   trunk/src/schmitzm/swing/ExpansionBar.java
   trunk/src/schmitzm/swing/FileInputOption.java
   trunk/src/schmitzm/swing/InputCompass.java
   trunk/src/schmitzm/swing/InputOption.java
   trunk/src/schmitzm/swing/JPanel.java
   trunk/src/schmitzm/swing/ManualInputOption.java
   trunk/src/schmitzm/swing/MultipleOptionPane.java
   trunk/src/schmitzm/swing/ObjectDisplayContainer.java
   trunk/src/schmitzm/swing/OperationTreePanel.java
   trunk/src/schmitzm/swing/RotationSpinnerNumberModel.java
   trunk/src/schmitzm/swing/SelectionInputOption.java
   trunk/src/schmitzm/swing/SelectionPreservingCaret.java
   trunk/src/schmitzm/swing/SliderSpinnerPanel.java
   trunk/src/schmitzm/swing/SpringUtilities.java
   trunk/src/schmitzm/swing/StatusDialog.java
   trunk/src/schmitzm/swing/StoplightContainer.java
   trunk/src/schmitzm/swing/SwingResource.20080424
   trunk/src/schmitzm/swing/SwingResource_de.20080424
   trunk/src/schmitzm/swing/SwingResource_en.20080424
   trunk/src/schmitzm/swing/SwingUtil.java
   trunk/src/schmitzm/swing/SwingWorker.java
   trunk/src/schmitzm/swing/TextAreaPrintStream.java
   trunk/src/schmitzm/swing/TreeSelectionDialog.java
   trunk/src/schmitzm/swing/event/
   trunk/src/schmitzm/swing/event/InputOptionAdapter.java
   trunk/src/schmitzm/swing/event/InputOptionListener.java
   trunk/src/schmitzm/swing/event/MouseAdapter.java
   trunk/src/schmitzm/swing/event/PopupMenuListener.java
   trunk/src/schmitzm/swing/event/WindowEventConnector.java
   trunk/src/schmitzm/swing/event/package.html
   trunk/src/schmitzm/swing/log4j/
   trunk/src/schmitzm/swing/log4j/LoggerConfigurationTableModel.java
   trunk/src/schmitzm/swing/log4j/LoggerFrame.java
   trunk/src/schmitzm/swing/menu/
   trunk/src/schmitzm/swing/menu/ActionStructure.java
   trunk/src/schmitzm/swing/menu/ObjectMenuItem.java
   trunk/src/schmitzm/swing/menu/ObjectSubMenu.java
   trunk/src/schmitzm/swing/menu/package.html
   trunk/src/schmitzm/swing/package.html
   trunk/src/schmitzm/swing/resource/
   trunk/src/schmitzm/swing/resource/cursor/
   trunk/src/schmitzm/swing/resource/cursor/crosshair.gif
   trunk/src/schmitzm/swing/resource/cursor/gimp-tool-cursors.xcf
   trunk/src/schmitzm/swing/resource/cursor/hand_closed.png
   trunk/src/schmitzm/swing/resource/cursor/hand_pan.png
   trunk/src/schmitzm/swing/resource/cursor/zoom.gif
   trunk/src/schmitzm/swing/resource/cursor/zoom_cursor.gif
   trunk/src/schmitzm/swing/resource/cursor/zoom_in.gif
   trunk/src/schmitzm/swing/resource/cursor/zoom_out.gif
   trunk/src/schmitzm/swing/resource/icons/
   trunk/src/schmitzm/swing/resource/icons/large/
   trunk/src/schmitzm/swing/resource/icons/large/info.gif
   trunk/src/schmitzm/swing/resource/icons/large/select_multi.gif
   trunk/src/schmitzm/swing/resource/icons/large/select_normal.gif
   trunk/src/schmitzm/swing/resource/icons/large/zoom.gif
   trunk/src/schmitzm/swing/resource/icons/small/
   trunk/src/schmitzm/swing/resource/icons/small/Thumbs.db
   trunk/src/schmitzm/swing/resource/icons/small/close_polygon.bmp
   trunk/src/schmitzm/swing/resource/icons/small/close_polygon.gif
   trunk/src/schmitzm/swing/resource/icons/small/info.gif
   trunk/src/schmitzm/swing/resource/icons/small/lasso.gif
   trunk/src/schmitzm/swing/resource/icons/small/new.gif
   trunk/src/schmitzm/swing/resource/icons/small/redo.gif
   trunk/src/schmitzm/swing/resource/icons/small/select_multi.gif
   trunk/src/schmitzm/swing/resource/icons/small/select_normal.gif
   trunk/src/schmitzm/swing/resource/icons/small/select_points.bmp
   trunk/src/schmitzm/swing/resource/icons/small/select_points.gif
   trunk/src/schmitzm/swing/resource/icons/small/select_polgon.bmp
   trunk/src/schmitzm/swing/resource/icons/small/select_polgon.gif
   trunk/src/schmitzm/swing/resource/icons/small/undo.gif
   trunk/src/schmitzm/swing/resource/icons/small/utilities.gif
   trunk/src/schmitzm/swing/resource/icons/small/utilities.png
   trunk/src/schmitzm/swing/resource/icons/small/zoom.gif
   trunk/src/schmitzm/swing/resource/locales/
   trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle.properties
   trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle_de.properties
   trunk/src/schmitzm/swing/table/
   trunk/src/schmitzm/swing/table/AbstractMutableTableModel.java
   trunk/src/schmitzm/swing/table/AbstractTableModel.java
   trunk/src/schmitzm/swing/table/ColorEditor.java
   trunk/src/schmitzm/swing/table/ColorRenderer.java
   trunk/src/schmitzm/swing/table/ColorsRenderer.java
   trunk/src/schmitzm/swing/table/ComponentRenderer.java
   trunk/src/schmitzm/swing/table/MutableTable.java
   trunk/src/schmitzm/swing/table/MutableTableModel.java
   trunk/src/schmitzm/swing/table/TableComponentMouseListener.java
   trunk/src/schmitzm/swing/table/package.html
   trunk/src/schmitzm/swing/tree/
   trunk/src/schmitzm/swing/tree/ContentNode.java
   trunk/src/schmitzm/swing/tree/EditableNode.java
   trunk/src/schmitzm/swing/tree/EmptyInnerNode.java
   trunk/src/schmitzm/swing/tree/EmptyNode.java
   trunk/src/schmitzm/swing/tree/package.html
   trunk/src/schmitzm/temp/
   trunk/src/schmitzm/temp/BaseTypeUtil.java
   trunk/src/schmitzm/temp/package.html
   trunk/src/schmitzm/xml/
   trunk/src/schmitzm/xml/XMLUtil.java
   trunk/src/skrueger/
   trunk/src/skrueger/AttributeMetaData.java
   trunk/src/skrueger/AttributeMetaDataAttributeTypeFilter.java
   trunk/src/skrueger/RasterLegendData.java
   trunk/src/skrueger/geotools/
   trunk/src/skrueger/geotools/AbstractStyledMap.java
   trunk/src/skrueger/geotools/MapContextManagerInterface.java
   trunk/src/skrueger/geotools/MapPaneToolBar.java
   trunk/src/skrueger/geotools/MapView.java
   trunk/src/skrueger/geotools/StyledDataStoreInterface.java
   trunk/src/skrueger/geotools/StyledFS.java
   trunk/src/skrueger/geotools/StyledFeatureCollection.java
   trunk/src/skrueger/geotools/StyledFeatureCollectionInterface.java
   trunk/src/skrueger/geotools/StyledFeatureSourceInterface.java
   trunk/src/skrueger/geotools/StyledGridCoverage.java
   trunk/src/skrueger/geotools/StyledGridCoverageInterface.java
   trunk/src/skrueger/geotools/StyledGridCoverageReader.java
   trunk/src/skrueger/geotools/StyledGridCoverageReaderInterface.java
   trunk/src/skrueger/geotools/StyledMapInterface.java
   trunk/src/skrueger/geotools/StyledMapStyle.java
   trunk/src/skrueger/geotools/StyledMapUtil.java
   trunk/src/skrueger/geotools/StyledRasterInterface.java
   trunk/src/skrueger/geotools/ZoomRestrictableGridInterface.java
   trunk/src/skrueger/geotools/info.png
   trunk/src/skrueger/geotools/io/
   trunk/src/skrueger/geotools/io/GeoImportUtilURL.java
   trunk/src/skrueger/geotools/pan.png
   trunk/src/skrueger/geotools/zoom_back.png
   trunk/src/skrueger/geotools/zoom_forward.png
   trunk/src/skrueger/geotools/zoom_in.png
   trunk/src/skrueger/geotools/zoom_out.png
   trunk/src/skrueger/i8n/
   trunk/src/skrueger/i8n/I8NUtil.java
   trunk/src/skrueger/i8n/SwitchLanguageDialog.java
   trunk/src/skrueger/i8n/Translation.java
   trunk/src/skrueger/swing/
   trunk/src/skrueger/swing/CancelButton.java
   trunk/src/skrueger/swing/OkButton.java
   trunk/src/skrueger/swing/TranslationAskJDialog.java
   trunk/src/skrueger/swing/TranslationEditJPanel.java
   trunk/src/skrueger/swing/TranslationJTextField.java
   trunk/src/skrueger/swing/resource/
   trunk/src/skrueger/swing/resource/flags.jpg
   trunk/src/skrueger/swing/resource/locales/
   trunk/src/skrueger/swing/resource/locales/SwingResourceBundle.properties
   trunk/src/skrueger/swing/resource/locales/SwingResourceBundle_de.properties
   trunk/src/skrueger/swing/small/
   trunk/src/skrueger/swing/small/cancel.png
   trunk/src/skrueger/swing/small/ok.png
Modified:
   trunk/
Log:
First Commit, corresponds to Revision 1008 of Wikisquare-SVN
includes:
- schmitzm.* (except schmitzm.test)
- org.geotools.* (all overridden classes)
- skrueger.geotools
- skrueger.i8n
- skrueger.swing
- appl.data.LateLoadable (dependency in SCHMITZM)
- appl.data.LoadingException (dependency in SCHMITZM)
- appl.util.RasterMetaData (dependency in SCHMITZM)



Property changes on: trunk
___________________________________________________________________
Name: svn:ignore
   + classes


Added: trunk/.classpath
===================================================================
--- trunk/.classpath	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/.classpath	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry excluding="**/.svn/" kind="src" path="src"/>
+	<classpathentry exported="true" kind="lib" path="lib/geotoolsArcGrid/gt2-render-2.4.2.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/geotoolsArcGrid/junit-4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-main-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-mappane-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/log4j-1.2.14/log4j-1.2.14.jar"/>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry exported="true" kind="lib" path="lib/jFreeChart/jcommon-1.0.10.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/jFreeChart/jfreechart-1.0.6.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/Adagios/AdagiosJavaLib.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/jini/lib/jini-ext.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-coverage-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-coverageio-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-api-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-metadata-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/commons-beanutils-1.7.0.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/commons-logging-1.1.1.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/geoapi-nogenerics-2.1.0.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-arcgrid-2.3.0-M0.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-arcgrid-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-arcsde-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-brewer-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-epsg-hsql-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-epsg-wkt-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-geotiff-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-image-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-imagemosaic-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-imagepyramid-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-referencing-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-render-2.4.2.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-shapefile-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-shapefile-renderer-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/gt2-widgets-swing-2.4.4.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/jaxb-impl-1.3.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/jsr108-0.01.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/jts-1.8.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/vecmath-1.3.1.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/gt2-2.4.4/jdom-1.0.jar"/>
+	<classpathentry kind="output" path="classes"/>
+</classpath>

Added: trunk/.project
===================================================================
--- trunk/.project	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/.project	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>SCHMITZM_TEST</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

Added: trunk/lib/Adagios/AdagiosJavaLib.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/Adagios/AdagiosJavaLib.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/commons-io-1.4.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/commons-io-1.4.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.2.0.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.2.0.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.3.0-M0.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/gt2-arcgrid-2.3.0-M0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/gt2-render-2.4.2.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/gt2-render-2.4.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/gt2-render-2.4.4.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/gt2-render-2.4.4.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/jmock-1.1.0RC1.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/jmock-1.1.0RC1.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/junit-3.8.1.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/junit-3.8.1.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/junit-4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/junit-4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/geotoolsArcGrid/readme.txt
===================================================================
--- trunk/lib/geotoolsArcGrid/readme.txt	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/lib/geotoolsArcGrid/readme.txt	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,23 @@
+===============================================
+Additionally necessary libs (besides Geotools)
+because no longer included in Geotools
+===============================================
+
+when using gt2.4.2
+------------------
+gt2-arcgrid-2.3.0-M0.jar
+commons-io-1.4.jar
+junit-4.4.jar
+
+
+when using gt2.4.4
+------------------
+gt2-arcgrid-2.3.0-M0.jar
+junit-4.4.jar
+gt2-render-2.4.2.jar   # using this jar instead of the 2.4.4 version resolves the bug that
+                       # the PointSymbolizer does not render point layers from inside a
+                       # JAR properly
+
+
+The older arcgrid has to be included below the 2.4.4 arcgrid! This dependency is a TODO and should be removed
+

Added: trunk/lib/geotoolsArcGrid/swingx-0.9.2.ja_
===================================================================
(Binary files differ)


Property changes on: trunk/lib/geotoolsArcGrid/swingx-0.9.2.ja_
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/commons-beanutils-1.7.0.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/commons-beanutils-1.7.0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/commons-logging-1.1.1.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/commons-logging-1.1.1.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/geoapi-nogenerics-2.1.0.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/geoapi-nogenerics-2.1.0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-api-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-api-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-arcgrid-2.3.0-M0.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-arcgrid-2.3.0-M0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-arcgrid-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-arcgrid-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-arcsde-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-arcsde-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-brewer-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-brewer-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-coverage-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-coverage-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-coverageio-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-coverageio-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-epsg-hsql-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-epsg-hsql-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-epsg-wkt-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-epsg-wkt-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-geotiff-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-geotiff-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-image-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-image-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-imagemosaic-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-imagemosaic-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-imagepyramid-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-imagepyramid-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-main-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-main-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-mappane-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-mappane-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-metadata-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-metadata-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-referencing-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-referencing-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-render-2.4.2.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-render-2.4.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-shapefile-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-shapefile-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-shapefile-renderer-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-shapefile-renderer-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/gt2-widgets-swing-2.4.4.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/gt2-widgets-swing-2.4.4.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/jaxb-impl-1.3.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/jaxb-impl-1.3.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/jdom-1.0.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/jdom-1.0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/jsr108-0.01.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/jsr108-0.01.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/jts-1.8.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/jts-1.8.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/gt2-2.4.4/vecmath-1.3.1.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/gt2-2.4.4/vecmath-1.3.1.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jFreeChart/jcommon-1.0.10.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jFreeChart/jcommon-1.0.10.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jFreeChart/jfreechart-1.0.6.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jFreeChart/jfreechart-1.0.6.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jini/lib/jini-ext.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jini/lib/jini-ext.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/log4j-1.2.14/NTEventLogAppender.dll
===================================================================
(Binary files differ)


Property changes on: trunk/lib/log4j-1.2.14/NTEventLogAppender.dll
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/lib/log4j-1.2.14/log4j-1.2.14.jar
===================================================================
(Binary files differ)


Property changes on: trunk/lib/log4j-1.2.14/log4j-1.2.14.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/META-INF/PREFERRED.LIST
===================================================================
--- trunk/src/META-INF/PREFERRED.LIST	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/META-INF/PREFERRED.LIST	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,8 @@
+PreferredResources-Version: 1.0
+Preferred: false
+
+Name: edu/bonn/xulu/plugin/model/*
+Preferred: true
+
+Name: edu/bonn/xulu/plugin/model/-
+Preferred: true

Added: trunk/src/appl/data/LateLoadable.java
===================================================================
--- trunk/src/appl/data/LateLoadable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/appl/data/LateLoadable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,40 @@
+package appl.data;
+
+import schmitzm.data.WritableGrid;
+
+/**
+ * Defines the base functionality of late loading. Late loading means that the
+ * data is loaded into memory only when it is accessed (for example by
+ * <code>getRasterSample(..)</code>-Methods when implemented for
+ * {@link WritableGrid}s). Notice that this class can be used with any datatype
+ * (not only grids).
+ * 
+ * @author Dominik Appl
+ */
+public interface LateLoadable {
+	/**
+	 * Answers if the data type supports late loading
+	 * 
+	 * @return true, if late loading is supported
+	 */
+	public boolean isLateLoadable();
+
+	/**
+	 * Loads the Data into the memory if this function supported. Else it should
+	 * be already loaded
+	 * 
+	 * @throws LoadingException
+	 *             if the loading fails
+	 */
+	public void loadData() throws LoadingException;
+
+	/**
+	 * Unloads the Data onto disk or into nirvana, depending on implementation.
+	 * Or does nothing, if late loading is not supported. <b>NOTICE</b>:
+	 * UNLOADING DOES NOT MEAN, THAT DATA IS WRITTEN BACK TO THE SOURCE. It may
+	 * be stored in a temporary file for later loading/export, depending on
+	 * implementation.
+	 */
+	public void unloadData();
+
+}

Added: trunk/src/appl/data/LoadingException.java
===================================================================
--- trunk/src/appl/data/LoadingException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/appl/data/LoadingException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,28 @@
+package appl.data;
+
+/**
+ * Thrown when the data loading from some source fails.
+ * 
+ * @author Dominik Appl
+ */
+public class LoadingException extends Exception {
+
+	String loadSource;
+
+	/**
+	 * Creates a new Exception with the name of the Source from which the
+	 * loading failed
+	 */
+	public LoadingException(String message, Object loadSource) {
+		super(message);
+		this.loadSource = loadSource.toString();
+
+	}
+
+	/**
+	 * @return the name of the Source from which the loading failed
+	 */
+	public String getLoadSource() {
+		return loadSource;
+	}
+}

Added: trunk/src/appl/util/RasterMetaData.java
===================================================================
--- trunk/src/appl/util/RasterMetaData.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/appl/util/RasterMetaData.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,236 @@
+package appl.util;
+
+import java.awt.image.DataBuffer;
+import java.io.Serializable;
+
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.data.WritableGrid;
+
+/**
+ * Simple immutable class that encapsulates raster metadata, 
+ * especially the MetaData of a {@link WritableGrid}. 
+ * Just a constructor and getters
+ * on the fields.
+ * 
+ * <br> 
+ * @see WritableGrid for details on the variable description
+ *
+ * @author Dominik Appl
+ */
+public final class RasterMetaData implements Serializable {
+
+    int width = 0;
+
+    int height = 0;
+
+    int minX = 0;
+
+    int minY = 0;
+
+    double realWidth = 0;
+
+    double realHeight = 0;
+
+    int dataType = DataBuffer.TYPE_UNDEFINED;
+
+    double x = 0.0;
+
+    double y = 0.0;
+
+    CoordinateReferenceSystem crs = null;
+
+
+        /**
+         * @return Returns the CRS of the raster
+         */
+        public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
+          return crs;
+        }
+
+	/**
+	 * @return Returns the height in cells
+	 */
+	public final int getHeight() {
+		return height;
+	}
+
+	/**
+	 * @return Returns the width in cells.
+	 */
+	public final int getWidth() {
+		return width;
+	}
+
+	/**
+	 * @return Returns the dataType.
+	 */
+	public final int getDataType() {
+		return dataType;
+	}
+
+	/**
+	 * @return Returns minX (used to indicate a start index)
+	 * @see WritableGrid#getMinX()
+	 */
+	public final int getMinX() {
+		return minX;
+	}
+
+	/**
+	 * @return Returns the minY ((used to indicate a start index)
+	 * @see WritableGrid#getMinY()
+	 */
+	public final int getMinY() {
+		return minY;
+	}
+
+	/**
+	 * @return Returns the realHeight.
+	 */
+	public final double getRealHeight() {
+		return realHeight;
+	}
+
+	/**
+	 * @return Returns the realWidth.
+	 */
+	public final double getRealWidth() {
+		return realWidth;
+	}
+
+	/**
+	 * @return Returns the (geographic) x-coordinate
+	 */
+	public final double getX() {
+		return x;
+	}
+
+
+	/**
+	 * @return Returns  the (geographic) y-coordinate
+	 */
+	public final double getY() {
+		return y;
+	}
+
+	/**
+	 * @param dataType the datatype
+	 * @param gridWidth the width of the grid (in cells)
+	 * @param gridHeight the height of the grid (in cells)
+	 * @param minX the minX (used to indicate a start index)
+	 * @param minY the minY (used to indicate a start index)
+	 * @param realWidth the real width
+	 * @param realHeight the real height
+	 * @param x the (geographic) x-coordinate
+	 * @param y the (geographic) y-coordinate
+     * @param crs the {@link CoordinateReferenceSystem}. Use null for DefaultCRS (WGS84)
+     * 
+     * @see WritableGrid
+	 */
+	public RasterMetaData(int dataType, int gridWidth, int gridHeight,
+			int minX, int minY, double x, double y, double realWidth,
+			double realHeight, CoordinateReferenceSystem crs) {
+		this.width = gridWidth;
+		this.height = gridHeight;
+		this.minX = minX;
+		this.minY = minY;
+		this.realWidth = realWidth;
+		this.realHeight = realHeight;
+		this.dataType = dataType;
+		this.x = x;
+		this.y = y;
+		this.crs = (crs == null) ? DefaultGeographicCRS.WGS84 : crs;
+        
+	}
+
+	/**
+	 * Constructs a RasterMetaData Object. The values of the real height/ width 
+	 * are calculated out of the cellsize. The cells are assumed to be squares.
+	 * 
+	 * @param dataType the datatype
+	 * @param gridWidth the width of the grid (in cells)
+	 * @param gridHeight the height of the grid (in cells)
+	 * @param minX the minX (used to indicate a start index)
+	 * @param minY the minY (used to indicate a start index)
+	 * @param x the (geographic) x-coordinate
+	 * @param y the (geographic) y-coordinate
+	 * @param cellSize the real size of one cell
+     * @param crs the {@link CoordinateReferenceSystem}, use null for default CRS (WGS84)
+	 */
+	public RasterMetaData(int dataType,  int gridWidth, int gridHeight,
+			int minX, int minY, double x, double y, double cellSize, CoordinateReferenceSystem crs) {
+
+		this(dataType, gridWidth, gridHeight, minX, minY, x, y, gridWidth
+				* cellSize, gridHeight * cellSize,crs);
+	}
+
+	/**
+	 * Constructs a RasterMetaDataObject out of the given Grid
+	 * @param w the source grid
+	 */
+	public RasterMetaData(WritableGrid w) {
+        super();
+        this.width = w.getWidth();
+        this.height = w.getHeight();
+        this.minX = w.getMinX();
+        this.minY = w.getMinY();
+        this.realWidth = w.getRealWidth();
+        this.realHeight = w.getRealHeight();
+        this.dataType = w.getSampleType();
+        this.x = w.getX();
+        this.y = w.getY();
+        this.crs = w.getCoordinateReferenceSystem();
+    }
+
+	/**
+	 * needed for serialization
+	 */
+	private RasterMetaData() {
+	 this(0,0,0,0,0,0,0,0,0,null);
+	}
+
+	/**
+	 * @return the real width of a raster cell
+	 */
+	public final double getCellWidth() {
+		return getRealWidth() / getWidth();
+	}
+
+	/**
+	 * Checks if the given RasterMetaData object has the same values.
+	 *
+	 * @param rasterMeta must be a RasterMetaDataObject! Else ClassCastException will be thrown.
+	 * @return true, if all values are the same
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	@Override
+	public boolean equals(Object rasterMeta) {
+		RasterMetaData r = (RasterMetaData) rasterMeta;
+        return (
+        		this.width == r.getWidth() &&
+        		this.height == r.getHeight() &&
+        		this.minX == r.getMinX() &&
+        		this.minY == r.getMinY() &&
+        		this.realWidth == r.getRealWidth() &&
+        		this.realHeight == r.getRealHeight() &&
+        		this.dataType == r.getDataType() &&
+        		this.x == r.getX() &&
+        		this.y == r.getY()
+        		);
+	}
+
+	/**
+	 * @return the real height of a raster cell
+	 */
+	public final double getCellHeight() {
+		return getRealHeight() / getHeight();
+	}
+
+	public final String toString() {
+		return "MetaData: type:" + dataType + " wc:" + width + "hc:" + height
+				+ " minX:" + minX + " minY:" + minY + " x:" + x + " y:" + y
+				+ " rw:" + realWidth + " rh:" + realHeight;
+	}
+}

Added: trunk/src/org/geotools/coverage/CoverageUtil.java
===================================================================
--- trunk/src/org/geotools/coverage/CoverageUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/coverage/CoverageUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,25 @@
+package org.geotools.coverage;
+
+import org.geotools.coverage.Category;
+import org.geotools.coverage.GeophysicsCategory;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * Diese Klasse stellt eine Hilfs-Klasse dar, um auf Klassen des Pakets
+ * <code>org.geotools.coverage</code> zuzugreifen, die von ausserhalb des
+ * Pakets nicht erreichbar sind.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class CoverageUtil {
+  /**
+   * Erzeugt eine neue Instanz von {@link GeophysicsCategory}.
+   * @param  inverse {@linkplain Category Ursprungskategorie}.
+   * @param  isQuantitative {@code true} wenn die Ursprungskategorie quantitativ ist
+   * @throws TransformException wenn die Transformation fehlschlaegt
+   */
+  public static GeophysicsCategory createGeophysicsCategory(Category inverse, boolean isQuantitative) throws TransformException  {
+    return new GeophysicsCategory(inverse,isQuantitative);
+  }
+
+}

Added: trunk/src/org/geotools/coverage/package.html
===================================================================
--- trunk/src/org/geotools/coverage/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/coverage/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,8 @@
+<html>
+<body>
+	Dieses Paket enthält angepasste Klassen der
+	<a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek, sowie
+	Klassen, die aus Zugriffsgründen direkt im Paket <code>org.geotools.coverage</code>
+	implementiert werden müssen.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/org/geotools/feature/collection/SubFeatureCollection.GT2-2.3.4
===================================================================
--- trunk/src/org/geotools/feature/collection/SubFeatureCollection.GT2-2.3.4	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/feature/collection/SubFeatureCollection.GT2-2.3.4	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,273 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2005-2006, GeoTools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.feature.collection;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.geotools.data.FeatureReader;
+import org.geotools.data.collection.DelegateFeatureReader;
+import org.geotools.feature.CollectionListener;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.feature.FeatureList;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.IllegalAttributeException;
+import org.geotools.feature.visitor.FeatureVisitor;
+import org.geotools.filter.Filter;
+import org.geotools.filter.SortBy;
+import org.geotools.util.ProgressListener;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.3.4 to optimize the {@link #size()} method!
+ *    The original variant always iterates ALL features at every call!.</b><br><br>
+ *
+ * Used as a reasonable default implementation for subCollection.
+ * <p>
+ * Note: to implementors, this is not optimal, please do your own
+ * thing - your users will thank you.
+ * </p>
+ *
+ * @author Jody Garnett, Refractions Research, Inc.
+ *
+ * @source $URL: http://svn.geotools.org/geotools/branches/2.3.x/module/main/src/org/geotools/feature/collection/SubFeatureCollection.java $
+ */
+public class SubFeatureCollection extends AbstractResourceCollection implements FeatureCollection {
+// Xulu-01.sn
+    private int size = -1;
+// Xulu-01.en
+
+        /** Filter */
+    protected Filter filter;
+
+    /** Origional Collection */
+        protected FeatureCollection collection;
+
+    protected FeatureState state;
+
+    public SubFeatureCollection(FeatureCollection collection ) {
+        this( collection, null );
+    }
+        public SubFeatureCollection(FeatureCollection collection, Filter subfilter ){
+                if (subfilter != null && subfilter.equals(Filter.ALL)) {
+                        throw new IllegalArgumentException("A subcollection with Filter.ALL is a null operation");
+                }
+                if (subfilter != null && subfilter.equals(Filter.NONE)) {
+                        throw new IllegalArgumentException("A subcollection with Filter.NONE should be a FeatureCollectionEmpty");
+                }
+        if( subfilter != null && (collection instanceof SubFeatureCollection)){
+                        SubFeatureCollection filtered = (SubFeatureCollection) collection;
+                        this.collection = filtered.collection;
+                        this.filter = filtered.filter().and(subfilter);
+                } else {
+                        this.collection = collection;
+                        this.filter = subfilter;
+                }
+        state = new SubFeatureState( this.collection, this );
+        }
+
+        protected Filter filter(){
+            if( filter == null ){
+            filter = createFilter();
+        }
+        return filter;
+    }
+    /** Override to implement subsetting */
+    protected Filter createFilter(){
+        return Filter.NONE;
+    }
+
+        public FeatureType getFeatureType() {
+                return state.getFeatureType();
+        }
+
+        public FeatureIterator features() {
+                return new DelegateFeatureIterator( this, iterator() );
+        }
+
+        public void closeIterator(Iterator iterator) {
+                if( iterator == null ) return;
+
+                if( iterator instanceof FilteredIterator){
+                        FilteredIterator filtered = (FilteredIterator) iterator;
+                        filtered.close();
+                }
+        }
+        public void close(FeatureIterator close) {
+                if( close != null ) close.close();
+        }
+
+    //
+    // Feature methods
+    //
+        public String getID() {
+                return state.getId();
+        }
+
+        public Envelope getBounds(){
+        return state.getBounds();
+        }
+
+        public Geometry getDefaultGeometry() {
+                return state.getDefaultGeometry();
+        }
+
+        public void setDefaultGeometry(Geometry g) throws IllegalAttributeException {
+                state.setDefaultGeometry( g );
+        }
+    public void addListener(CollectionListener listener) throws NullPointerException {
+        state.addListener( listener );
+    }
+
+    public void removeListener(CollectionListener listener) throws NullPointerException {
+        state.removeListener( listener );
+    }
+
+    public FeatureCollection getParent() {
+        return state.getParent();
+    }
+
+    public void setParent(FeatureCollection collection) {
+        state.setParent( collection );
+    }
+
+    public Object[] getAttributes(Object[] attributes) {
+        return state.getAttributes( attributes );
+    }
+
+    public Object getAttribute(String xPath) {
+        return state.getAttribute( xPath );
+    }
+
+    public Object getAttribute(int index) {
+        return state.getAttribute( index );
+    }
+
+    public void setAttribute(int position, Object val) throws IllegalAttributeException, ArrayIndexOutOfBoundsException {
+        state.setAttribute( position, val  );
+    }
+    public int getNumberOfAttributes() {
+        return state.getNumberOfAttributes();
+    }
+
+    public void setAttribute(String xPath, Object attribute) throws IllegalAttributeException {
+        state.setAttribute( xPath, attribute );
+    }
+
+    //
+    //
+    //
+        public FeatureCollection subCollection(Filter filter) {
+                if (filter.equals(Filter.NONE)) {
+                        return this;
+                }
+                if (filter.equals(Filter.ALL)) {
+                        // TODO implement EmptyFeatureCollection( schema )
+                }
+                return new SubFeatureCollection(this, filter);
+        }
+
+        public int size() {
+//Xulu-01.sn
+            if ( this.size >= 0 )
+              return this.size;
+//Xulu-01.en
+                int count = 0;
+                Iterator i = null;
+                try {
+                        for( i = iterator(); i.hasNext(); count++) i.next();
+                }
+                finally {
+                        close( i );
+                }
+//Xulu-01.sn
+               this.size = count;
+//Xulu-01.en
+
+                return count;
+        }
+
+        public boolean isEmpty() {
+                Iterator iterator = iterator();
+                try {
+                        return iterator.hasNext();
+                }
+                finally {
+                        close( iterator );
+                }
+        }
+
+        public Iterator openIterator() {
+                return new FilteredIterator( collection, filter() );
+        }
+
+
+        public FeatureType getSchema() {
+        return collection.getSchema();
+        }
+
+    /**
+     * Accepts a visitor, which then visits each feature in the collection.
+     * @throws IOException
+     */
+    public void accepts(FeatureVisitor visitor, ProgressListener progress ) throws IOException {
+        Iterator iterator = null;
+        // if( progress == null ) progress = new NullProgressListener();
+        try{
+            float size = size();
+            float position = 0;
+            progress.started();
+            for( iterator = iterator(); !progress.isCanceled() && iterator.hasNext(); progress.progress( position++/size )){
+                try {
+                    Feature feature = (Feature) iterator.next();
+                    visitor.visit(feature);
+                }
+                catch( Exception erp ){
+                    progress.exceptionOccurred( erp );
+                }
+            }
+        }
+        finally {
+            progress.complete();
+            close( iterator );
+        }
+    }
+
+        public FeatureReader reader() throws IOException {
+                return new DelegateFeatureReader( getSchema(), features() );
+        }
+
+        public int getCount() throws IOException {
+                return size();
+        }
+
+        public FeatureCollection collection() throws IOException {
+                return this;
+        }
+
+        public FeatureList sort(SortBy order) {
+                return null;
+        }
+
+        public void purge() {
+                collection.purge();
+        }
+}

Added: trunk/src/org/geotools/feature/collection/SubFeatureCollection.java
===================================================================
--- trunk/src/org/geotools/feature/collection/SubFeatureCollection.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/feature/collection/SubFeatureCollection.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,281 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2005-2006, GeoTools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.feature.collection;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.geotools.data.FeatureReader;
+import org.geotools.data.collection.DelegateFeatureReader;
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.feature.CollectionListener;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.feature.FeatureList;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.IllegalAttributeException;
+import org.geotools.feature.visitor.FeatureVisitor;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.opengis.filter.Filter;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.sort.SortBy;
+import org.geotools.util.ProgressListener;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.4.2 to optimize the {@link #size()} method!
+ *    The original variant always iterates ALL features at every call!.</b><br><br>
+ *
+ * Used as a reasonable default implementation for subCollection.
+ * <p>
+ * Note: to implementors, this is not optimal, please do your own
+ * thing - your users will thank you.
+ * </p>
+ *
+ * @author Jody Garnett, Refractions Research, Inc.
+ *
+ * @source $URL: http://svn.geotools.org/geotools/tags/2.4.2/modules/library/main/src/main/java/org/geotools/feature/collection/SubFeatureCollection.java $
+ */
+public class SubFeatureCollection extends AbstractResourceCollection implements FeatureCollection {
+// Xulu-01.sn
+  private int size = -1;
+// Xulu-01.en
+
+        /** Filter */
+    protected Filter filter;
+
+    /** Origional Collection */
+        protected FeatureCollection collection;
+    protected FeatureState state;
+    protected FilterFactory ff = CommonFactoryFinder.getFilterFactory( null );
+
+    public SubFeatureCollection(FeatureCollection collection ) {
+        this( collection, Filter.INCLUDE );
+    }
+        public SubFeatureCollection(FeatureCollection collection, Filter subfilter ){
+                if (subfilter == null ) subfilter = Filter.INCLUDE;
+                if (subfilter.equals(Filter.EXCLUDE)) {
+                        throw new IllegalArgumentException("A subcollection with Filter.EXCLUDE is a null operation");
+                }
+
+        if( collection instanceof SubFeatureCollection){
+                        SubFeatureCollection filtered = (SubFeatureCollection) collection;
+                        if( subfilter.equals(Filter.INCLUDE)){
+                this.collection = filtered.collection;
+                            this.filter = filtered.filter();
+                        }
+                        else {
+                            this.collection = filtered.collection;
+                            this.filter = ff.and( filtered.filter(), subfilter );
+                        }
+                } else {
+                        this.collection = collection;
+                        this.filter = subfilter;
+                }
+        state = new SubFeatureState( this.collection, this );
+        }
+
+        protected Filter filter(){
+            if( filter == null ){
+            filter = createFilter();
+        }
+        return filter;
+    }
+    /** Override to implement subsetting */
+    protected Filter createFilter(){
+        return Filter.INCLUDE;
+    }
+
+        public FeatureType getFeatureType() {
+                return state.getFeatureType();
+        }
+
+        public FeatureIterator features() {
+                return new DelegateFeatureIterator( this, iterator() );
+        }
+
+        public void closeIterator(Iterator iterator) {
+                if( iterator == null ) return;
+
+                if( iterator instanceof FilteredIterator){
+                        FilteredIterator filtered = (FilteredIterator) iterator;
+                        filtered.close();
+                }
+        }
+        public void close(FeatureIterator close) {
+                if( close != null ) close.close();
+        }
+
+    //
+    // Feature methods
+    //
+        public String getID() {
+                return state.getId();
+        }
+
+        public ReferencedEnvelope getBounds(){
+        return ReferencedEnvelope.reference(state.getBounds());
+        }
+
+        public Geometry getDefaultGeometry() {
+            return state.getDefaultGeometry();
+        }
+
+        public void setDefaultGeometry(Geometry g) throws IllegalAttributeException {
+            state.setDefaultGeometry( g );
+        }
+
+    public void addListener(CollectionListener listener) throws NullPointerException {
+        state.addListener( listener );
+    }
+
+    public void removeListener(CollectionListener listener) throws NullPointerException {
+        state.removeListener( listener );
+    }
+
+    public FeatureCollection getParent() {
+        return state.getParent();
+    }
+
+    public void setParent(FeatureCollection collection) {
+        state.setParent( collection );
+    }
+
+    public Object[] getAttributes(Object[] attributes) {
+        return state.getAttributes( attributes );
+    }
+
+    public Object getAttribute(String xPath) {
+        return state.getAttribute( xPath );
+    }
+
+    public Object getAttribute(int index) {
+        return state.getAttribute( index );
+    }
+
+    public void setAttribute(int position, Object val) throws IllegalAttributeException, ArrayIndexOutOfBoundsException {
+        state.setAttribute( position, val  );
+    }
+    public int getNumberOfAttributes() {
+        return state.getNumberOfAttributes();
+    }
+
+    public void setAttribute(String xPath, Object attribute) throws IllegalAttributeException {
+        state.setAttribute( xPath, attribute );
+    }
+
+    //
+    //
+    //
+        public FeatureCollection subCollection(Filter filter) {
+                if (filter.equals(Filter.INCLUDE)) {
+                        return this;
+                }
+                if (filter.equals(Filter.EXCLUDE)) {
+                        // TODO implement EmptyFeatureCollection( schema )
+                }
+                return new SubFeatureCollection(this, filter);
+        }
+
+        public int size() {
+//Xulu-01.sn
+          if ( this.size >= 0 )
+            return this.size;
+//Xulu-01.en
+                int count = 0;
+                Iterator i = null;
+                try {
+                        for( i = iterator(); i.hasNext(); count++) i.next();
+                }
+                finally {
+                        close( i );
+                }
+//Xulu-01.sn
+                this.size = count;
+//Xulu-01.en
+                return count;
+        }
+
+        public boolean isEmpty() {
+                Iterator iterator = iterator();
+                try {
+                        return !iterator.hasNext();
+                }
+                finally {
+                        close( iterator );
+                }
+        }
+
+        public Iterator openIterator() {
+                return new FilteredIterator( collection, filter() );
+        }
+
+
+        public FeatureType getSchema() {
+        return collection.getSchema();
+        }
+
+    /**
+     * Accepts a visitor, which then visits each feature in the collection.
+     * @throws IOException
+     */
+    public void accepts(FeatureVisitor visitor, ProgressListener progress ) throws IOException {
+        Iterator iterator = null;
+        // if( progress == null ) progress = new NullProgressListener();
+        try{
+            float size = size();
+            float position = 0;
+            progress.started();
+            for( iterator = iterator(); !progress.isCanceled() && iterator.hasNext(); progress.progress( position++/size )){
+                try {
+                    Feature feature = (Feature) iterator.next();
+                    visitor.visit(feature);
+                }
+                catch( Exception erp ){
+                    progress.exceptionOccurred( erp );
+                }
+            }
+        }
+        finally {
+            progress.complete();
+            close( iterator );
+        }
+    }
+
+        public FeatureReader reader() throws IOException {
+                return new DelegateFeatureReader( getSchema(), features() );
+        }
+
+        public int getCount() throws IOException {
+                return size();
+        }
+
+        public FeatureCollection collection() throws IOException {
+                return this;
+        }
+
+        public FeatureList sort(SortBy order) {
+                return null;
+        }
+
+        public void purge() {
+                collection.purge();
+        }
+}

Added: trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.1
===================================================================
--- trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.1	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.1	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,704 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.gui.swing;
+/**
+ * @author Ian Turton
+ *
+ */
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.filter.Filter;
+import org.geotools.filter.FilterFactory;
+import org.geotools.filter.FilterFactoryFinder;
+import org.geotools.filter.GeometryFilter;
+import org.geotools.filter.IllegalFilterException;
+import org.geotools.gui.swing.event.HighlightChangeListener;
+import org.geotools.gui.swing.event.HighlightChangedEvent;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.styling.Graphic;
+import org.geotools.styling.LineSymbolizer;
+import org.geotools.styling.Mark;
+import org.geotools.styling.PointSymbolizer;
+import org.geotools.styling.PolygonSymbolizer;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.geotools.styling.StyleFactory;
+import org.geotools.styling.StyleFactoryFinder;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.3.1 to suppress the {@code System.out}-messages
+ *    in {@link #setState(int)} and {@link #paintComponent(Graphics)}.</b><br><br>
+ *
+ * A simple map container that is a JPanel with a map in.
+ * provides simple pan,zoom, highlight and selection
+ * The mappane stores an image of the map (drawn from the context) and
+ * an image of the slected feature(s) to speed up
+ * rendering of the highlights. Thus the whole map is only redrawn
+ * when the bbox changes, selection is only redrawn when the
+ * selected feature changes.
+ *
+ * If you intend to use this in production code you'll
+ * need to make selection and highlighting work in the same way.
+ * @author Ian Turton
+ *
+ */
+public class JMapPane extends JPanel implements MouseListener,
+        HighlightChangeListener, PropertyChangeListener {
+    /**
+     * what renders the map
+     */
+    GTRenderer renderer;
+
+    /**
+     * the map context to render
+     */
+    MapContext context;
+
+    private MapContext selectionContext;
+
+    /**
+     * the area of the map to draw
+     */
+    Envelope mapArea;
+
+    /**
+     * the size of the pane last time we drew
+     */
+    private Rectangle oldRect = null;
+
+    /**
+     * the last map area drawn.
+     */
+    private Envelope oldMapArea = null;
+
+    /**
+     * the base image of the map
+     */
+    private BufferedImage baseImage;
+    /**
+     * image of selection
+     */
+    private BufferedImage selectImage;
+    /**
+     * style for selected items
+     */
+    private Style selectionStyle;
+    /**
+     * layer that selection works on
+     */
+    private int selectionLayer = -1;
+    /**
+     * layer that highlight works on
+     */
+    private MapLayer highlightLayer;
+    /**
+     * the object which manages highlighting
+     */
+    private HighlightManager highlightManager;
+    /**
+     * is highlighting on or off
+     */
+    private boolean highlight = true;
+    /**
+     * a factory for filters
+     */
+    FilterFactory ff = FilterFactoryFinder.createFilterFactory();
+    /**
+     * a factory for geometries
+     */
+    GeometryFactory gf = new GeometryFactory();
+    /**
+     * the collections of features to be selected or highlighted
+     */
+    FeatureCollection selection, highlightFeature;
+
+    public static final int Reset = 0;
+
+    public static final int ZoomIn = 1;
+
+    public static final int ZoomOut = 2;
+
+    public static final int Pan = 3;
+
+    public static final int Select = 4;
+
+    private int state = ZoomIn;
+
+    /**
+     * how far to zoom in or out
+     */
+    private double zoomFactor = 2.0;
+
+    Style lineHighlightStyle;
+
+    Style pointHighlightStyle;
+
+    Style polygonHighlightStyle;
+
+    Style polygonSelectionStyle;
+
+    Style pointSelectionStyle;
+
+    Style lineSelectionStyle;
+
+    public JMapPane() {
+        this(null, true, null, null);
+    }
+    /**
+     * create a basic JMapPane
+     * @param render - how to draw the map
+     * @param context - the map context to display
+     */
+    public JMapPane(GTRenderer render, MapContext context) {
+        this(null, true, render, context);
+    }
+    /**
+     * full constructor extending JPanel
+     * @param layout - layout (probably shouldn't be set)
+     * @param isDoubleBuffered - a Swing thing I don't really understand
+     * @param render - what to draw the map with
+     * @param context - what to draw
+     */
+    public JMapPane(LayoutManager layout, boolean isDoubleBuffered,
+            GTRenderer render, MapContext context) {
+        super(layout, isDoubleBuffered);
+        setRenderer(render);
+
+        setContext(context);
+
+        this.addMouseListener(this);
+        setHighlightManager(new HighlightManager(highlightLayer));
+
+        lineHighlightStyle = setupStyle(LINE, Color.red);
+
+        pointHighlightStyle = setupStyle(POINT, Color.red);
+
+        polygonHighlightStyle = setupStyle(POLYGON, Color.red);
+
+        polygonSelectionStyle = setupStyle(POLYGON, Color.cyan);
+
+        pointSelectionStyle = setupStyle(POINT, Color.cyan);
+
+        lineSelectionStyle = setupStyle(LINE, Color.cyan);
+
+    }
+    /**
+     * get the renderer
+     */
+
+    public GTRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(GTRenderer renderer) {
+        this.renderer = renderer;
+
+        if (this.context != null) {
+            this.renderer.setContext(this.context);
+
+        }
+    }
+
+    public MapContext getContext() {
+        return context;
+    }
+
+    public void setContext(MapContext context) {
+
+        this.context = context;
+
+        if (renderer != null) {
+            renderer.setContext(this.context);
+
+        }
+    }
+
+    public Envelope getMapArea() {
+        return mapArea;
+    }
+
+    public void setMapArea(Envelope mapArea) {
+        this.mapArea = mapArea;
+    }
+
+    public int getState() {
+        return state;
+    }
+
+    public void setState(int state) {
+        this.state = state;
+//        System.out.println("State: " + state);
+    }
+
+    public double getZoomFactor() {
+        return zoomFactor;
+    }
+
+    public void setZoomFactor(double zoomFactor) {
+        this.zoomFactor = zoomFactor;
+    }
+
+    public int getSelectionLayer() {
+        return selectionLayer;
+    }
+
+    public void setSelectionLayer(int selectionLayer) {
+        this.selectionLayer = selectionLayer;
+    }
+
+    public boolean isHighlight() {
+        return highlight;
+    }
+
+    public void setHighlight(boolean highlight) {
+        this.highlight = highlight;
+    }
+
+    public MapLayer getHighlightLayer() {
+        return highlightLayer;
+    }
+
+    public void setHighlightLayer(MapLayer highlightLayer) {
+        this.highlightLayer = highlightLayer;
+        if (highlightManager != null) {
+            highlightManager.setHighlightLayer(highlightLayer);
+        }
+    }
+
+    public HighlightManager getHighlightManager() {
+        return highlightManager;
+    }
+
+    public void setHighlightManager(HighlightManager highlightManager) {
+        this.highlightManager = highlightManager;
+        this.highlightManager.addHighlightChangeListener(this);
+        this.addMouseMotionListener(this.highlightManager);
+    }
+
+    public Style getLineHighlightStyle() {
+        return lineHighlightStyle;
+    }
+
+    public void setLineHighlightStyle(Style lineHighlightStyle) {
+        this.lineHighlightStyle = lineHighlightStyle;
+    }
+
+    public Style getLineSelectionStyle() {
+        return lineSelectionStyle;
+    }
+
+    public void setLineSelectionStyle(Style lineSelectionStyle) {
+        this.lineSelectionStyle = lineSelectionStyle;
+    }
+
+    public Style getPointHighlightStyle() {
+        return pointHighlightStyle;
+    }
+
+    public void setPointHighlightStyle(Style pointHighlightStyle) {
+        this.pointHighlightStyle = pointHighlightStyle;
+    }
+
+    public Style getPointSelectionStyle() {
+        return pointSelectionStyle;
+    }
+
+    public void setPointSelectionStyle(Style pointSelectionStyle) {
+        this.pointSelectionStyle = pointSelectionStyle;
+    }
+
+    public Style getPolygonHighlightStyle() {
+        return polygonHighlightStyle;
+    }
+
+    public void setPolygonHighlightStyle(Style polygonHighlightStyle) {
+        this.polygonHighlightStyle = polygonHighlightStyle;
+    }
+
+    public Style getPolygonSelectionStyle() {
+        return polygonSelectionStyle;
+    }
+
+    public void setPolygonSelectionStyle(Style polygonSelectionStyle) {
+        this.polygonSelectionStyle = polygonSelectionStyle;
+    }
+
+    private boolean reset = false;
+
+    protected void paintComponent(Graphics g) {
+        boolean changed = false;
+        super.paintComponent(g);
+        if (renderer == null || mapArea == null) {
+            return;
+        }
+        Rectangle r = getBounds();
+        Rectangle dr = new Rectangle(r.width, r.height);
+        if (!r.equals(oldRect) || reset) {
+            /*either the viewer size has changed or we've done a reset*/
+            changed = true; /* note we need to redraw */
+            reset = false; /* forget about the reset */
+            oldRect = r; /* store what the current size is */
+            double mapWidth = mapArea.getWidth(); /* get the extent of the map*/
+            double mapHeight = mapArea.getHeight();
+            double scaleX = r.getWidth() / mapArea.getWidth(); /* calculate the new scale*/
+            double scaleY = r.getHeight() / mapArea.getHeight();
+            double scale = 1.0; // stupid compiler!
+            if (scaleX < scaleY) {/*pick the smaller scale */
+                scale = scaleX;
+            } else {
+                scale = scaleY;
+            }
+            /* calculate the difference in width and height of the new extent*/
+            double deltaX = /*Math.abs*/((r.getWidth() / scale) - mapWidth);
+            double deltaY = /*Math.abs*/((r.getHeight() / scale) - mapHeight);
+
+//            System.out.println("delta x "+deltaX); System.out.println("delta y "+deltaY);
+            /* create the new extent */
+            Coordinate ll = new Coordinate(mapArea.getMinX() - (deltaX / 2.0),
+                    mapArea.getMinY() - (deltaY / 2.0));
+            Coordinate ur = new Coordinate(mapArea.getMaxX() + (deltaX / 2.0),
+                    mapArea.getMaxY() + (deltaY / 2.0));
+            mapArea = new Envelope(ll, ur);
+        }
+        if (!mapArea.equals(oldMapArea)) {/* did the map extent change?*/
+            changed = true;
+            oldMapArea = mapArea;
+        }
+        if (changed) {/* if the map changed then redraw*/
+
+            baseImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D ig = baseImage.createGraphics();
+            /* System.out.println("rendering"); */
+            renderer.setContext(context);
+            renderer.paint((Graphics2D) ig, dr, mapArea);
+        }
+        ((Graphics2D) g).drawImage(baseImage, 0, 0, this);
+        if (selection != null && selection.size() > 0) {
+            // paint selection
+            /*
+             * String type = selection.getDefaultGeometry().getGeometryType();
+             * System.out.println(type); if(type==null) type="polygon";
+             */
+            String type = "polygon";
+            if (type.equalsIgnoreCase("polygon")) {
+
+                selectionStyle = polygonSelectionStyle;
+            } else if (type.equalsIgnoreCase("point")) {
+
+                selectionStyle = pointSelectionStyle;
+            } else if (type.equalsIgnoreCase("line")) {
+
+                selectionStyle = lineSelectionStyle;
+            }
+
+            selectionContext = new DefaultMapContext();
+
+            selectionContext.addLayer(selection, selectionStyle);
+            renderer.setContext(selectionContext);
+
+            selectImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D ig = selectImage.createGraphics();
+            /* System.out.println("rendering selection"); */
+            renderer.paint((Graphics2D) ig, dr, mapArea);
+
+            ((Graphics2D) g).drawImage(selectImage, 0, 0, this);
+        }
+        if (highlight && highlightFeature != null
+                && highlightFeature.size() > 0) {
+            /*
+             * String type = selection.getDefaultGeometry().getGeometryType();
+             * System.out.println(type); if(type==null) type="polygon";
+             */
+            String type = "polygon";
+            Style highlightStyle = null;
+            if (type.equalsIgnoreCase("polygon")) {
+
+                highlightStyle = polygonHighlightStyle;
+            } else if (type.equalsIgnoreCase("point")) {
+
+                highlightStyle = pointHighlightStyle;
+            } else if (type.equalsIgnoreCase("line")) {
+
+                highlightStyle = lineHighlightStyle;
+            }
+
+            MapContext highlightContext = new DefaultMapContext();
+
+            highlightContext.addLayer(highlightFeature, highlightStyle);
+            renderer.setContext(highlightContext);
+
+            /* System.out.println("rendering highlight"); */
+            renderer.paint((Graphics2D) g, dr, mapArea);
+
+        }
+    }
+
+    public FeatureCollection doSelection(double x, double y, int layer) {
+        GeometryFilter f = null;
+        FeatureCollection select = null;
+        Geometry geometry = gf.createPoint(new Coordinate(x, y));
+        try {
+            f = ff.createGeometryFilter(GeometryFilter.GEOMETRY_CONTAINS);
+            f.addRightGeometry(ff.createLiteralExpression(geometry));
+        } catch (IllegalFilterException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        if (layer == -1) {
+            for (int i = 0; i < context.getLayers().length; i++) {
+                FeatureCollection fx = findFeature(f, i);
+                if (select != null) {
+                    select.addAll(fx);
+                } else {
+                    select = fx;
+                }
+            }
+        } else {
+            select = findFeature(f, layer);
+        }
+        return select;
+    }
+
+    /**
+     * @param f -
+     *            a partial geometry filter. The geom name will be added
+     * @param i -
+     *            the index of the layer to search
+     * @throws IndexOutOfBoundsException
+     */
+    private FeatureCollection findFeature(GeometryFilter f, int i)
+            throws IndexOutOfBoundsException {
+        FeatureCollection fcol = null;
+        if (context != null && i > context.getLayers().length) {
+            return fcol;
+        }
+        MapLayer layer = context.getLayer(i);
+
+        try {
+            String name = layer.getFeatureSource().getSchema()
+                    .getDefaultGeometry().getName();
+            if (name == "")
+                name = "the_geom";
+            f.addLeftGeometry(ff.createAttributeExpression(name));
+            // System.out.println("looking with " + f);
+            FeatureCollection fc = layer.getFeatureSource().getFeatures(f);
+            if (fc.size() > 0) {
+                // selectionStyle.getFeatureTypeStyles()[0].getRules()[0].setFilter(f);
+                selectionLayer = i;
+            }
+            if (fcol == null) {
+                fcol = fc;
+                // here we should set the defaultgeom type
+            } else {
+                fcol.addAll(fc);
+            }
+            /*
+             * GeometryAttributeType gat =
+             * layer.getFeatureSource().getSchema().getDefaultGeometry();
+             * fcol.setDefaultGeometry((Geometry)gat.createDefaultValue());
+             */
+
+            /*
+             * Iterator fi = fc.iterator(); while (fi.hasNext()) { Feature feat =
+             * (Feature) fi.next(); System.out.println("selected " +
+             * feat.getAttribute("STATE_NAME")); }
+             */
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalFilterException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }/*
+             * catch (IllegalAttributeException e) { // TODO Auto-generated
+             * catch block System.err.println(e.getMessage()); //
+             * e.printStackTrace(); }
+             */
+        return fcol;
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        // TODO Auto-generated method stub
+        // System.out.println("before area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        Rectangle bounds = this.getBounds();
+        double x = (double) (e.getX());
+        double y = (double) (e.getY());
+        double width = mapArea.getWidth();
+        double height = mapArea.getHeight();
+        double width2 = mapArea.getWidth() / 2.0;
+        double height2 = mapArea.getHeight() / 2.0;
+
+        double mapX = (x * width / (double) bounds.width) + mapArea.getMinX();
+        double mapY = ((bounds.getHeight() - y) * height / (double) bounds.height)
+                + mapArea.getMinY();
+        /*
+         * System.out.println(""+x+"->"+mapX);
+         * System.out.println(""+y+"->"+mapY);
+         */
+        /*
+         * Coordinate ll = new Coordinate(mapArea.getMinX(), mapArea.getMinY());
+         * Coordinate ur = new Coordinate(mapArea.getMaxX(), mapArea.getMaxY());
+         */
+        double zlevel = 1.0;
+        switch (state) {
+        case Pan:
+            zlevel = 1.0;
+            break;
+        case ZoomIn:
+            zlevel = zoomFactor;
+            break;
+        case ZoomOut:
+            zlevel = 1.0 / zoomFactor;
+            break;
+        case Select:
+            selection = doSelection(mapX, mapY, selectionLayer);
+            repaint();
+            return;
+        default:
+            return;
+        }
+        Coordinate ll = new Coordinate(mapX - (width2 / zlevel), mapY
+                - (height2 / zlevel));
+        Coordinate ur = new Coordinate(mapX + (width2 / zlevel), mapY
+                + (height2 / zlevel));
+
+        mapArea = new Envelope(ll, ur);
+        // System.out.println("after area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        repaint();
+    }
+
+    public void mouseEntered(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void mouseExited(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void mousePressed(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void mouseReleased(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    private static final int POLYGON = 0;
+
+    private static final int LINE = 1;
+
+    private static final int POINT = 2;
+
+    private org.geotools.styling.Style setupStyle(int type, Color color) {
+        StyleFactory sf = StyleFactoryFinder.createStyleFactory();
+        StyleBuilder sb = new StyleBuilder(sf, ff);
+
+        org.geotools.styling.Style s = sf.createStyle();
+        s.setTitle("selection");
+
+        // TODO parameterise the color
+        PolygonSymbolizer ps = sb.createPolygonSymbolizer(color);
+        ps.setStroke(sb.createStroke(color));
+        LineSymbolizer ls = sb.createLineSymbolizer(color);
+        Graphic h = sb.createGraphic();
+        h.setMarks(new Mark[] { sb.createMark("square", color) });
+        PointSymbolizer pts = sb.createPointSymbolizer(h);
+
+        // Rule r = sb.createRule(new Symbolizer[]{ps,ls,pts});
+        switch (type) {
+        case POLYGON:
+            s = sb.createStyle(ps);
+            break;
+        case POINT:
+            s = sb.createStyle(pts);
+            break;
+        case LINE:
+            s = sb.createStyle(ls);
+        }
+
+        return s;
+
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void highlightChanged(HighlightChangedEvent e) {
+        // TODO Auto-generated method stub
+
+        Filter f = e.getFilter();
+        try {
+            highlightFeature = highlightLayer.getFeatureSource().getFeatures(f);
+        } catch (IOException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+        }
+        repaint();
+
+    }
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        // TODO Auto-generated method stub
+        String prop = evt.getPropertyName();
+        if (prop.equalsIgnoreCase("crs")) {
+            context.setAreaOfInterest(context.getAreaOfInterest(),
+                    (CoordinateReferenceSystem) evt.getNewValue());
+        }
+    }
+
+    public boolean isReset() {
+        return reset;
+    }
+
+    public void setReset(boolean reset) {
+        this.reset = reset;
+    }
+}
+

Added: trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.4
===================================================================
--- trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.4	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/gui/swing/JMapPane.GT2-2.3.4	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,821 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.gui.swing;
+/**
+ * @author Ian Turton
+ *
+ */
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.filter.Filter;
+import org.geotools.filter.FilterFactory;
+import org.geotools.filter.FilterFactoryFinder;
+import org.geotools.filter.GeometryFilter;
+import org.geotools.filter.IllegalFilterException;
+import org.geotools.gui.swing.event.HighlightChangeListener;
+import org.geotools.gui.swing.event.HighlightChangedEvent;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.styling.Graphic;
+import org.geotools.styling.LineSymbolizer;
+import org.geotools.styling.Mark;
+import org.geotools.styling.PointSymbolizer;
+import org.geotools.styling.PolygonSymbolizer;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.geotools.styling.StyleFactory;
+import org.geotools.styling.StyleFactoryFinder;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.3.4 to suppress the {@code System.out}-messages
+ *    in {@link #setState(int)} and {@link #paintComponent(Graphics)}.</b><br><br>
+ * A simple map container that is a JPanel with a map in.
+ * provides simple pan,zoom, highlight and selection
+ * The mappane stores an image of the map (drawn from the context) and
+ * an image of the slected feature(s) to speed up
+ * rendering of the highlights. Thus the whole map is only redrawn
+ * when the bbox changes, selection is only redrawn when the
+ * selected feature changes.
+ *
+ * If you intend to use this in production code you'll
+ * need to make selection and highlighting work in the same way.
+ * @author Ian Turton
+ *
+ */
+/**
+ * @author ijt1
+ *
+ */
+/**
+ * @author ijt1
+ *
+ */
+public class JMapPane extends JPanel implements MouseListener,
+        HighlightChangeListener, PropertyChangeListener {
+    /**
+     * what renders the map
+     */
+    GTRenderer renderer;
+
+    /**
+     * the map context to render
+     */
+    MapContext context;
+
+    private MapContext selectionContext;
+
+    /**
+     * the area of the map to draw
+     */
+    protected Envelope mapArea;
+
+    /**
+     * the size of the pane last time we drew
+     */
+    protected Rectangle oldRect = null;
+
+    /**
+     * the last map area drawn.
+     */
+    protected Envelope oldMapArea = null;
+
+    /**
+     * the base image of the map
+     */
+    private BufferedImage baseImage;
+    /**
+     * image of selection
+     */
+    private BufferedImage selectImage;
+    /**
+     * style for selected items
+     */
+    private Style selectionStyle;
+    /**
+     * layer that selection works on
+     */
+    private int selectionLayer = -1;
+    /**
+     * layer that highlight works on
+     */
+    private MapLayer highlightLayer;
+    /**
+     * the object which manages highlighting
+     */
+    private HighlightManager highlightManager;
+    /**
+     * is highlighting on or off
+     */
+    private boolean highlight = true;
+    /**
+     * a factory for filters
+     */
+    FilterFactory ff = FilterFactoryFinder.createFilterFactory();
+    /**
+     * a factory for geometries
+     */
+    GeometryFactory gf = new GeometryFactory();
+    /**
+     * the collections of features to be selected or highlighted
+     */
+    FeatureCollection selection, highlightFeature;
+
+    public static final int Reset = 0;
+
+    public static final int ZoomIn = 1;
+
+    public static final int ZoomOut = 2;
+
+    public static final int Pan = 3;
+
+    public static final int Select = 4;
+
+    private int state = ZoomIn;
+
+    /**
+     * how far to zoom in or out
+     */
+    private double zoomFactor = 2.0;
+
+    Style lineHighlightStyle;
+
+    Style pointHighlightStyle;
+
+    Style polygonHighlightStyle;
+
+    Style polygonSelectionStyle;
+
+    Style pointSelectionStyle;
+
+    Style lineSelectionStyle;
+
+    public JMapPane() {
+        this(null, true, null, null);
+    }
+    /**
+     * create a basic JMapPane
+     * @param render - how to draw the map
+     * @param context - the map context to display
+     */
+    public JMapPane(GTRenderer render, MapContext context) {
+        this(null, true, render, context);
+    }
+    /**
+     * full constructor extending JPanel
+     * @param layout - layout (probably shouldn't be set)
+     * @param isDoubleBuffered - a Swing thing I don't really understand
+     * @param render - what to draw the map with
+     * @param context - what to draw
+     */
+    public JMapPane(LayoutManager layout, boolean isDoubleBuffered,
+            GTRenderer render, MapContext context) {
+        super(layout, isDoubleBuffered);
+        setRenderer(render);
+
+        setContext(context);
+
+        this.addMouseListener(this);
+        setHighlightManager(new HighlightManager(highlightLayer));
+
+        lineHighlightStyle = setupStyle(LINE, Color.red);
+
+        pointHighlightStyle = setupStyle(POINT, Color.red);
+
+        polygonHighlightStyle = setupStyle(POLYGON, Color.red);
+
+        polygonSelectionStyle = setupStyle(POLYGON, Color.cyan);
+
+        pointSelectionStyle = setupStyle(POINT, Color.cyan);
+
+        lineSelectionStyle = setupStyle(LINE, Color.cyan);
+
+    }
+    /**
+     * get the renderer
+     */
+
+    public GTRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(GTRenderer renderer) {
+        this.renderer = renderer;
+
+        if (this.context != null) {
+            this.renderer.setContext(this.context);
+
+        }
+    }
+
+    public MapContext getContext() {
+        return context;
+    }
+
+    public void setContext(MapContext context) {
+
+        this.context = context;
+
+        if (renderer != null) {
+            renderer.setContext(this.context);
+
+        }
+    }
+
+    public final Envelope getMapArea() {
+        return mapArea;
+    }
+
+    public void setMapArea(Envelope mapArea) {
+        this.mapArea = mapArea;
+    }
+
+    public int getState() {
+        return state;
+    }
+
+    public void setState(int state) {
+        this.state = state;
+//-- LINED OUT FOR XULU --
+//        System.out.println("State: " + state);
+    }
+
+    public double getZoomFactor() {
+        return zoomFactor;
+    }
+
+    public void setZoomFactor(double zoomFactor) {
+        this.zoomFactor = zoomFactor;
+    }
+
+    public int getSelectionLayer() {
+        return selectionLayer;
+    }
+
+    public void setSelectionLayer(int selectionLayer) {
+        this.selectionLayer = selectionLayer;
+    }
+
+    public boolean isHighlight() {
+        return highlight;
+    }
+
+    public void setHighlight(boolean highlight) {
+        this.highlight = highlight;
+    }
+
+    public MapLayer getHighlightLayer() {
+        return highlightLayer;
+    }
+
+    public void setHighlightLayer(MapLayer highlightLayer) {
+        this.highlightLayer = highlightLayer;
+        if (highlightManager != null) {
+            highlightManager.setHighlightLayer(highlightLayer);
+        }
+    }
+
+    public HighlightManager getHighlightManager() {
+        return highlightManager;
+    }
+
+    public void setHighlightManager(HighlightManager highlightManager) {
+        this.highlightManager = highlightManager;
+        this.highlightManager.addHighlightChangeListener(this);
+        this.addMouseMotionListener(this.highlightManager);
+    }
+
+    public Style getLineHighlightStyle() {
+        return lineHighlightStyle;
+    }
+
+    public void setLineHighlightStyle(Style lineHighlightStyle) {
+        this.lineHighlightStyle = lineHighlightStyle;
+    }
+
+    public Style getLineSelectionStyle() {
+        return lineSelectionStyle;
+    }
+
+    public void setLineSelectionStyle(Style lineSelectionStyle) {
+        this.lineSelectionStyle = lineSelectionStyle;
+    }
+
+    public Style getPointHighlightStyle() {
+        return pointHighlightStyle;
+    }
+
+    public void setPointHighlightStyle(Style pointHighlightStyle) {
+        this.pointHighlightStyle = pointHighlightStyle;
+    }
+
+    public Style getPointSelectionStyle() {
+        return pointSelectionStyle;
+    }
+
+    public void setPointSelectionStyle(Style pointSelectionStyle) {
+        this.pointSelectionStyle = pointSelectionStyle;
+    }
+
+    public Style getPolygonHighlightStyle() {
+        return polygonHighlightStyle;
+    }
+
+    public void setPolygonHighlightStyle(Style polygonHighlightStyle) {
+        this.polygonHighlightStyle = polygonHighlightStyle;
+    }
+
+    public Style getPolygonSelectionStyle() {
+        return polygonSelectionStyle;
+    }
+
+    public void setPolygonSelectionStyle(Style polygonSelectionStyle) {
+        this.polygonSelectionStyle = polygonSelectionStyle;
+    }
+
+    protected boolean reset = false;
+
+	private Double maxZoomScale = Double.MIN_VALUE;
+	private Double minZoomScale = Double.MAX_VALUE;
+
+	/**
+	 * SK 27.9.2007: Auch hier final bei den Variablen einfegfuegt, da die Routine so oft aufgerufen wird.
+	 */
+    protected void paintComponent(final Graphics g) {
+        boolean changed = false;
+        super.paintComponent(g);
+        if (renderer == null || mapArea == null) {
+            return;
+        }
+        final Rectangle r = getBounds();
+        final Rectangle dr = new Rectangle(r.width, r.height);
+        if (!r.equals(oldRect) || reset) {
+            /*either the viewer size has changed or we've done a reset*/
+            changed = true; /* note we need to redraw */
+            reset = false; /* forget about the reset */
+            oldRect = r; /* store what the current size is */
+            final double mapWidth = mapArea.getWidth(); /* get the extent of the map*/
+            final double mapHeight = mapArea.getHeight();
+            final double scaleX = r.getWidth() / mapArea.getWidth(); /* calculate the new scale*/
+            final double scaleY = r.getHeight() / mapArea.getHeight();
+            double scale = 1.0; // stupid compiler!
+            if (scaleX < scaleY) {/*pick the smaller scale */
+                scale = scaleX;
+            } else {
+                scale = scaleY;
+            }
+            /* calculate the difference in width and height of the new extent*/
+            final double deltaX = /*Math.abs*/((r.getWidth() / scale) - mapWidth);
+            final double deltaY = /*Math.abs*/((r.getHeight() / scale) - mapHeight);
+// -- LINED OUT FOR XULU --
+//            System.out.println("delta x "+deltaX); System.out.println("delta y "+deltaY);
+            /* create the new extent */
+            final Coordinate ll = new Coordinate(mapArea.getMinX() - (deltaX / 2.0),
+                    mapArea.getMinY() - (deltaY / 2.0));
+            final Coordinate ur = new Coordinate(mapArea.getMaxX() + (deltaX / 2.0),
+                    mapArea.getMaxY() + (deltaY / 2.0));
+            mapArea = new Envelope(ll, ur);
+        }
+        if (!mapArea.equals(oldMapArea)) {/* did the map extent change?*/
+            changed = true;
+            oldMapArea = mapArea;
+        }
+        if (changed) {/* if the map changed then redraw*/
+
+            baseImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+            final Graphics2D ig = baseImage.createGraphics();
+            /* System.out.println("rendering"); */
+            renderer.setContext(context);
+            renderer.paint((Graphics2D) ig, dr, mapArea);
+        }
+        ((Graphics2D) g).drawImage(baseImage, 0, 0, this);
+        if (selection != null && selection.size() > 0) {
+            // paint selection
+            /*
+             * String type = selection.getDefaultGeometry().getGeometryType();
+             * System.out.println(type); if(type==null) type="polygon";
+             */
+            final String type = "polygon";
+            if (type.equalsIgnoreCase("polygon")) {
+
+                selectionStyle = polygonSelectionStyle;
+            } else if (type.equalsIgnoreCase("point")) {
+
+                selectionStyle = pointSelectionStyle;
+            } else if (type.equalsIgnoreCase("line")) {
+
+                selectionStyle = lineSelectionStyle;
+            }
+
+            selectionContext = new DefaultMapContext();
+
+            selectionContext.addLayer(selection, selectionStyle);
+            renderer.setContext(selectionContext);
+
+            selectImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+            final Graphics2D ig = selectImage.createGraphics();
+            /* System.out.println("rendering selection"); */
+            renderer.paint((Graphics2D) ig, dr, mapArea);
+
+            ((Graphics2D) g).drawImage(selectImage, 0, 0, this);
+        }
+        if (highlight && highlightFeature != null
+                && highlightFeature.size() > 0) {
+            /*
+             * String type = selection.getDefaultGeometry().getGeometryType();
+             * System.out.println(type); if(type==null) type="polygon";
+             */
+            final String type = "polygon";
+            Style highlightStyle = null;
+            if (type.equalsIgnoreCase("polygon")) {
+
+                highlightStyle = polygonHighlightStyle;
+            } else if (type.equalsIgnoreCase("point")) {
+
+                highlightStyle = pointHighlightStyle;
+            } else if (type.equalsIgnoreCase("line")) {
+
+                highlightStyle = lineHighlightStyle;
+            }
+
+            final MapContext highlightContext = new DefaultMapContext();
+
+            highlightContext.addLayer(highlightFeature, highlightStyle);
+            renderer.setContext(highlightContext);
+
+            /* System.out.println("rendering highlight"); */
+            renderer.paint((Graphics2D) g, dr, mapArea);
+
+        }
+    }
+
+    public FeatureCollection doSelection(double x, double y, int layer) {
+        GeometryFilter f = null;
+        FeatureCollection select = null;
+        Geometry geometry = gf.createPoint(new Coordinate(x, y));
+        try {
+            f = ff.createGeometryFilter(GeometryFilter.GEOMETRY_CONTAINS);
+            f.addRightGeometry(ff.createLiteralExpression(geometry));
+        } catch (IllegalFilterException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        if (layer == -1) {
+            for (int i = 0; i < context.getLayers().length; i++) {
+                FeatureCollection fx = findFeature(f, i);
+                if (select != null) {
+                    select.addAll(fx);
+                } else {
+                    select = fx;
+                }
+            }
+        } else {
+            select = findFeature(f, layer);
+        }
+        return select;
+    }
+
+    /**
+     * @param f -
+     *            a partial geometry filter. The geom name will be added
+     * @param i -
+     *            the index of the layer to search
+     * @throws IndexOutOfBoundsException
+     */
+    private FeatureCollection findFeature(GeometryFilter f, int i)
+            throws IndexOutOfBoundsException {
+        FeatureCollection fcol = null;
+        if (context != null && i > context.getLayers().length) {
+            return fcol;
+        }
+        MapLayer layer = context.getLayer(i);
+
+        try {
+            String name = layer.getFeatureSource().getSchema()
+                    .getDefaultGeometry().getName();
+            if (name == "")
+                name = "the_geom";
+            f.addLeftGeometry(ff.createAttributeExpression(name));
+            // System.out.println("looking with " + f);
+            FeatureCollection fc = layer.getFeatureSource().getFeatures(f);
+            if (fc.size() > 0) {
+                // selectionStyle.getFeatureTypeStyles()[0].getRules()[0].setFilter(f);
+                selectionLayer = i;
+            }
+            if (fcol == null) {
+                fcol = fc;
+                // here we should set the defaultgeom type
+            } else {
+                fcol.addAll(fc);
+            }
+            /*
+             * GeometryAttributeType gat =
+             * layer.getFeatureSource().getSchema().getDefaultGeometry();
+             * fcol.setDefaultGeometry((Geometry)gat.createDefaultValue());
+             */
+
+            /*
+             * Iterator fi = fc.iterator(); while (fi.hasNext()) { Feature feat =
+             * (Feature) fi.next(); System.out.println("selected " +
+             * feat.getAttribute("STATE_NAME")); }
+             */
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalFilterException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }/*
+             * catch (IllegalAttributeException e) { // TODO Auto-generated
+             * catch block System.err.println(e.getMessage()); //
+             * e.printStackTrace(); }
+             */
+        return fcol;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+     */
+    public void mouseClicked(MouseEvent e) {
+        // System.out.println("before area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        Rectangle bounds = this.getBounds();
+        double x = (double) (e.getX());
+        double y = (double) (e.getY());
+        double width = mapArea.getWidth();
+        double height = mapArea.getHeight();
+
+        double width2 = width / 2.0;
+        double height2 = height / 2.0;
+
+        double mapX = (x * width / (double) bounds.width) + mapArea.getMinX();
+        double mapY = ((bounds.getHeight() - y) * height / (double) bounds.height)
+                + mapArea.getMinY();
+        /*
+         * System.out.println(""+x+"->"+mapX);
+         * System.out.println(""+y+"->"+mapY);
+         */
+        /*
+         * Coordinate ll = new Coordinate(mapArea.getMinX(), mapArea.getMinY());
+         * Coordinate ur = new Coordinate(mapArea.getMaxX(), mapArea.getMaxY());
+         */
+
+        double zlevel = 1.0;
+        switch (state) {
+	        case Pan:
+	            zlevel = 1.0;
+
+	            // Changed by SK: return here.. a mouselistener is amanaging the PANNING
+	            return;
+	        case ZoomIn:
+	            zlevel = zoomFactor;
+	            break;
+	        case ZoomOut:
+	            zlevel = 1.0 / zoomFactor;
+	            break;
+	        case Select:
+	            selection = doSelection(mapX, mapY, selectionLayer);
+	            repaint();
+	            return;
+	        default:
+	            return;
+        }
+
+    	//****************************************************************************
+        // Changed by SK:
+    	// performing the zoom and/or pan by recalculating the mapArea
+        // important and new here: don't zoom in/out more that the min/max scale!
+        // n.b.: zoom is not only performed here, but also and with the mouse wheel and setMapArea
+    	//****************************************************************************
+
+    	Coordinate ll = new Coordinate(mapX - (width2 / zlevel), mapY
+    			- (height2 / zlevel));
+    	Coordinate ur = new Coordinate(mapX + (width2 / zlevel), mapY
+    			+ (height2 / zlevel));
+
+    	final Envelope newMapArea = new Envelope(ll, ur);
+
+    	// Hier passiert die Ueberpruefung und ggf Anpassung der Scale
+		mapArea = bestAllowedMapArea( newMapArea );
+
+        // System.out.println("after area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        repaint();
+    }
+
+
+	  /**
+	   * Korrigiert den {@link Envelope} aka mapArea auf die beste erlaubte Flaeche damit
+	   * die Massstabsbeschaenkungen noch eingehalten werden, FALLS der uebergeben Envelope
+	   * nicht schon gueltig sein sollte.
+	   *
+	   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	   */
+	  public Envelope bestAllowedMapArea(Envelope env) {
+		    if (getWidth() == 0) return env;
+		    
+		    if (env == null) return env;
+
+			double scale = env.getWidth() / getWidth();
+
+			double centerX = env.getMinX() + env.getWidth() / 2.;
+			double centerY = env.getMinY() + env.getHeight() / 2.;
+
+			double newWidth2;
+			double newHeight2;
+			if (scale < getMaxZoomScale()){
+
+				//****************************************************************************
+				// Wir zoomen weiter rein als erlaubt => Anpassen des envelope
+				//****************************************************************************
+		    	newWidth2 = getMaxZoomScale() * getWidth() / 2.;
+				newHeight2 = getMaxZoomScale() * getHeight() / 2.;
+			} else
+				if (scale > getMinZoomScale())
+				{
+
+					//****************************************************************************
+					// Wir zoomen weiter raus als erlaubt => Anpassen des envelope
+					//****************************************************************************
+					newWidth2 = getMinZoomScale() * getWidth() / 2.;
+					newHeight2 = getMinZoomScale() * getHeight() / 2.;
+				} else {
+
+					//****************************************************************************
+					// Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
+					//****************************************************************************
+					return env;
+				}
+
+			Coordinate ll = new Coordinate(centerX - newWidth2, centerY
+					- newHeight2);
+			Coordinate ur = new Coordinate(centerX + newWidth2, centerY
+					+ newHeight2);
+
+			return new Envelope(ll, ur);
+	}
+
+	public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+    public void mousePressed(MouseEvent e) {
+    }
+
+    public void mouseReleased(MouseEvent e) {
+    }
+
+    private static final int POLYGON = 0;
+
+    private static final int LINE = 1;
+
+    private static final int POINT = 2;
+
+    private org.geotools.styling.Style setupStyle(int type, Color color) {
+        StyleFactory sf = StyleFactoryFinder.createStyleFactory();
+        StyleBuilder sb = new StyleBuilder(sf, ff);
+
+        org.geotools.styling.Style s = sf.createStyle();
+        s.setTitle("selection");
+
+        // TODO parameterise the color
+        PolygonSymbolizer ps = sb.createPolygonSymbolizer(color);
+        ps.setStroke(sb.createStroke(color));
+        LineSymbolizer ls = sb.createLineSymbolizer(color);
+        Graphic h = sb.createGraphic();
+        h.setMarks(new Mark[] { sb.createMark("square", color) });
+        PointSymbolizer pts = sb.createPointSymbolizer(h);
+
+        // Rule r = sb.createRule(new Symbolizer[]{ps,ls,pts});
+        switch (type) {
+        case POLYGON:
+            s = sb.createStyle(ps);
+            break;
+        case POINT:
+            s = sb.createStyle(pts);
+            break;
+        case LINE:
+            s = sb.createStyle(ls);
+        }
+
+        return s;
+
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void highlightChanged(HighlightChangedEvent e) {
+        // TODO Auto-generated method stub
+
+        Filter f = e.getFilter();
+        try {
+            highlightFeature = highlightLayer.getFeatureSource().getFeatures(f);
+        } catch (IOException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+        }
+        repaint();
+
+    }
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        // TODO Auto-generated method stub
+        String prop = evt.getPropertyName();
+        if (prop.equalsIgnoreCase("crs")) {
+            context.setAreaOfInterest(context.getAreaOfInterest(),
+                    (CoordinateReferenceSystem) evt.getNewValue());
+        }
+    }
+
+    public boolean isReset() {
+        return reset;
+    }
+
+    public void setReset(boolean reset) {
+        this.reset = reset;
+    }
+
+
+
+    /**
+     * Retuns the minimum allowed zoom scale. This is the bigger number value of the two.
+     * Defaults to {@link Double}.MAX_VALUE
+     *
+     * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+     */
+    public Double getMinZoomScale() {
+		return minZoomScale;
+	}
+
+
+	/**
+     * Retuns the maximum allowed zoom scale. This is the smaller number value of the two.
+     * Defaults to {@link Double}.MIN_VALUE
+     *
+     * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+     */
+    public Double getMaxZoomScale() {
+		return maxZoomScale;
+	}
+
+    /**
+     * Set the maximum allowed zoom scale. This is the smaller number value of the two.
+     *
+     * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+     */
+	public void setMaxZoomScale(Double maxZoomScale) {
+		// System.out.println("setting max scale to "+maxZoomScale);
+		this.maxZoomScale = maxZoomScale;
+	}
+	/**
+	 * Set the minimum (nearest) allowed zoom scale. This is the bigger number value of the two.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public void setMinZoomScale(Double minZoomScale) {
+		this.minZoomScale = minZoomScale;
+	}
+}

Added: trunk/src/org/geotools/gui/swing/JMapPane.java
===================================================================
--- trunk/src/org/geotools/gui/swing/JMapPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/gui/swing/JMapPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,1326 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.gui.swing;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.4.2 to make some changes (marked with {@code xulu}),
+ *    which can not be realized in a subclass:</b>
+ *    <ul>
+ *       <li>{@link #getMapArea()} declared as {@code final}<li>
+ *       <li>some variables declared as {@code protected}</li>
+ *       <li>minimal/maximal zoom scale</li>
+ *       <li>zoom in and zoom out via mouse click realized by setMapArea(..)</li>
+ *    </ul>
+ * <br><br>
+ * A simple map container that is a JPanel with a map in. provides simple
+ * pan,zoom, highlight and selection The mappane stores an image of the map
+ * (drawn from the context) and an image of the slected feature(s) to speed up
+ * rendering of the highlights. Thus the whole map is only redrawn when the bbox
+ * changes, selection is only redrawn when the selected feature changes.
+ *
+ *
+ * @author Ian Turton
+ *
+ */
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.filter.IllegalFilterException;
+import org.geotools.gui.swing.event.HighlightChangeListener;
+import org.geotools.gui.swing.event.HighlightChangedEvent;
+import org.geotools.gui.swing.event.SelectionChangeListener;
+import org.geotools.gui.swing.event.SelectionChangedEvent;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.map.event.MapLayerListEvent;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.renderer.lite.LabelCache;
+import org.geotools.renderer.lite.LabelCacheDefault;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.styling.Graphic;
+import org.geotools.styling.LineSymbolizer;
+import org.geotools.styling.Mark;
+import org.geotools.styling.PointSymbolizer;
+import org.geotools.styling.PolygonSymbolizer;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.geotools.styling.StyleFactory;
+import org.opengis.filter.Filter;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+public class JMapPane extends JPanel implements MouseListener,
+        MouseMotionListener, HighlightChangeListener,SelectionChangeListener, PropertyChangeListener,
+        MapLayerListListener {
+    private static Logger LOGGER = Logger.getLogger( JMapPane.class.getName() );
+   /**
+     *
+     */
+    private static final long serialVersionUID = -8647971481359690499L;
+
+    public static final int Reset = 0;
+
+    public static final int ZoomIn = 1;
+
+    public static final int ZoomOut = 2;
+
+    public static final int Pan = 3;
+
+    public static final int Select = 4;
+
+    private static final int POLYGON = 0;
+
+    private static final int LINE = 1;
+
+    private static final int POINT = 2;
+
+    /**
+     * what renders the map
+     */
+    GTRenderer renderer;
+
+    private GTRenderer highlightRenderer, selectionRenderer;
+
+    /**
+     * the map context to render
+     */
+    MapContext context;
+
+    private MapContext selectionContext;
+
+    /**
+     * the area of the map to draw
+     */
+//xulu.sc
+//    Envelope mapArea;
+    protected Envelope mapArea;
+//xulu.ec
+
+    /**
+     * the size of the pane last time we drew
+     */
+//xulu.sc
+//    private Rectangle oldRect = null;
+    protected Rectangle oldRect = null;
+//xulu.ec
+
+    /**
+     * the last map area drawn.
+     */
+//xulu.sc
+//    private Envelope oldMapArea = null;
+    protected Envelope oldMapArea = null;
+//xulu.ec
+
+    /**
+     * the base image of the map
+     */
+    protected  BufferedImage baseImage, panningImage;
+    // SK: private BufferedImage baseImage, panningImage;
+
+    /**
+     * image of selection
+     */
+    private BufferedImage selectImage;
+
+    /**
+     * style for selected items
+     */
+    private Style selectionStyle;
+
+    /**
+     * layer that selection works on
+     */
+    private MapLayer selectionLayer;
+
+    /**
+     * layer that highlight works on
+     */
+    private MapLayer highlightLayer;
+
+    /**
+     * the object which manages highlighting
+     */
+    private HighlightManager highlightManager;
+
+    /**
+     * is highlighting on or off
+     */
+    private boolean highlight = true;
+
+    /**
+     * a factory for filters
+     */
+    FilterFactory2 ff;
+
+    /**
+     * a factory for geometries
+     */
+    GeometryFactory gf = new GeometryFactory(); // FactoryFinder.getGeometryFactory(null);
+
+    /**
+     * the collections of features to be selected or highlighted
+     */
+    FeatureCollection selection;
+
+    /**
+     * the collections of features to be selected or highlighted
+     */
+    FeatureCollection highlightFeature;
+
+    private int state = ZoomIn;
+
+    /**
+     * how far to zoom in or out
+     */
+    private double zoomFactor = 2.0;
+
+    Style lineHighlightStyle;
+
+    Style pointHighlightStyle;
+
+    Style polygonHighlightStyle;
+
+    Style polygonSelectionStyle;
+
+    Style pointSelectionStyle;
+
+    Style lineSelectionStyle;
+
+    boolean changed = true;
+
+    LabelCache labelCache = new LabelCacheDefault();
+
+//xulu.sc
+//    private boolean reset = false;
+    protected boolean reset = false;
+//xulu.ec
+
+    int startX;
+
+    int startY;
+
+    private boolean clickable;
+
+    int lastX;
+
+    int lastY;
+
+    private SelectionManager selectionManager;
+//xulu.sn
+    private Double maxZoomScale = Double.MIN_VALUE;
+    private Double minZoomScale = Double.MAX_VALUE;
+//xulu.en
+
+// sk.sn    
+    /**
+     * Wenn true, dann wurde PANNING via mouseDraged-Events begonnen. Dieses Flag wird benutzt um nur einmal den passenden Cursor nur einmal zu setzen.
+     */
+	private boolean panning_started = false;
+// sk.en	
+
+    public JMapPane() {
+        this(null, true, null, null);
+    }
+
+    /**
+     * create a basic JMapPane
+     *
+     * @param render -
+     *            how to draw the map
+     * @param context -
+     *            the map context to display
+     */
+    public JMapPane(GTRenderer render, MapContext context) {
+        this(null, true, render, context);
+    }
+
+    /**
+     * full constructor extending JPanel
+     *
+     * @param layout -
+     *            layout (probably shouldn't be set)
+     * @param isDoubleBuffered -
+     *            a Swing thing I don't really understand
+     * @param render -
+     *            what to draw the map with
+     * @param context -
+     *            what to draw
+     */
+    public JMapPane(LayoutManager layout, boolean isDoubleBuffered,
+            GTRenderer render, MapContext context) {
+        super(layout, isDoubleBuffered);
+
+        ff = (FilterFactory2) org.geotools.factory.CommonFactoryFinder
+                .getFilterFactory(null);
+        setRenderer(render);
+
+        setContext(context);
+
+        this.addMouseListener(this);
+        this.addMouseMotionListener(this);
+        setHighlightManager(new HighlightManager(highlightLayer));
+        setSelectionManager(new SelectionManager(selectionLayer));
+        lineHighlightStyle = setupStyle(LINE, Color.red);
+
+        pointHighlightStyle = setupStyle(POINT, Color.red);
+
+        polygonHighlightStyle = setupStyle(POLYGON, Color.red);
+
+        polygonSelectionStyle = setupStyle(POLYGON, Color.cyan);
+
+        pointSelectionStyle = setupStyle(POINT, Color.cyan);
+
+        lineSelectionStyle = setupStyle(LINE, Color.cyan);
+        setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
+    }
+
+    /**
+     * get the renderer
+     */
+    public GTRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(GTRenderer renderer) {
+        Map hints = new HashMap();
+        if (renderer instanceof StreamingRenderer) {
+            hints = renderer.getRendererHints();
+            if (hints == null) {
+                hints = new HashMap();
+            }
+            if (hints.containsKey(StreamingRenderer.LABEL_CACHE_KEY)) {
+                labelCache = (LabelCache) hints
+                        .get(StreamingRenderer.LABEL_CACHE_KEY);
+            } else {
+                hints.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
+            }
+            renderer.setRendererHints(hints);
+        }
+
+        this.renderer = renderer;
+        this.highlightRenderer = new StreamingRenderer();
+        this.selectionRenderer = new StreamingRenderer();
+
+        hints.put("memoryPreloadingEnabled", Boolean.FALSE);
+        highlightRenderer.setRendererHints(hints);
+        selectionRenderer.setRendererHints(hints);
+
+        if (this.context != null) {
+            this.renderer.setContext(this.context);
+        }
+    }
+
+    public MapContext getContext() {
+        return context;
+    }
+
+    public void setContext(MapContext context) {
+        if (this.context != null) {
+            this.context.removeMapLayerListListener(this);
+        }
+
+        this.context = context;
+
+        if (context != null) {
+            this.context.addMapLayerListListener(this);
+        }
+
+        if (renderer != null) {
+            renderer.setContext(this.context);
+        }
+    }
+
+    public Envelope getMapArea() {
+        return mapArea;
+    }
+
+    public void setMapArea(Envelope mapArea) {
+        this.mapArea = mapArea;
+    }
+
+    public int getState() {
+        return state;
+    }
+
+    public void setState(int state) {
+        this.state = state;
+
+        // System.out.println("State: " + state);
+    }
+
+    public double getZoomFactor() {
+        return zoomFactor;
+    }
+
+    public void setZoomFactor(double zoomFactor) {
+        this.zoomFactor = zoomFactor;
+    }
+
+    public MapLayer getSelectionLayer() {
+        return selectionLayer;
+    }
+
+    public void setSelectionLayer(MapLayer selectionLayer) {
+        this.selectionLayer = selectionLayer;
+        if(selectionManager!=null) {
+            selectionManager.setSelectionLayer(selectionLayer);
+        }
+    }
+
+    public boolean isHighlight() {
+        return highlight;
+    }
+
+    public void setHighlight(boolean highlight) {
+        this.highlight = highlight;
+    }
+
+    public MapLayer getHighlightLayer() {
+        return highlightLayer;
+    }
+
+    public void setHighlightLayer(MapLayer highlightLayer) {
+        this.highlightLayer = highlightLayer;
+
+        if (highlightManager != null) {
+            highlightManager.setHighlightLayer(highlightLayer);
+        }
+    }
+
+    public HighlightManager getHighlightManager() {
+        return highlightManager;
+    }
+
+    public void setHighlightManager(HighlightManager highlightManager) {
+        this.highlightManager = highlightManager;
+        this.highlightManager.addHighlightChangeListener(this);
+        this.addMouseMotionListener(this.highlightManager);
+    }
+
+    public Style getLineHighlightStyle() {
+        return lineHighlightStyle;
+    }
+
+    public void setLineHighlightStyle(Style lineHighlightStyle) {
+        this.lineHighlightStyle = lineHighlightStyle;
+    }
+
+    public Style getLineSelectionStyle() {
+        return lineSelectionStyle;
+    }
+
+    public void setLineSelectionStyle(Style lineSelectionStyle) {
+        this.lineSelectionStyle = lineSelectionStyle;
+    }
+
+    public Style getPointHighlightStyle() {
+        return pointHighlightStyle;
+    }
+
+    public void setPointHighlightStyle(Style pointHighlightStyle) {
+        this.pointHighlightStyle = pointHighlightStyle;
+    }
+
+    public Style getPointSelectionStyle() {
+        return pointSelectionStyle;
+    }
+
+    public void setPointSelectionStyle(Style pointSelectionStyle) {
+        this.pointSelectionStyle = pointSelectionStyle;
+    }
+
+    public Style getPolygonHighlightStyle() {
+        return polygonHighlightStyle;
+    }
+
+    public void setPolygonHighlightStyle(Style polygonHighlightStyle) {
+        this.polygonHighlightStyle = polygonHighlightStyle;
+    }
+
+    public Style getPolygonSelectionStyle() {
+        return polygonSelectionStyle;
+    }
+
+    public void setPolygonSelectionStyle(Style polygonSelectionStyle) {
+        this.polygonSelectionStyle = polygonSelectionStyle;
+    }
+
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+        if ((renderer == null) || (mapArea == null)  ) {
+            return;
+        }
+
+        Rectangle r = getBounds();
+        Rectangle dr = new Rectangle(r.width, r.height);
+
+        if (!r.equals(oldRect) || reset) {
+                if(!r.equals(oldRect) && (mapArea == null)) {
+                        try {
+                                        mapArea=context.getLayerBounds();
+                                } catch (IOException e) {
+                                        // TODO Auto-generated catch block
+                                        e.printStackTrace();
+                                }
+                }
+                
+                if (mapArea != null){
+                	/* either the viewer size has changed or we've done a reset */
+                	changed = true; /* note we need to redraw */
+                	reset = false; /* forget about the reset */
+                	oldRect = r; /* store what the current size is */
+                	
+                	mapArea = fixAspectRatio(r, mapArea);
+                } 
+        }
+
+        if (!mapArea.equals(oldMapArea)) { /* did the map extent change? */
+            changed = true;
+            oldMapArea = mapArea;
+//          when we tell the context that the bounds have changed WMSLayers
+            // can refresh them selves
+            context.setAreaOfInterest(mapArea, context
+                    .getCoordinateReferenceSystem());
+        }
+
+        if (changed) { /* if the map changed then redraw */
+            changed = false;
+            baseImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+
+            Graphics2D ig = baseImage.createGraphics();
+            /* System.out.println("rendering"); */
+            renderer.setContext(context);
+            labelCache.clear(); // work around anoying labelcache bug
+
+
+            // draw the map
+            renderer.paint((Graphics2D) ig, dr, mapArea);
+
+            // TODO , nur machen, wenn panning beginnt 
+            panningImage = new BufferedImage(dr.width, dr.height,
+            		BufferedImage.TYPE_INT_RGB);
+            
+        }
+
+        ((Graphics2D) g).drawImage(baseImage, 0, 0, this);
+
+        if ((selection != null) && (selection.size() > 0)) {
+            // paint selection
+
+            String type = selectionLayer.getFeatureSource().getSchema()
+            .getDefaultGeometry().getType().getName();
+            /*String type = selection.getDefaultGeometry().getGeometryType();*/
+            /*System.out.println(type);*/
+            if (type == null)
+                type = "polygon";
+
+            /* String type = "point"; */
+
+            if (type.toLowerCase().endsWith("polygon")) {
+                selectionStyle = polygonSelectionStyle;
+            } else if (type.toLowerCase().endsWith("point")) {
+                selectionStyle = pointSelectionStyle;
+            } else if (type.toLowerCase().endsWith("line")) {
+                selectionStyle = lineSelectionStyle;
+            }
+
+            selectionContext = new DefaultMapContext(DefaultGeographicCRS.WGS84);
+
+            selectionContext.addLayer(selection, selectionStyle);
+            selectionRenderer.setContext(selectionContext);
+
+            selectImage = new BufferedImage(dr.width, dr.height,
+                    BufferedImage.TYPE_INT_ARGB);
+
+            Graphics2D ig = selectImage.createGraphics();
+            /* System.out.println("rendering selection"); */
+            selectionRenderer.paint((Graphics2D) ig, dr, mapArea);
+
+            ((Graphics2D) g).drawImage(selectImage, 0, 0, this);
+        }
+
+        if (highlight && (highlightFeature != null)
+                && (highlightFeature.size() > 0)) {
+            /*
+             * String type = selection.getDefaultGeometry().getGeometryType();
+             * System.out.println(type); if(type==null) type="polygon";
+             */
+            String type = highlightLayer.getFeatureSource().getSchema()
+            .getDefaultGeometry().getType().getName();
+            /*String type = selection.getDefaultGeometry().getGeometryType();*/
+            //System.out.println(type);
+            if (type == null)
+                type = "polygon";
+
+            /* String type = "point"; */
+            Style highlightStyle = null;
+            if (type.toLowerCase().endsWith("polygon")) {
+                highlightStyle = polygonHighlightStyle;
+            } else if (type.toLowerCase().endsWith("point")) {
+                highlightStyle = pointHighlightStyle;
+            } else if (type.toLowerCase().endsWith("line")) {
+                highlightStyle = lineHighlightStyle;
+            }
+
+
+
+
+            MapContext highlightContext = new DefaultMapContext(
+                    DefaultGeographicCRS.WGS84);
+
+            highlightContext.addLayer(highlightFeature, highlightStyle);
+            highlightRenderer.setContext(highlightContext);
+
+            /* System.out.println("rendering highlight"); */
+            highlightRenderer.paint((Graphics2D) g, dr, mapArea);
+        }
+    }
+
+    private Envelope fixAspectRatio(Rectangle r, Envelope mapArea) {
+    	
+        double mapWidth = mapArea.getWidth(); /* get the extent of the map */
+        double mapHeight = mapArea.getHeight();
+        double scaleX = r.getWidth() / mapArea.getWidth(); /*
+                                                             * calculate the new
+                                                             * scale
+                                                             */
+
+        double scaleY = r.getHeight() / mapArea.getHeight();
+        double scale = 1.0; // stupid compiler!
+
+        if (scaleX < scaleY) { /* pick the smaller scale */
+            scale = scaleX;
+        } else {
+            scale = scaleY;
+        }
+
+        /* calculate the difference in width and height of the new extent */
+        double deltaX = /* Math.abs */((r.getWidth() / scale) - mapWidth);
+        double deltaY = /* Math.abs */((r.getHeight() / scale) - mapHeight);
+
+        /*
+         * System.out.println("delta x " + deltaX); System.out.println("delta y " +
+         * deltaY);
+         */
+
+        /* create the new extent */
+        Coordinate ll = new Coordinate(mapArea.getMinX() - (deltaX / 2.0),
+                mapArea.getMinY() - (deltaY / 2.0));
+        Coordinate ur = new Coordinate(mapArea.getMaxX() + (deltaX / 2.0),
+                mapArea.getMaxY() + (deltaY / 2.0));
+
+        return new Envelope(ll, ur);
+    }
+
+    public void doSelection(double x, double y, MapLayer layer) {
+
+        Geometry geometry = gf.createPoint(new Coordinate(x, y));
+
+        // org.opengis.geometry.Geometry geometry = new Point();
+
+            findFeature(geometry, layer);
+
+    }
+
+    /**
+     * @param geometry -
+     *            a geometry to construct the filter with
+     * @param i -
+     *            the index of the layer to search
+     * @throws IndexOutOfBoundsException
+     */
+    private void findFeature(Geometry geometry, MapLayer layer)
+            throws IndexOutOfBoundsException {
+        org.opengis.filter.spatial.BinarySpatialOperator f = null;
+
+
+        if ((context == null) || (layer==null)) {
+            return ;
+        }
+
+
+
+        try {
+            String name = layer.getFeatureSource().getSchema()
+                    .getDefaultGeometry().getName();
+
+            if (name == "") {
+                name = "the_geom";
+            }
+
+            try {
+                f = ff.contains(ff.property(name), ff.literal(geometry));
+                if(selectionManager!=null) {
+                    System.out.println("selection changed");
+                    selectionManager.selectionChanged(this, f);
+
+                }
+            } catch (IllegalFilterException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+
+            /*// f.addLeftGeometry(ff.property(name));
+            // System.out.println("looking with " + f);
+            FeatureCollection fc = layer.getFeatureSource().getFeatures(f);
+
+
+
+            if (fcol == null) {
+                fcol = fc;
+
+                // here we should set the defaultgeom type
+            } else {
+                fcol.addAll(fc);
+            }*/
+
+            /*
+             * GeometryAttributeType gat =
+             * layer.getFeatureSource().getSchema().getDefaultGeometry();
+             * fcol.setDefaultGeometry((Geometry)gat.createDefaultValue());
+             */
+
+            /*
+             * Iterator fi = fc.iterator(); while (fi.hasNext()) { Feature feat =
+             * (Feature) fi.next(); System.out.println("selected " +
+             * feat.getAttribute("STATE_NAME")); }
+             */
+        } catch (IllegalFilterException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return ;
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        // System.out.println("before area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        Rectangle bounds = this.getBounds();
+        double x = (double) (e.getX());
+        double y = (double) (e.getY());
+        double width = mapArea.getWidth();
+        double height = mapArea.getHeight();
+//xulu.sc
+//        double width2 = mapArea.getWidth() / 2.0;
+//        double height2 = mapArea.getHeight() / 2.0;
+        double width2 = width / 2.0;
+        double height2 = height / 2.0;
+//xulu.ec
+        double mapX = ((x * width) / (double) bounds.width) + mapArea.getMinX();
+        double mapY = (((bounds.getHeight() - y) * height) / (double) bounds.height)
+                + mapArea.getMinY();
+
+        /*
+         * System.out.println(""+x+"->"+mapX);
+         * System.out.println(""+y+"->"+mapY);
+         */
+
+        /*
+         * Coordinate ll = new Coordinate(mapArea.getMinX(), mapArea.getMinY());
+         * Coordinate ur = new Coordinate(mapArea.getMaxX(), mapArea.getMaxY());
+         */
+        double zlevel = 1.0;
+
+        switch (state) {
+        case Pan:
+            zlevel = 1.0;
+//xulu.sc SK: return here.. a mouselistener is maanaging the PANNING
+//            break;
+            return;
+//xulu.ec
+        case ZoomIn:
+            zlevel = zoomFactor;
+
+            break;
+
+        case ZoomOut:
+            zlevel = 1.0 / zoomFactor;
+
+            break;
+
+        case Select:
+            doSelection(mapX, mapY, selectionLayer);
+
+
+            return;
+
+        default:
+            return;
+        }
+
+        Coordinate ll = new Coordinate(mapX - (width2 / zlevel), mapY
+                - (height2 / zlevel));
+        Coordinate ur = new Coordinate(mapX + (width2 / zlevel), mapY
+                + (height2 / zlevel));
+//xulu.sc SK: Check for min/max scale
+//        mapArea = new Envelope(ll, ur);
+        final Envelope newMapArea = new Envelope(ll, ur);
+        setMapArea( bestAllowedMapArea( newMapArea ) );
+//xulu.ec
+
+        // System.out.println("after area "+mapArea+"\nw:"+mapArea.getWidth()+"
+        // h:"+mapArea.getHeight());
+        repaint();
+    }
+
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+    public void mousePressed(MouseEvent e) {
+        startX = e.getX();
+        startY = e.getY();
+        lastX = 0;
+        lastY = 0;
+    }
+    
+    public void mouseReleased(MouseEvent e) {
+        int endX = e.getX();
+        int endY = e.getY();
+        
+        processDrag(startX, startY, endX, endY, e);
+        lastX = 0;
+        lastY = 0;
+        
+    	/**
+    	 * Es wird nicht (mehr) gepannt!
+    	 */
+    	panning_started = false;
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        Graphics graphics = this.getGraphics();
+        int x = e.getX();
+        int y = e.getY();
+        
+
+        if ( (state == JMapPane.Pan) || ((e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0))  {
+            /**
+             * SK:
+             * Der Cursor wird auf PANNING gesetzt. 
+             */
+        	if (panning_started == false){
+        		panning_started = true;
+        		setCursor( SwingUtil.PANNING_CURSOR ); 
+        	}
+        	
+        	
+            // move the image with the mouse
+            if ((lastX > 0) && (lastY > 0)) {
+                int dx = lastX - startX;
+                int dy = lastY - startY;
+                // System.out.println("translate "+dx+","+dy);
+                final Graphics2D g2 = panningImage.createGraphics();
+                g2.setBackground( new Color(240,240,240) ); //TODO richtige farbe? am besten vom L&F die hintergrundfarbe auslesen... 
+				g2.clearRect(0, 0, this.getWidth(), this.getHeight());
+				g2.drawImage(baseImage, dx, dy, this);
+                graphics.drawImage(panningImage, 0, 0, this);
+            }
+
+            lastX = x;
+            lastY = y;
+        } else 
+        	
+    	if ((state == JMapPane.ZoomIn) || (state == JMapPane.ZoomOut)) {
+    	
+    	
+            graphics.setXORMode(Color.WHITE);
+
+            if ((lastX > 0) && (lastY > 0)) {
+                drawRectangle(graphics);
+            }
+
+            // draw new box
+            lastX = x;
+            lastY = y;
+            drawRectangle(graphics);
+        } else if (state == JMapPane.Select && selectionLayer != null) {
+
+            // construct a new bbox filter
+            Rectangle bounds = this.getBounds();
+
+            double mapWidth = mapArea.getWidth();
+            double mapHeight = mapArea.getHeight();
+
+            double x1 = ((this.startX * mapWidth) / (double) bounds.width)
+                    + mapArea.getMinX();
+            double y1 = (((bounds.getHeight() - this.startY) * mapHeight) / (double) bounds.height)
+                    + mapArea.getMinY();
+            double x2 = ((x * mapWidth) / (double) bounds.width)
+                    + mapArea.getMinX();
+            double y2 = (((bounds.getHeight() - y) * mapHeight) / (double) bounds.height)
+                    + mapArea.getMinY();
+            double left = Math.min(x1, x2);
+            double right = Math.max(x1, x2);
+            double bottom = Math.min(y1, y2);
+            double top = Math.max(y1, y2);
+
+
+            String name = selectionLayer.getFeatureSource().getSchema()
+                    .getDefaultGeometry().getName();
+
+            if (name == "") {
+                name = "the_geom";
+            }
+            Filter bb = ff.bbox(ff.property(name), left, bottom, right, top,
+                    getContext().getCoordinateReferenceSystem().toString());
+            if(selectionManager!=null) {
+                selectionManager.selectionChanged(this, bb);
+            }
+
+            graphics.setXORMode(Color.green);
+
+            /*
+             * if ((lastX > 0) && (lastY > 0)) { drawRectangle(graphics); }
+             */
+
+            // draw new box
+            lastX = x;
+            lastY = y;
+            drawRectangle(graphics);
+       } 
+    
+    }
+
+    // sk.cs
+//    private void processDrag(int x1, int y1, int x2, int y2) {
+    // sk.ce
+    protected void processDrag(int x1, int y1, int x2, int y2, MouseEvent e) {
+    	
+    	/****
+    	 * If no layer is availabe we dont want a NullPointerException
+    	 */
+    	if (mapArea == null) return;
+    	
+        // System.out.println("processing drag from " + x1 + "," + y1 + " -> "
+        // + x2 + "," + y2);
+        if ((x1 == x2) && (y1 == y2)) {
+            if (isClickable()) {
+                mouseClicked(new MouseEvent(this, 0, new Date().getTime(), 0,
+                        x1, y1, y2, false));
+            }
+
+            return;
+        }
+        
+        Rectangle bounds = this.getBounds();
+
+        double mapWidth = mapArea.getWidth();
+        double mapHeight = mapArea.getHeight();
+
+        double startX = ((x1 * mapWidth) / (double) bounds.width)
+                + mapArea.getMinX();
+        double startY = (((bounds.getHeight() - y1) * mapHeight) / (double) bounds.height)
+                + mapArea.getMinY();
+        double endX = ((x2 * mapWidth) / (double) bounds.width)
+                + mapArea.getMinX();
+        double endY = (((bounds.getHeight() - y2) * mapHeight) / (double) bounds.height)
+                + mapArea.getMinY();
+
+        if (  (state == JMapPane.Pan) || (e.getButton()== MouseEvent.BUTTON3)) {
+            // move the image with the mouse
+            // calculate X offsets from start point to the end Point
+            double deltaX1 = endX - startX;
+
+            // System.out.println("deltaX " + deltaX1);
+            // new edges
+            double left = mapArea.getMinX() - deltaX1;
+            double right = mapArea.getMaxX() - deltaX1;
+
+            // now for Y
+            double deltaY1 = endY - startY;
+
+            // System.out.println("deltaY " + deltaY1);
+            double bottom = mapArea.getMinY() - deltaY1;
+            double top = mapArea.getMaxY() - deltaY1;
+            Coordinate ll = new Coordinate(left, bottom);
+            Coordinate ur = new Coordinate(right, top);
+//xulu.sc
+//            mapArea = fixAspectRatio(this.getBounds(), new Envelope(ll, ur));
+            setMapArea( fixAspectRatio(this.getBounds(), new Envelope(ll, ur)) );
+//xulu.ec
+        } else if (state == JMapPane.ZoomIn) {
+        	
+
+        	// Zu kleine Flächen sollen nicht gezoomt werden.
+        	//sk.bc
+        	if ( (Math.abs(x1-x2) * Math.abs(y2-y1) )<100 ) return;
+        	// sk.ec
+        	
+        	drawRectangle(this.getGraphics());
+            // make the dragged rectangle (in map coords) the new BBOX
+            double left = Math.min(startX, endX);
+            double right = Math.max(startX, endX);
+            double bottom = Math.min(startY, endY);
+            double top = Math.max(startY, endY);
+            Coordinate ll = new Coordinate(left, bottom);
+            Coordinate ur = new Coordinate(right, top);
+//xulu.sc
+//            mapArea = fixAspectRatio(this.getBounds(), new Envelope(ll, ur));
+            setMapArea( fixAspectRatio(this.getBounds(), new Envelope(ll, ur)) );
+//xulu.ec
+        } else if (state == JMapPane.ZoomOut) {
+        	drawRectangle(this.getGraphics());
+        	
+        	// make the dragged rectangle in screen coords the new map size?
+            double left = Math.min(startX, endX);
+            double right = Math.max(startX, endX);
+            double bottom = Math.min(startY, endY);
+            double top = Math.max(startY, endY);
+            double nWidth = (mapWidth * mapWidth) / (right - left);
+            double nHeight = (mapHeight * mapHeight) / (top - bottom);
+            double deltaX1 = left - mapArea.getMinX();
+            double nDeltaX1 = (deltaX1 * nWidth) / mapWidth;
+            double deltaY1 = bottom - mapArea.getMinY();
+            double nDeltaY1 = (deltaY1 * nHeight) / mapHeight;
+            Coordinate ll = new Coordinate(mapArea.getMinX() - nDeltaX1,
+                    mapArea.getMinY() - nDeltaY1);
+            double deltaX2 = mapArea.getMaxX() - right;
+            double nDeltaX2 = (deltaX2 * nWidth) / mapWidth;
+            double deltaY2 = mapArea.getMaxY() - top;
+            double nDeltaY2 = (deltaY2 * nHeight) / mapHeight;
+            Coordinate ur = new Coordinate(mapArea.getMaxX() + nDeltaX2,
+                    mapArea.getMaxY() + nDeltaY2);
+//xulu.sc            
+//            mapArea = fixAspectRatio(this.getBounds(), new Envelope(ll, ur));
+            setMapArea( fixAspectRatio(this.getBounds(), new Envelope(ll, ur)) );
+//xulu.ec
+        } else if (state == JMapPane.Select && selectionLayer !=null) {
+            double left = Math.min(startX, endX);
+            double right = Math.max(startX, endX);
+            double bottom = Math.min(startY, endY);
+            double top = Math.max(startY, endY);
+
+
+            String name = selectionLayer.getFeatureSource().getSchema()
+                    .getDefaultGeometry().getLocalName();
+
+            if (name == "") {
+                name = "the_geom";
+            }
+            Filter bb = ff.bbox(ff.property(name), left, bottom, right, top,
+                    getContext().getCoordinateReferenceSystem().toString());
+            //System.out.println(bb.toString());
+            if(selectionManager!=null) {
+                selectionManager.selectionChanged(this, bb);
+            }
+            /*FeatureCollection fc;
+            selection = null;
+            try {
+                fc = selectionLayer.getFeatureSource().getFeatures(bb);
+                selection = fc;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+*/
+        }
+
+//xulu.so
+//        setMapArea(mapArea);
+//xulu.eo
+        repaint();
+    }
+
+    private boolean isClickable() {
+        return clickable;
+    }
+
+    private org.geotools.styling.Style setupStyle(int type, Color color) {
+        StyleFactory sf = org.geotools.factory.CommonFactoryFinder
+                .getStyleFactory(null);
+        StyleBuilder sb = new StyleBuilder();
+
+        org.geotools.styling.Style s = sf.createStyle();
+        s.setTitle("selection");
+
+        // TODO parameterise the color
+        PolygonSymbolizer ps = sb.createPolygonSymbolizer(color);
+        ps.setStroke(sb.createStroke(color));
+
+        LineSymbolizer ls = sb.createLineSymbolizer(color);
+        Graphic h = sb.createGraphic();
+        h.setMarks(new Mark[] { sb.createMark("square", color) });
+
+        PointSymbolizer pts = sb.createPointSymbolizer(h);
+
+        // Rule r = sb.createRule(new Symbolizer[]{ps,ls,pts});
+        switch (type) {
+        case POLYGON:
+            s = sb.createStyle(ps);
+
+            break;
+
+        case POINT:
+            s = sb.createStyle(pts);
+
+            break;
+
+        case LINE:
+            s = sb.createStyle(ls);
+        }
+
+        return s;
+    }
+
+    public void highlightChanged(HighlightChangedEvent e) {
+        org.opengis.filter.Filter f = e.getFilter();
+
+        try {
+            highlightFeature = highlightLayer.getFeatureSource().getFeatures(f);
+        } catch (IOException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+        }
+
+        repaint();
+    }
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        String prop = evt.getPropertyName();
+
+        if (prop.equalsIgnoreCase("crs")) {
+            context.setAreaOfInterest(context.getAreaOfInterest(),
+                    (CoordinateReferenceSystem) evt.getNewValue());
+        }
+    }
+
+    public boolean isReset() {
+        return reset;
+    }
+
+    public void setReset(boolean reset) {
+        this.reset = reset;
+    }
+
+    public void layerAdded(MapLayerListEvent event) {
+        changed = true;
+
+        if (context.getLayers().length == 1) { // the first one
+
+            try {
+//xulu.sc    
+//            mapArea = context.getLayerBounds();
+              mapArea = context.getAreaOfInterest();
+              if ( mapArea == null )
+                mapArea = context.getLayerBounds();
+//xulu.ec
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+
+            reset = true;
+        }
+
+        repaint();
+    }
+
+    public void layerRemoved(MapLayerListEvent event) {
+        changed = true;
+        repaint();
+    }
+
+    public void layerChanged(MapLayerListEvent event) {
+        changed = true;
+        // System.out.println("layer changed - repaint");
+        repaint();
+    }
+
+    public void layerMoved(MapLayerListEvent event) {
+        changed = true;
+        repaint();
+    }
+
+    protected void drawRectangle(Graphics graphics) {
+    	// undraw last box/draw new box
+        int left = Math.min(startX, lastX);
+        int right = Math.max(startX, lastX);
+        int top = Math.max(startY, lastY);
+        int bottom = Math.min(startY, lastY);
+        int width = right - left;
+        int height = top - bottom;
+        // System.out.println("drawing rect("+left+","+bottom+","+ width+","+
+        // height+")");
+        graphics.drawRect(left, bottom, width, height);
+    }
+
+    /**
+     * if clickable is set to true then a single click on the map pane will zoom
+     * or pan the map.
+     *
+     * @param clickable
+     */
+    public void setClickable(boolean clickable) {
+        this.clickable = clickable;
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    public FeatureCollection getSelection() {
+        return selection;
+    }
+
+    public void setSelection(FeatureCollection selection) {
+        this.selection = selection;
+        repaint();
+    }
+
+    /* (non-Javadoc)
+     * @see org.geotools.gui.swing.event.SelectionChangeListener#selectionChanged(org.geotools.gui.swing.event.SelectionChangedEvent)
+     */
+    public void selectionChanged(SelectionChangedEvent e) {
+
+        try {
+            selection = selectionLayer.getFeatureSource().getFeatures(e.getFilter());
+            repaint();
+        } catch (IOException e1) {
+            e1.printStackTrace();
+        }
+    }
+
+    public SelectionManager getSelectionManager() {
+        return selectionManager;
+    }
+
+    public void setSelectionManager(SelectionManager selectionManager) {
+        this.selectionManager = selectionManager;
+        this.selectionManager.addSelectionChangeListener(this);
+
+    }
+
+
+//xulu.sn
+    /**
+     * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste erlaubte Flaeche damit
+     * die Massstabsbeschaenkungen noch eingehalten werden, FALLS der uebergeben Envelope
+     * nicht schon gueltig sein sollte.
+     * 
+     * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+     */
+  public Envelope bestAllowedMapArea(Envelope env) {
+    if (getWidth() == 0) return env;
+    if (env == null) return env;
+
+    double scale = env.getWidth() / getWidth();
+    double centerX = env.getMinX() + env.getWidth() / 2.;
+    double centerY = env.getMinY() + env.getHeight() / 2.;
+    double newWidth2;
+    double newHeight2;
+    if (scale < getMaxZoomScale()) {
+      //****************************************************************************
+      // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
+      //****************************************************************************
+      newWidth2 = getMaxZoomScale() * getWidth() / 2.;
+      newHeight2 = getMaxZoomScale() * getHeight() / 2.;
+    } else if (scale > getMinZoomScale()) {
+      //****************************************************************************
+      // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
+      //****************************************************************************
+      newWidth2 = getMinZoomScale() * getWidth() / 2.;
+      newHeight2 = getMinZoomScale() * getHeight() / 2.;
+    } else {
+      //****************************************************************************
+      // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
+      //****************************************************************************
+      return env;
+    }
+
+    Coordinate ll = new Coordinate(centerX - newWidth2, centerY
+                                   - newHeight2);
+    Coordinate ur = new Coordinate(centerX + newWidth2, centerY
+                                   + newHeight2);
+
+    return new Envelope(ll, ur);
+  }
+
+  /**
+   * Retuns the minimum allowed zoom scale. This is the bigger number value of the two.
+   * Defaults to {@link Double}.MAX_VALUE
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public Double getMinZoomScale() {
+    return minZoomScale;
+  }
+
+  /**
+   * Retuns the maximum allowed zoom scale. This is the smaller number value of the two.
+   * Defaults to {@link Double}.MIN_VALUE
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public Double getMaxZoomScale() {
+    return maxZoomScale;
+  }
+
+  /**
+   * Set the maximum allowed zoom scale. This is the smaller number value of the two.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setMaxZoomScale(Double maxZoomScale) {
+    // System.out.println("setting max scale to "+maxZoomScale);
+    this.maxZoomScale = maxZoomScale;
+  }
+
+  /**
+   * Set the minimum (nearest) allowed zoom scale. This is the bigger number value of the two.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setMinZoomScale(Double minZoomScale) {
+    this.minZoomScale = minZoomScale;
+  }
+//xulu.en
+
+}

Added: trunk/src/org/geotools/gui/swing/MouseSelectionTracker_Public.java
===================================================================
--- trunk/src/org/geotools/gui/swing/MouseSelectionTracker_Public.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/gui/swing/MouseSelectionTracker_Public.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,15 @@
+package org.geotools.gui.swing;
+
+import org.geotools.gui.swing.MouseSelectionTracker;
+
+/**
+ * Diese Klasse stellt lediglich eine Dummy-Klasse dar, um die Geotools-Klasse
+ * {@link MouseSelectionTracker} auch von ausserhalb des Pakets
+ * <code>org.geotools.gui.swing</code> verwenden zu koennen.
+ * {@link MouseSelectionTracker org.geotools.gui.swing.MouseSelectionTracker}
+ * ist naemlich keine <code>public</code>-Klasse.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class MouseSelectionTracker_Public extends MouseSelectionTracker {
+}

Added: trunk/src/org/geotools/gui/swing/package.html
===================================================================
--- trunk/src/org/geotools/gui/swing/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/gui/swing/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,8 @@
+<html>
+<body>
+	Dieses Paket enthält angepasste Klassen der
+	<a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek, sowie
+	Klassen, die aus Zugriffsgründen direkt im Paket <code>org.geotools.gui.swing</code>
+	implementiert werden müssen.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.1
===================================================================
--- trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.1	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.1	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,251 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+
+package org.geotools.renderer.lite;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+
+import org.opengis.referencing.operation.MathTransform;
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryCollection;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.LinearRing;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.3.1 to deal with empty {@code GeometryCollection}-objects.</b><br><br>
+ *
+ * A path iterator for the LiteShape class, specialized to iterate over a
+ * geometry collection. It can be seen as a composite, since uses in fact
+ * other, simpler iterator to carry on its duties.
+ *
+ * @author Andrea Aime
+ * @source $URL: http://svn.geotools.org/geotools/tags/2.3.1/module/render/src/org/geotools/renderer/lite/GeomCollectionIterator.java $
+ * @version $Id: GeomCollectionIterator.java 20874 2006-08-07 10:00:01Z jgarnett $
+ */
+public class GeomCollectionIterator extends AbstractLiteIterator {
+    /** Transform applied on the coordinates during iteration */
+    private AffineTransform at;
+
+    /** The set of geometries that we will iterate over */
+    private GeometryCollection gc;
+
+    /** The current geometry */
+    private int currentGeom;
+
+    /** The current sub-iterator */
+    private PathIterator currentIterator;
+
+    /** True when the iterator is terminate */
+    private boolean done = false;
+
+    /** If true, apply simple distance based generalization */
+    private boolean generalize = false;
+
+    /** Maximum distance for point elision when generalizing */
+    private double maxDistance = 1.0;
+
+    private LineIterator lineIterator = new LineIterator();
+
+    public GeomCollectionIterator()
+    {
+
+    }
+
+    /**
+         * @param gc
+         * @param at
+         */
+        public void init(GeometryCollection gc, AffineTransform at, boolean generalize, double maxDistance) {
+        this.gc = gc;
+        this.at = at==null?new AffineTransform():at;
+        this.generalize = generalize;
+        this.maxDistance = maxDistance;
+        currentGeom = 0;
+        done = false;
+//Xulu-01.sc
+//        currentIterator = getIterator(gc.getGeometryN(0));
+        if ( !gc.isEmpty() )
+          currentIterator = getIterator(gc.getGeometryN(0));
+        else {
+          currentIterator = null;
+          done = true;
+        }
+//Xulu-01.ec
+      }
+
+    /**
+     * Creates a new instance of GeomCollectionIterator
+     *
+     * @param gc The geometry collection the iterator will use
+     * @param at The affine transform applied to coordinates during iteration
+     * @param generalize if true apply simple distance based generalization
+     * @param maxDistance during iteration, a point will be skipped if it's
+     *        distance from the previous is less than maxDistance
+     */
+    public GeomCollectionIterator(
+        GeometryCollection gc, AffineTransform at, boolean generalize,
+                double maxDistance) {
+        init(gc, at, generalize, maxDistance);
+    }
+
+    /**
+     * Sets the distance limit for point skipping during distance based
+     * generalization
+     *
+     * @param distance the maximum distance for point skipping
+     */
+    public void setMaxDistance(double distance) {
+        maxDistance = distance;
+    }
+
+    /**
+     * Returns the distance limit for point skipping during distance based
+     * generalization
+     *
+     * @return the maximum distance for distance based generalization
+     */
+    public double getMaxDistance() {
+        return maxDistance;
+    }
+
+    /**
+     * Returns the specific iterator for the geometry passed.
+     *
+     * @param g The geometry whole iterator is requested
+     *
+     * @return the specific iterator for the geometry passed.
+     */
+    private AbstractLiteIterator getIterator(Geometry g) {
+        AbstractLiteIterator pi = null;
+
+        if (g instanceof Polygon) {
+            Polygon p = (Polygon) g;
+            pi = new PolygonIterator(p, at, generalize, maxDistance);
+        } else if (g instanceof GeometryCollection) {
+            GeometryCollection gc = (GeometryCollection) g;
+            pi = new GeomCollectionIterator(gc, at, generalize, maxDistance);
+        } else if (g instanceof LineString) {
+            LineString ls = (LineString) g;
+            lineIterator.init(ls, at, generalize, (float) maxDistance);
+            pi = lineIterator;
+        } else if (g instanceof LinearRing) {
+            LinearRing lr = (LinearRing) g;
+            lineIterator.init(lr, at, generalize, (float) maxDistance);
+            pi = lineIterator;
+        } else if (g instanceof Point) {
+            Point p = (Point) g;
+            pi = new PointIterator(p, at);
+        }
+
+        return pi;
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the
+     * iteration. The return value is the path-segment type: SEG_MOVETO,
+     * SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. A double array of
+     * length 6 must be passed in and can be used to store the coordinates of
+     * the point(s). Each point is stored as a pair of double x,y coordinates.
+     * SEG_MOVETO and SEG_LINETO types returns one point, SEG_QUADTO returns
+     * two points, SEG_CUBICTO returns 3 points and SEG_CLOSE does not return
+     * any points.
+     *
+     * @param coords an array that holds the data returned from this method
+     *
+     * @return the path-segment type of the current path segment.
+     *
+     * @see #SEG_MOVETO
+     * @see #SEG_LINETO
+     * @see #SEG_QUADTO
+     * @see #SEG_CUBICTO
+     * @see #SEG_CLOSE
+     */
+    public int currentSegment(double[] coords) {
+        return currentIterator.currentSegment(coords);
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the
+     * iteration. The return value is the path-segment type: SEG_MOVETO,
+     * SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. A float array of
+     * length 6 must be passed in and can be used to store the coordinates of
+     * the point(s). Each point is stored as a pair of float x,y coordinates.
+     * SEG_MOVETO and SEG_LINETO types returns one point, SEG_QUADTO returns
+     * two points, SEG_CUBICTO returns 3 points and SEG_CLOSE does not return
+     * any points.
+     *
+     * @param coords an array that holds the data returned from this method
+     *
+     * @return the path-segment type of the current path segment.
+     *
+     * @see #SEG_MOVETO
+     * @see #SEG_LINETO
+     * @see #SEG_QUADTO
+     * @see #SEG_CUBICTO
+     * @see #SEG_CLOSE
+     */
+    public int currentSegment(float[] coords) {
+        return currentIterator.currentSegment(coords);
+    }
+
+    /**
+     * Returns the winding rule for determining the interior of the path.
+     *
+     * @return the winding rule.
+     *
+     * @see #WIND_EVEN_ODD
+     * @see #WIND_NON_ZERO
+     */
+    public int getWindingRule() {
+        return WIND_NON_ZERO;
+    }
+
+    /**
+     * Tests if the iteration is complete.
+     *
+     * @return <code>true</code> if all the segments have been read;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isDone() {
+        return done;
+    }
+
+    /**
+     * Moves the iterator to the next segment of the path forwards along the
+     * primary direction of traversal as long as there are more points in that
+     * direction.
+     */
+    public void next() {
+        if (currentIterator.isDone()) {
+            if (currentGeom < (gc.getNumGeometries() - 1)) {
+                currentGeom++;
+                currentIterator = getIterator(gc.getGeometryN(currentGeom));
+            } else {
+                done = true;
+            }
+        } else {
+            currentIterator.next();
+        }
+    }
+
+}

Added: trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.4
===================================================================
--- trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.4	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/renderer/lite/GeomCollectionIterator.gt2-2.3.4	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,256 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+
+package org.geotools.renderer.lite;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+
+import org.opengis.referencing.operation.MathTransform;
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryCollection;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.LinearRing;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.3.1 to deal with empty {@code GeometryCollection}-objects.</b><br><br>
+ *
+ * A path iterator for the LiteShape class, specialized to iterate over a
+ * geometry collection. It can be seen as a composite, since uses in fact
+ * other, simpler iterator to carry on its duties.
+ *
+ * @author Andrea Aime
+ * @source $URL: http://svn.geotools.org/geotools/branches/2.3.x/module/render/src/org/geotools/renderer/lite/GeomCollectionIterator.java $
+ * @version $Id: GeomCollectionIterator.java 25168 2007-04-16 09:00:47Z aaime $
+ */
+public final class GeomCollectionIterator extends AbstractLiteIterator {
+    /** Transform applied on the coordinates during iteration */
+    private AffineTransform at;
+
+    /** The set of geometries that we will iterate over */
+    private GeometryCollection gc;
+
+    /** The current geometry */
+    private int currentGeom;
+
+    /** The current sub-iterator */
+    private PathIterator currentIterator;
+
+    /** True when the iterator is terminate */
+    private boolean done = false;
+
+    /** If true, apply simple distance based generalization */
+    private boolean generalize = false;
+
+    /** Maximum distance for point elision when generalizing */
+    private double maxDistance = 1.0;
+
+    private LineIterator lineIterator = new LineIterator();
+
+    private EmptyIterator emptyIterator = new EmptyIterator();
+
+    public GeomCollectionIterator()
+    {
+
+    }
+
+    /**
+	 * @param gc
+	 * @param at
+	 */
+	public void init(GeometryCollection gc, AffineTransform at, boolean generalize, double maxDistance) {
+        this.gc = gc;
+        this.at = at==null?new AffineTransform():at;
+        this.generalize = generalize;
+        this.maxDistance = maxDistance;
+        currentGeom = 0;
+        done = false;
+//Xulu-01.sc
+//        currentIterator = gc.isEmpty() ? emptyIterator : getIterator(gc.getGeometryN(0));
+        if ( !gc.isEmpty() )
+          currentIterator = getIterator(gc.getGeometryN(0));
+        else {
+          currentIterator = emptyIterator;
+          done = true;
+        }
+//Xulu-01.ec
+
+	}
+
+    /**
+     * Creates a new instance of GeomCollectionIterator
+     *
+     * @param gc The geometry collection the iterator will use
+     * @param at The affine transform applied to coordinates during iteration
+     * @param generalize if true apply simple distance based generalization
+     * @param maxDistance during iteration, a point will be skipped if it's
+     *        distance from the previous is less than maxDistance
+     */
+    public GeomCollectionIterator(
+        GeometryCollection gc, AffineTransform at, boolean generalize,
+		double maxDistance) {
+        init(gc, at, generalize, maxDistance);
+    }
+
+    /**
+     * Sets the distance limit for point skipping during distance based
+     * generalization
+     *
+     * @param distance the maximum distance for point skipping
+     */
+    public void setMaxDistance(double distance) {
+        maxDistance = distance;
+    }
+
+    /**
+     * Returns the distance limit for point skipping during distance based
+     * generalization
+     *
+     * @return the maximum distance for distance based generalization
+     */
+    public double getMaxDistance() {
+        return maxDistance;
+    }
+
+    /**
+     * Returns the specific iterator for the geometry passed.
+     *
+     * @param g The geometry whole iterator is requested
+     *
+     * @return the specific iterator for the geometry passed.
+     */
+    private AbstractLiteIterator getIterator(Geometry g) {
+        AbstractLiteIterator pi = null;
+
+        if (g.isEmpty())
+            return emptyIterator;
+        if (g instanceof Polygon) {
+            Polygon p = (Polygon) g;
+            pi = new PolygonIterator(p, at, generalize, maxDistance);
+        } else if (g instanceof GeometryCollection) {
+            GeometryCollection gc = (GeometryCollection) g;
+            pi = new GeomCollectionIterator(gc, at, generalize, maxDistance);
+        } else if (g instanceof LineString) {
+            LineString ls = (LineString) g;
+            lineIterator.init(ls, at, generalize, (float) maxDistance);
+            pi = lineIterator;
+        } else if (g instanceof LinearRing) {
+            LinearRing lr = (LinearRing) g;
+            lineIterator.init(lr, at, generalize, (float) maxDistance);
+            pi = lineIterator;
+        } else if (g instanceof Point) {
+            Point p = (Point) g;
+            pi = new PointIterator(p, at);
+        }
+
+        return pi;
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the
+     * iteration. The return value is the path-segment type: SEG_MOVETO,
+     * SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. A double array of
+     * length 6 must be passed in and can be used to store the coordinates of
+     * the point(s). Each point is stored as a pair of double x,y coordinates.
+     * SEG_MOVETO and SEG_LINETO types returns one point, SEG_QUADTO returns
+     * two points, SEG_CUBICTO returns 3 points and SEG_CLOSE does not return
+     * any points.
+     *
+     * @param coords an array that holds the data returned from this method
+     *
+     * @return the path-segment type of the current path segment.
+     *
+     * @see #SEG_MOVETO
+     * @see #SEG_LINETO
+     * @see #SEG_QUADTO
+     * @see #SEG_CUBICTO
+     * @see #SEG_CLOSE
+     */
+    public int currentSegment(double[] coords) {
+        return currentIterator.currentSegment(coords);
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the
+     * iteration. The return value is the path-segment type: SEG_MOVETO,
+     * SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. A float array of
+     * length 6 must be passed in and can be used to store the coordinates of
+     * the point(s). Each point is stored as a pair of float x,y coordinates.
+     * SEG_MOVETO and SEG_LINETO types returns one point, SEG_QUADTO returns
+     * two points, SEG_CUBICTO returns 3 points and SEG_CLOSE does not return
+     * any points.
+     *
+     * @param coords an array that holds the data returned from this method
+     *
+     * @return the path-segment type of the current path segment.
+     *
+     * @see #SEG_MOVETO
+     * @see #SEG_LINETO
+     * @see #SEG_QUADTO
+     * @see #SEG_CUBICTO
+     * @see #SEG_CLOSE
+     */
+    public int currentSegment(float[] coords) {
+        return currentIterator.currentSegment(coords);
+    }
+
+    /**
+     * Returns the winding rule for determining the interior of the path.
+     *
+     * @return the winding rule.
+     *
+     * @see #WIND_EVEN_ODD
+     * @see #WIND_NON_ZERO
+     */
+    public int getWindingRule() {
+        return WIND_NON_ZERO;
+    }
+
+    /**
+     * Tests if the iteration is complete.
+     *
+     * @return <code>true</code> if all the segments have been read;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isDone() {
+        return done;
+    }
+
+    /**
+     * Moves the iterator to the next segment of the path forwards along the
+     * primary direction of traversal as long as there are more points in that
+     * direction.
+     */
+    public void next() {
+        if (currentIterator.isDone()) {
+            if (currentGeom < (gc.getNumGeometries() - 1)) {
+                currentGeom++;
+                currentIterator = getIterator(gc.getGeometryN(currentGeom));
+            } else {
+                done = true;
+            }
+        } else {
+            currentIterator.next();
+        }
+    }
+
+}

Added: trunk/src/org/geotools/renderer/lite/gridcoverage2d/GridCoverageRenderer.gt2-2.4.4+Changes
===================================================================
--- trunk/src/org/geotools/renderer/lite/gridcoverage2d/GridCoverageRenderer.gt2-2.4.4+Changes	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/renderer/lite/gridcoverage2d/GridCoverageRenderer.gt2-2.4.4+Changes	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,732 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.renderer.lite.gridcoverage2d;
+
+// J2SE dependencies
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.imageio.ImageIO;
+import javax.media.jai.Interpolation;
+import javax.media.jai.InterpolationNearest;
+import javax.media.jai.JAI;
+
+import org.geotools.coverage.grid.GeneralGridRange;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.coverage.processing.DefaultProcessor;
+import org.geotools.coverage.processing.operation.Crop;
+import org.geotools.coverage.processing.operation.FilteredSubsample;
+import org.geotools.coverage.processing.operation.Resample;
+import org.geotools.coverage.processing.operation.Scale;
+import org.geotools.factory.Hints;
+import org.geotools.geometry.GeneralEnvelope;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
+import org.geotools.referencing.operation.matrix.XAffineTransform;
+import org.geotools.resources.CRSUtilities;
+import org.geotools.resources.coverage.CoverageUtilities;
+import org.geotools.resources.image.ImageUtilities;
+import org.geotools.styling.RasterSymbolizer;
+import org.opengis.coverage.grid.GridCoverage;
+import org.opengis.metadata.spatial.PixelOrientation;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * <b>Xulu:<br>
+ *    Code taken from gt-2.4.4 to make some changes (marked with {@code xulu}),
+ *    which can not be realized in a subclass:</b>
+ *    <ul>
+ *       <li></li>
+ *    </ul>
+ * <br><br>
+ * A helper class for rendering {@link GridCoverage} objects. Support for grid
+ * coverage SLD stylers is still limited.
+ * 
+ * @author Simone Giannecchini
+ * @author Andrea Aime
+ * @author Alessio Fabiani
+ * @source $URL:
+ *         http://svn.geotools.org/geotools/trunk/gt/module/render/src/org/geotools/renderer/lite/GridCoverageRenderer.java $
+ * @version $Id: GridCoverageRenderer.java 18352 2006-03-01 06:13:42Z
+ *          desruisseaux $
+ * 
+ * @task Add support for SLD styles
+ */
+public final class GridCoverageRenderer {
+    /**
+     * This variable is use for testing purposes in order to force this
+     * {@link GridCoverageRenderer} to dump images at various steps on the disk.
+     */
+    private final static boolean DEBUG = Boolean
+            .getBoolean("org.geotools.renderer.lite.gridcoverage2d.debug");
+    
+    protected static final double EPS = 1E-6;
+
+    private static String debugDir;
+    static {
+        if (DEBUG) {
+            final File tempDir = new File(System.getProperty("java.io.tmpdir"));
+            if (!tempDir.exists() || !tempDir.canWrite()) {
+                System.out
+                        .println("Unable to create debug dir, exiting application!!!");
+                System.exit(1);
+                debugDir = null;
+            } else
+                debugDir = tempDir.getAbsolutePath();
+        }
+    }
+
+    /** Cached factory for the {@link Scale} operation. */
+    private final static Scale scaleFactory = new Scale();
+
+    /** Cached factory for the {@link FilteredSubsample} operation. */
+    private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample();
+
+    /** Cached factory for the {@link Crop} operation. */
+    private final static Crop coverageCropFactory = new Crop();
+
+    /** Logger. */
+    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.rendering");
+
+    static {
+
+        // ///////////////////////////////////////////////////////////////////
+        //
+        // Caching parameters for performing the various operations.
+        //  
+        // ///////////////////////////////////////////////////////////////////
+        final DefaultProcessor processor = new DefaultProcessor(
+                CoverageUtilities.LENIENT_HINT);
+        resampleParams = processor.getOperation("Resample").getParameters();
+        cropParams = processor.getOperation("CoverageCrop").getParameters();
+    }
+
+    /** The Display (User defined) CRS * */
+    private final CoordinateReferenceSystem destinationCRS;
+
+    /** Area we want to draw. */
+    private final GeneralEnvelope destinationEnvelope;
+
+    /** Size of the area we want to draw in pixels. */
+    private final Rectangle destinationSize;
+
+    private final GridToEnvelopeMapper gridToEnvelopeMapper;
+
+    private final AffineTransform finalGridToWorld;
+
+    private final AffineTransform finalWorldToGrid;
+
+    private final Hints hints = new Hints(new HashMap(5));
+
+    /** Parameters used to control the {@link Resample} operation. */
+    private final static ParameterValueGroup resampleParams;
+
+    /** Parameters used to control the {@link Crop} operation. */
+    private static ParameterValueGroup cropParams;
+
+    /** Parameters used to control the {@link Scale} operation. */
+    private static final Resample resampleFactory = new Resample();
+
+    /**
+     * Tolerance for NOT drawing a coverage.
+     * 
+     * If after a scaling a coverage has all dimensions smaller than
+     * {@link GridCoverageRenderer#MIN_DIM_TOLERANCE} we just do not draw it.
+     */
+    private static final int MIN_DIM_TOLERANCE = 2;
+
+    /**
+     * Creates a new {@link GridCoverageRenderer} object.
+     * 
+     * @param destinationCRS
+     *            the CRS of the {@link GridCoverage2D} to render.
+     * @param envelope
+     *            delineating the area to be rendered.
+     * @param screenSize
+     *            at which we want to render the source {@link GridCoverage2D}.
+     * @throws TransformException
+     * @throws NoninvertibleTransformException
+     * 
+     */
+    public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS,
+            final Envelope envelope, Rectangle screenSize)
+            throws TransformException, NoninvertibleTransformException {
+
+        this(destinationCRS, envelope, screenSize, null);
+    }
+
+    /**
+     * Creates a new {@link GridCoverageRenderer} object.
+     * 
+     * @param destinationCRS
+     *            the CRS of the {@link GridCoverage2D} to render.
+     * @param envelope
+     *            delineating the area to be rendered.
+     * @param screenSize
+     *            at which we want to render the source {@link GridCoverage2D}.
+     * @param java2dHints
+     *            to control this rendering process.
+     * 
+     * @throws TransformException
+     * @throws NoninvertibleTransformException
+     */
+    public GridCoverageRenderer(final CoordinateReferenceSystem destinationCRS,
+            final Envelope envelope, Rectangle screenSize,
+            RenderingHints java2dHints) throws TransformException,
+            NoninvertibleTransformException {
+
+        // ///////////////////////////////////////////////////////////////////
+        //
+        // Initialize this renderer
+        //
+        // ///////////////////////////////////////////////////////////////////
+        this.destinationSize = screenSize;
+        this.destinationCRS = CRSUtilities.getCRS2D(destinationCRS);
+        gridToEnvelopeMapper = new GridToEnvelopeMapper();
+        gridToEnvelopeMapper.setGridType(PixelInCell.CELL_CORNER);
+        gridToEnvelopeMapper
+                .setGridRange(new GeneralGridRange(destinationSize));
+        destinationEnvelope = new GeneralEnvelope(new ReferencedEnvelope(
+                envelope, destinationCRS));
+        // ///////////////////////////////////////////////////////////////////
+        //
+        // FINAL DRAWING DIMENSIONS AND RESOLUTION
+        // I am here getting the final drawing dimensions (on the device) and
+        // the resolution for this renderer but in the CRS of the source coverage
+        // since I am going to compare this info with the same info for the
+        // source coverage.
+        //
+        // ///////////////////////////////////////////////////////////////////
+        gridToEnvelopeMapper.setEnvelope(destinationEnvelope);
+        finalGridToWorld = new AffineTransform(gridToEnvelopeMapper
+                .createAffineTransform());
+        finalWorldToGrid = finalGridToWorld.createInverse();
+
+        // ///////////////////////////////////////////////////////////////////
+        //
+        // HINTS
+        //
+        // ///////////////////////////////////////////////////////////////////
+        if (java2dHints != null)
+            this.hints.add(java2dHints);
+        // this prevents users from overriding leninet hint
+        this.hints.add(CoverageUtilities.LENIENT_HINT);
+        this.hints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
+    }
+
+    /**
+     * Paint this grid coverage. The caller must ensure that
+     * <code>graphics</code> has an affine transform mapping "real world"
+     * coordinates in the coordinate system given by {@link
+     * #getCoordinateSystem}.
+     * 
+     * @param graphics
+     *            the {@link Graphics2D} context in which to paint.
+     * @param gridCoverage 
+     *            the {@link GridCoverage2D} to be rendered.
+     * @param symbolizer 
+     *            the {@link RasterSymbolizer} for the rendering.
+     * @throws FactoryException
+     * @throws TransformException
+     * @throws NoninvertibleTransformException
+     * @throws Exception
+     * @throws UnsupportedOperationException
+     *             if the transformation from grid to coordinate system in the
+     *             GridCoverage is not an AffineTransform
+     */
+    public void paint(final Graphics2D graphics,
+            final GridCoverage2D gridCoverage, final RasterSymbolizer symbolizer)
+            throws FactoryException, TransformException,
+            NoninvertibleTransformException {
+
+         if (LOGGER.isLoggable(Level.FINE))
+                LOGGER.fine(new StringBuffer("Drawing coverage ").append(gridCoverage.toString()).toString());
+            // ///////////////////////////////////////////////////////////////////
+            //
+            // Getting information about the source coverage like the source CRS,
+            // the source envelope and the source geometry.
+            //
+            // ///////////////////////////////////////////////////////////////////
+            final CoordinateReferenceSystem sourceCoverageCRS = gridCoverage.getCoordinateReferenceSystem2D();
+            final GeneralEnvelope sourceCoverageEnvelope = (GeneralEnvelope) gridCoverage.getEnvelope();
+            final GridGeometry2D sourceGridGeometry=(GridGeometry2D) gridCoverage.getGridGeometry();
+            final boolean simpleG2WTransform=CoverageUtilities.isSimpleGridToWorldTransform((AffineTransform) sourceGridGeometry.getGridToCRS2D(PixelOrientation.UPPER_LEFT),1E-3);
+
+            // ///////////////////////////////////////////////////////////////////
+            //
+            // GET THE CRS MAPPING
+            //
+            // This step I instantiate the MathTransform for going from the source
+            // crs to the destination crs.
+            //
+            // ///////////////////////////////////////////////////////////////////
+            // math transform from source to target crs
+            final MathTransform sourceCRSToDestinationCRSTransformation = CRS.findMathTransform(sourceCoverageCRS, destinationCRS, true);
+            final MathTransform destinationCRSToSourceCRSTransformation = sourceCRSToDestinationCRSTransformation.inverse();
+            final boolean doReprojection = !sourceCRSToDestinationCRSTransformation.isIdentity();
+            if (LOGGER.isLoggable(Level.FINE))
+                LOGGER.fine(
+                        new StringBuffer("Transforming coverage envelope with transform ").
+                            append(destinationCRSToSourceCRSTransformation.toWKT()).toString());
+            
+            // //
+            //
+            // Do we need reprojection?
+            //
+            // //
+            GeneralEnvelope destinationEnvelopeInSourceCRS;
+            if (doReprojection) {
+                // /////////////////////////////////////////////////////////////////////
+                //
+                // PHASE 1
+                //
+                // PREPARING THE REQUESTED ENVELOPE FOR LATER INTERSECTION
+                //
+                // /////////////////////////////////////////////////////////////////////
+
+                // //
+                //
+                // Try to convert the destination envelope in the source crs. If
+                // this fails we pass through WGS84 as an intermediate step
+                //
+                // //
+                try {
+                    // convert the destination envelope to the source coverage
+                    // native crs in order to try and crop it. If we get an error we
+                    // try to
+                    // do this in two steps using WGS84 as a pivot. This introduces
+                    // some erros (it usually
+                    // increases the envelope we want to check) but it is still
+                    // useful.
+                    destinationEnvelopeInSourceCRS = CRS.transform(
+                            destinationCRSToSourceCRSTransformation,
+                            destinationEnvelope);
+                } catch (TransformException te) {
+                    // //
+                    //
+                    // Convert the destination envelope to WGS84 if needed for safer
+                    // comparisons later on with the original crs of this coverage.
+                    //
+                    // //
+                    final GeneralEnvelope destinationEnvelopeWGS84;
+                    if (!CRS.equalsIgnoreMetadata(destinationCRS,
+                            DefaultGeographicCRS.WGS84)) {
+                        // get a math transform to go to WGS84
+                        final MathTransform destinationCRSToWGS84transformation = CRS
+                                .findMathTransform(destinationCRS,
+                                        DefaultGeographicCRS.WGS84, true);
+                        if (!destinationCRSToWGS84transformation.isIdentity()) {
+                            destinationEnvelopeWGS84 = CRS.transform(
+                                    destinationCRSToWGS84transformation,
+                                    destinationEnvelope);
+                            destinationEnvelopeWGS84
+                                    .setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
+                        } else {
+                            destinationEnvelopeWGS84 = new GeneralEnvelope(
+                                    destinationEnvelope);
+                        }
+
+                    } else {
+                        destinationEnvelopeWGS84 = new GeneralEnvelope(
+                                destinationEnvelope);
+                    }
+
+                    // //
+                    //
+                    // Convert the requested envelope from WGS84 to the source crs
+                    // for cropping the provided coverage.
+                    //
+                    // //
+                    if (!CRS.equalsIgnoreMetadata(sourceCoverageCRS,
+                            DefaultGeographicCRS.WGS84)) {
+                        // get a math transform to go to WGS84
+                        final MathTransform WGS84ToSourceCoverageCRSTransformation = CRS
+                                .findMathTransform(DefaultGeographicCRS.WGS84,
+                                        sourceCoverageCRS, true);
+                        if (!WGS84ToSourceCoverageCRSTransformation.isIdentity()) {
+                            destinationEnvelopeInSourceCRS = CRS.transform(
+                                    WGS84ToSourceCoverageCRSTransformation,
+                                    destinationEnvelopeWGS84);
+                            destinationEnvelopeInSourceCRS
+                                    .setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
+                        } else {
+                            destinationEnvelopeInSourceCRS = new GeneralEnvelope(
+                                    destinationEnvelopeWGS84);
+                        }
+                    } else {
+                        destinationEnvelopeInSourceCRS = new GeneralEnvelope(
+                                destinationEnvelopeWGS84);
+                    }
+
+                }
+            } else
+                destinationEnvelopeInSourceCRS = new GeneralEnvelope(
+                        destinationEnvelope);
+            // /////////////////////////////////////////////////////////////////////
+            //
+            // NOW CHECKING THE INTERSECTION IN WGS84
+            //
+            // //
+            //
+            // If the two envelopes intersect each other in WGS84 we are
+            // reasonably sure that they intersect
+            //
+            // /////////////////////////////////////////////////////////////////////
+            final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(destinationEnvelopeInSourceCRS);
+            intersectionEnvelope.intersect(sourceCoverageEnvelope);
+            if (intersectionEnvelope.isEmpty()||intersectionEnvelope.isNull()) {
+                if (LOGGER.isLoggable(Level.FINE)) {
+                    LOGGER
+                            .warning("The destination envelope does not intersect the envelope of the source coverage.");
+                }
+                return;
+            }
+
+
+            final Interpolation interpolation = (Interpolation) hints
+                    .get(JAI.KEY_INTERPOLATION);
+            if (LOGGER.isLoggable(Level.FINE))
+                LOGGER.fine(new StringBuffer("Using interpolation ").append(
+                        interpolation).toString());
+
+
+            // /////////////////////////////////////////////////////////////////////
+            //
+            // CROPPING Coverage
+            //
+            // /////////////////////////////////////////////////////////////////////
+            GridCoverage2D preResample=gridCoverage;
+            if(simpleG2WTransform)
+            {
+                preResample = getCroppedCoverage(gridCoverage, intersectionEnvelope, sourceCoverageCRS);
+                if (preResample == null) {
+                    // nothing to render, the AOI does not overlap
+                    if (LOGGER.isLoggable(Level.FINE))
+                        LOGGER.fine(
+                                new StringBuffer("Skipping current coverage because cropped to an empty area").toString());
+                    return;
+                }
+                if (DEBUG) {
+                    try {
+                        ImageIO.write(
+                                preResample.geophysics(false).getRenderedImage(),
+                                "tiff",
+                                new File(debugDir,"cropped.tiff"));
+                    } catch (IOException e) {
+                        LOGGER.info(e.getLocalizedMessage());
+                    }
+                }
+            }
+                
+            
+            // /////////////////////////////////////////////////////////////////////
+            //
+            // Reproject
+            //
+            // /////////////////////////////////////////////////////////////////////
+            GridCoverage2D preSymbolizer;
+            if (doReprojection) {
+                preSymbolizer = resample(preResample, destinationCRS,
+                        interpolation == null ? new InterpolationNearest()
+                                : interpolation, destinationEnvelope);
+                if (LOGGER.isLoggable(Level.FINE))
+                    LOGGER.fine(new StringBuffer("Reprojecting to crs ").append(
+                            destinationCRS.toWKT()).toString());
+            } else
+                preSymbolizer = preResample;
+
+            if (DEBUG) {
+
+                try {
+                    ImageIO.write(preSymbolizer.geophysics(false)
+                            .getRenderedImage(), "tiff", new File(debugDir,
+                            "preSymbolizer.tiff"));
+                } catch (IOException e) {
+                    LOGGER.info(e.getLocalizedMessage());
+                }
+            }
+
+            // ///////////////////////////////////////////////////////////////////
+            //
+            // Apply RasterSymbolizer
+            //
+            // ///////////////////////////////////////////////////////////////////
+            if (LOGGER.isLoggable(Level.FINE))
+                LOGGER.fine(new StringBuffer("Raster Symbolizer ").toString());
+            final RasterSymbolizerSupport rsp = new RasterSymbolizerSupport(
+                    symbolizer);
+            final GridCoverage2D recoloredGridCoverage = (GridCoverage2D) rsp
+                    .recolorCoverage(preSymbolizer);
+//xulu.sc
+            final RenderedImage finalImage = recoloredGridCoverage
+                    .geophysics(false).getRenderedImage();
+
+//            int width  = preSymbolizer.getRenderedImage().getWidth();
+//            int height = preSymbolizer.getRenderedImage().getHeight();
+//            int minX   = preSymbolizer.getRenderedImage().getMinX();
+//            int minY   = preSymbolizer.getRenderedImage().getMinY();
+//            int maxX   = minX+width;
+//            int maxY   = minY+height;
+//            final BufferedImage finalImage = new BufferedImage(
+//                width,
+//                height,
+//                BufferedImage.TYPE_INT_ARGB
+//            );
+//            Graphics2D gr = (Graphics2D) finalImage.getGraphics();
+//            for (int x=minX; x<maxX; x++)
+//              for (int y=minY; y<maxY; y++) {
+//                float value = preSymbolizer.getRenderedImage().getData().getSampleFloat(x, y, 0);
+//                int   rgb   = 0;
+//                if ( value == 100 )
+//                  rgb = Color.WHITE.getRGB();
+//                if ( value == 240 )
+//                  rgb = Color.RED.getRGB();
+//                if ( value == 310 )
+//                  rgb = Color.GREEN.getRGB();
+//                if ( value == 320 )
+//                  rgb = Color.BLUE.getRGB();
+//                  
+//                finalImage.setRGB(x, y, rgb); 
+//                
+//              }
+            
+            
+//xulu.ec            
+            // ///////////////////////////////////////////////////////////////////
+            //
+            // DRAW ME
+            // I need the grid to world transform for drawing this grid coverage to
+            // the display
+            //
+            // ///////////////////////////////////////////////////////////////////
+            final AffineTransform finalGCgridToWorld = new AffineTransform(
+                    (AffineTransform) ((GridGeometry2D) recoloredGridCoverage
+                            .getGridGeometry()).getGridToCRS2D());
+            if (!(finalGCgridToWorld instanceof AffineTransform)) {
+                throw new UnsupportedOperationException(
+                        "Non-affine transformations not yet implemented"); // TODO
+            }
+
+            // //
+            //
+            // I need to translate half of a pixel since in wms 1.1.1 the envelope
+            // map to the corners of the raster space not to the center of the
+            // pixels.
+            //
+            // //
+            finalGCgridToWorld.translate(-0.5, -0.5); // Map to upper-left corner.
+
+            // //
+            //
+            // I am going to concatenate the final world to grid transform for the
+            // screen area with the grid to world transform of the input coverage.
+            //
+            // This way i right away position the coverage at the right place in the
+            // area of interest for the device.
+            //
+            // //
+            final AffineTransform clonedFinalWorldToGrid = (AffineTransform) finalWorldToGrid
+                    .clone();
+            clonedFinalWorldToGrid.concatenate(finalGCgridToWorld);
+            if (LOGGER.isLoggable(Level.FINE))
+                LOGGER.fine(new StringBuffer("clonedFinalWorldToGrid ").append(clonedFinalWorldToGrid.toString()).toString());
+
+            // it should be a simple translation TODO check
+            final RenderingHints oldHints = graphics.getRenderingHints();
+            graphics.setRenderingHints(this.hints);
+
+            // //
+            // Opacity
+            // //
+            final float alpha = rsp.getOpacity();
+            final Composite oldAlphaComposite = graphics.getComposite();
+            graphics.setComposite(AlphaComposite.getInstance(
+                    AlphaComposite.SRC_OVER, alpha));
+            try {
+                // //
+                // Drawing the Image
+                // //
+                graphics.drawRenderedImage(finalImage, clonedFinalWorldToGrid);
+            } catch (Throwable t) {
+                try {
+                    if (DEBUG) {
+                        try {
+                            ImageIO.write(finalImage, "tiff", new File(debugDir,
+                                    "final0.tiff"));
+                        } catch (IOException e) {
+
+                            e.printStackTrace();
+                        }
+                    }
+                    // /////////////////////////////////////////////////////////////
+                    // this is a workaround for a bug in Java2D
+                    // (see bug 4723021
+                    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4723021).
+                    //
+                    // AffineTransformOp.filter throws a
+                    // java.awt.image.ImagingOpException: Unable to tranform src
+                    // image when a PixelInterleavedSampleModel is used.
+                    //
+                    // CUSTOMER WORKAROUND :
+                    // draw the BufferedImage into a buffered image of type ARGB
+                    // then perform the affine transform. THIS OPERATION WASTES
+                    // RESOURCES BY PERFORMING AN ALLOCATION OF MEMORY AND A COPY ON
+                    // LARGE IMAGES.
+                    // /////////////////////////////////////////////////////////////
+                    final BufferedImage buf = new BufferedImage((int) finalImage
+                            .getWidth(), (int) finalImage.getHeight(),
+                            BufferedImage.TYPE_4BYTE_ABGR);
+                    final Graphics2D g = (Graphics2D) buf.getGraphics();
+                    g.drawRenderedImage(finalImage, AffineTransform
+                            .getScaleInstance(1, 1));
+                    g.dispose();
+                    if (DEBUG) {
+                        try {
+                            ImageIO.write(buf, "tiff", new File(debugDir,
+                                    "final1.tiff"));
+                        } catch (IOException e1) {
+
+                        }
+                    }
+
+                    graphics.drawImage(buf, clonedFinalWorldToGrid, null);
+                    buf.flush();
+
+                } catch (Throwable t1) {
+                    // if fthe workaround fails again, there is really nothing to do
+                    // :-(
+                    LOGGER.log(Level.WARNING, t1.getLocalizedMessage(), t1);
+                }
+            }
+
+            // ///////////////////////////////////////////////////////////////////
+            //
+            // Restore old elements
+            //
+            // ///////////////////////////////////////////////////////////////////
+            graphics.setComposite(oldAlphaComposite);
+            graphics.setRenderingHints(oldHints);
+    }
+
+    /**
+     * Reprojecting the input coverage using the provided parameters.
+     * 
+     * @param gc 
+     *            the {@link GridCoverage2D} to be resampled.
+     * @param crs
+     *            the destination CRS
+     * @param interpolation 
+     *            the desired type of interpolation to be used when resampling. 
+     * @param destinationEnvelope 
+     *            the destination envelope.
+     * @return the resampled grid coverage.
+     * @throws FactoryException
+     */
+    private GridCoverage2D resample(final GridCoverage2D gc,
+            CoordinateReferenceSystem crs, final Interpolation interpolation,
+            final GeneralEnvelope destinationEnvelope) throws FactoryException {
+        // paranoid check
+        assert CRS.equalsIgnoreMetadata(destinationEnvelope
+                .getCoordinateReferenceSystem(), crs)|| CRS
+                .findMathTransform(
+                        destinationEnvelope
+                                .getCoordinateReferenceSystem(), crs)
+                .isIdentity();
+
+        final ParameterValueGroup param = (ParameterValueGroup) resampleParams
+                .clone();
+        param.parameter("source").setValue(gc);
+        param.parameter("CoordinateReferenceSystem").setValue(crs);
+//      param.parameter("GridGeometry").setValue(
+//              new GridGeometry2D(gc.getGridGeometry().getGridRange(),
+//                      destinationEnvelope));
+        param.parameter("InterpolationType").setValue(interpolation);
+        return (GridCoverage2D) resampleFactory.doOperation(param, hints);
+    }
+    
+    /**
+     * Checks the transformation is a pure scale/translate instance (using a tolerance)
+     * @param transform
+     * @return
+     */
+    private boolean isScaleTranslate(MathTransform transform) {
+        if(!(transform instanceof AffineTransform))
+            return false;
+        final AffineTransform at = new AffineTransform((AffineTransform) transform);
+        XAffineTransform.round(at, EPS);
+        final double rotation= XAffineTransform.getRotation(at);
+        final boolean retVal =(Math.abs(rotation)==0);
+        return retVal;
+    }
+
+    /**
+     * Cropping the provided coverage to the requested geographic area.
+     * 
+     * @param gc 
+     *            the source {@link GridCoverage2D}.
+     * @param envelope 
+     *            the requested envelope.
+     * @param crs
+     *            the coordinate reference system.
+     * @return a cropped coverage.
+     */
+    private GridCoverage2D getCroppedCoverage(GridCoverage2D gc,
+            GeneralEnvelope envelope, CoordinateReferenceSystem crs) {
+        final GeneralEnvelope oldEnvelope = (GeneralEnvelope) gc.getEnvelope();
+        // intersect the envelopes in order to prepare for cropping the coverage
+        // down to the needed resolution
+        final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(
+                envelope);
+        intersectionEnvelope.setCoordinateReferenceSystem(crs);
+        intersectionEnvelope.intersect((GeneralEnvelope) oldEnvelope);
+
+        // Do we have something to show? After the crop I could get a null
+        // coverage which would mean nothing to show.
+        if (intersectionEnvelope.isEmpty())
+            return null;
+
+        // crop
+        final ParameterValueGroup param = (ParameterValueGroup) cropParams
+                .clone();
+        param.parameter("source").setValue(gc);
+        param.parameter("ConserveEnvelope").setValue(Boolean.TRUE);
+        param.parameter("Envelope").setValue(intersectionEnvelope);
+        return (GridCoverage2D) coverageCropFactory.doOperation(param, hints);
+    }
+}

Added: trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.gt2-2.4.4+Changes
===================================================================
--- trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.gt2-2.4.4+Changes	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.gt2-2.4.4+Changes	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,346 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.renderer.lite.gridcoverage2d;
+
+// J2SE dependencies
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.media.jai.util.Range;
+
+import org.geotools.coverage.Category;
+import org.geotools.coverage.FactoryFinder;
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.ColorMapEntry;
+import org.geotools.styling.RasterSymbolizer;
+import org.opengis.coverage.SampleDimension;
+import org.opengis.coverage.grid.GridCoverage;
+import org.opengis.filter.expression.Expression;
+
+import schmitzm.geotools.styling.StylingUtil;
+
+/**
+ * A helper class for {@link GridCoverage} objects rendering SLD stylers
+ * support.
+ * 
+ * @author Alessio Fabiani
+ * @author Simone Giannecchini
+ * 
+ * @task Optimize and complete
+ * @source $URL:
+ *         http://svn.geotools.org/geotools/branches/coverages_branch/trunk/gt/module/render/src/org/geotools/renderer/lite/RasterSymbolizerSupport.java $
+ */
+public final class RasterSymbolizerSupport {
+    /** The Styled Layer Descriptor * */
+    private final RasterSymbolizer symbolizer;
+
+    /**
+     * Constructor
+     * 
+     * @param symbolizer
+     * 
+     */
+    public RasterSymbolizerSupport(final RasterSymbolizer symbolizer) {
+        this.symbolizer = symbolizer;
+    }
+
+    public GridCoverage recolorCoverage(GridCoverage grid)
+            throws IllegalArgumentException {
+        if (!(grid instanceof GridCoverage2D)) {
+            throw new IllegalArgumentException(
+                    "Cannot Recolor GridCoverage: GridCoverage2D is needed.");
+        }
+
+        // final ColorMap map = symbolizer.getColorMap();
+        // if (map == null)
+        // throw new IllegalArgumentException(
+        // "Cannot Recolor GridCoverage: ColorMap is needed.");
+        // final ColorMapEntry[] entries = map.getColorMapEntries();
+        // if (entries == null || entries.length <= 0)
+        // throw new IllegalArgumentException(
+        // "Cannot Recolor GridCoverage: ColorMapEntries are needed.");
+        // final int length = entries.length;
+        // final Map colorMap = new HashMap();
+        // for (int i = 0; i < length; i++)
+        // colorMap.put(entries[i].getQuantity().getValue(null), entries[i]
+        // .getQuantity().getValue(null));
+        // return Operations.DEFAULT.recolor(grid, new Map[] { Collections
+        // .singletonMap(null, colorMap) });
+
+        final GridCoverage2D gridCoverage = (GridCoverage2D) grid;
+        final int numBands = gridCoverage.getNumSampleDimensions();
+        final GridSampleDimension[] targetBands = new GridSampleDimension[numBands];
+        final Map[] colorMaps = new Map[numBands];
+        for (int band = 0; band < numBands; band++) { // TODO get separated
+            // R,G,B colorMaps from
+            // symbolizer
+            final Map categories = getCategories(band);
+            colorMaps[band] = categories;
+
+            /**
+             * Temporary solution, until the Recolor Operation is ported ...
+             */
+//xulu.sc
+//            targetBands[band] = (GridSampleDimension) transformColormap(band,
+//                    gridCoverage.getSampleDimension(band), colorMaps);
+            targetBands[band] = transformColormap(
+                (GridSampleDimension)gridCoverage.getSampleDimension(band), this.symbolizer);
+//xulu.ec
+        }
+
+        GridCoverage2D createed = FactoryFinder.getGridCoverageFactory(null).create(
+                gridCoverage.getName(), gridCoverage.getRenderedImage(),
+                (GridGeometry2D) gridCoverage.getGridGeometry(), targetBands,
+                new GridCoverage[] { gridCoverage }, null);
+        return createed;
+    }
+
+    /**
+     * HELPER FUNCTIONS
+     */
+    public float getOpacity() {
+        float alpha = 1.0f;
+        Expression exp = this.symbolizer.getOpacity();
+        if (exp == null){
+            return alpha;
+        }
+        Number number = (Number) exp.evaluate(null,Float.class);
+        if (number == null){
+            return alpha;
+        }
+        return number.floatValue();
+    }
+
+    public Map getCategories(final int band) {
+        final String[] labels = getLabels(band);
+        final Color[] colors = getColors(band);
+
+        final Map categories = new HashMap();
+
+        /**
+         * Checking Categories
+         */
+        final int labelsLength = labels.length;
+        Color[] oldCmap;
+        int length;
+        Color[] newCmap;
+        for (int i = 0; i < labelsLength; i++) {
+            if (!categories.containsKey(labels[i])) {
+                categories.put(labels[i], new Color[] { colors[i] });
+            } else {
+                oldCmap = (Color[]) categories.get(labels[i]);
+                length = oldCmap.length;
+                newCmap = new Color[length + 1];
+                System.arraycopy(oldCmap, 0, newCmap, 0, length);
+                newCmap[length] = colors[i];
+                categories.put(labels[i], newCmap);
+            }
+        }
+
+        return categories;
+    }
+
+    public String[] getLabels(final int band) {
+        String[] labels = null;
+        if (this.symbolizer.getColorMap() != null) {
+            final ColorMapEntry[] colors = this.symbolizer.getColorMap()
+                    .getColorMapEntries();
+            final int numColors = colors.length;
+            labels = new String[numColors];
+            for (int ci = 0; ci < numColors; ci++) {
+                labels[ci] = colors[ci].getLabel();
+            }
+        }
+
+        return labels;
+    }
+
+    public Color[] getColors(final int band) {
+        Color[] colorTable = null;
+        if (this.symbolizer.getColorMap() != null) {
+            final ColorMapEntry[] colors = this.symbolizer.getColorMap()
+                    .getColorMapEntries();
+            final int numColors = colors.length;
+            colorTable = new Color[numColors];
+            double opacity;
+            for (int ci = 0; ci < numColors; ci++) {
+                opacity = toOpacity( colors[ci].getOpacity() );
+                colorTable[ci] = toColor( colors[ci].getColor(), opacity );
+                if (colorTable[ci] == null){
+                    return null;
+                }
+            }
+        }
+
+        return colorTable;
+    }
+    public static Color toColor( Expression exp, double opacity ){
+        if (exp == null){
+            return null;
+        }
+        Color color = (Color) exp.evaluate(null, Color.class);
+        int alpha = new Double(Math.ceil(255.0 * opacity)).intValue();        
+        if( color != null ){                       
+            return new Color( color.getRed(), color.getGreen(), color.getBlue(), alpha );            
+        }
+        else {
+            // the value morphing code failed us .. let's try by hand
+            Object obj = exp.evaluate( null );
+            if (obj == null){
+                return null;
+            }
+            Integer  intval = Integer.decode((String) obj);
+            int i = intval.intValue();            
+            return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF, alpha);
+        }
+    }
+    public static double toOpacity( Expression opacity ) {
+        if (opacity == null) {
+            return 1.0;
+        }
+         
+        Double value = (Double) opacity.evaluate(null, Double.class);
+        if (value == null) {
+            return 1.0;
+        }
+        return value.doubleValue();
+//
+//        opacity = (colors[ci].getOpacity() != null
+//                ? (colors[ci].getOpacity().getValue(null) instanceof String
+//                        ? Double.valueOf((String) colors[ci].getOpacity().getValue(null))
+//                        : (Double) colors[ci].getOpacity().getValue(null))
+//                : new Double(1.0));
+    }
+
+//xulu.sn  
+    public GridSampleDimension transformColormap(GridSampleDimension dimension, RasterSymbolizer symbolizer) {
+      if ( symbolizer == null )
+        return dimension;
+
+      Map<Number,Category> categories = new HashMap<Number,Category>(); 
+      double           opacity        = StylingUtil.getOpacityFromExpression(symbolizer.getOpacity());
+      for ( ColorMapEntry entry : symbolizer.getColorMap().getColorMapEntries() ) {
+        double   quantity = StylingUtil.getQuantityFromColorMapEntry(entry);
+        int      intQuant = (int)Math.round(quantity);
+        Color    color    = Color.cyan; //StylingUtil.getColorFromColorMapEntry(entry);
+        switch (intQuant) {
+          case 100: color = Color.WHITE; break; 
+          case 240: color = Color.RED; break; 
+          case 320: color = Color.GREEN; break; 
+          case 330: color = Color.BLUE; break; 
+        }
+//        Category category = dimension.getCategory(quantity);
+//        if ( category != null )
+//          category.recolor( new Color[] {color} );
+//        else
+//          category = new Category("", color, quantity);
+        String   label    = entry.getLabel();
+        if ( label == null )
+          label = "";
+//        Category category = new Category(label, new Color[] {color}, intQuant, intQuant+1, 1,0);
+        Category category = new Category(label, color, quantity);
+        categories.put(quantity,category);
+      }
+
+//      if ( !categories.containsKey(Double.NaN) )
+//        categories.put(Double.NaN, Category.NODATA );
+//      if ( !categories.containsKey(Float.NaN) )
+//        categories.put(Float.NaN, Category.NODATA );
+
+//      GridSampleDimension gsd = new GridSampleDimension(
+//          dimension.getDescription(),
+//          categories.values().toArray(new Category[0]),
+//          null//dimension.getUnits()
+//      );
+      String[] labels = new String[330];
+      Color[]  colors = new Color[330];
+      Random   random = new Random();
+      for (int i=0; i<labels.length; i++) {
+        labels[i] = "";
+        colors[i] = new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255));
+      }
+      labels[ 99] = "weiss"; colors[ 99] = Color.WHITE;
+      labels[239] = "rot";   colors[239] = Color.RED;
+      labels[319] = "gruen"; colors[319] = Color.GREEN;
+      labels[329] = "blau";  colors[329] = Color.BLUE;
+      GridSampleDimension gsd = new GridSampleDimension(
+          dimension.getDescription(),
+          labels,
+          colors
+      );
+      
+      return gsd;
+    }
+//xulu.en
+    
+    /**
+     * Transform the supplied RGB colors.
+     */
+    public SampleDimension transformColormap(final int band,
+            SampleDimension dimension, final Map[] colorMaps) {
+        if (colorMaps == null || colorMaps.length == 0) {
+            return dimension;
+        }
+        boolean changed = false;
+        final Map colorMap = colorMaps[Math.min(band, colorMaps.length - 1)];
+        final List categoryList = ((GridSampleDimension) dimension)
+                .getCategories();
+        if (categoryList == null) {
+            return dimension;
+        }
+        final Category categories[] = (Category[]) categoryList.toArray();
+        Category category;
+        Color[] colors;
+        final int length = categories.length;
+        Range range;
+        int lower;
+        int upper;
+        for (int j = length; --j >= 0;) {
+            category = categories[j];
+            colors = (Color[]) colorMap.get(category.getName().toString());
+            if (colors == null) {
+                if (!category.isQuantitative()) {
+                    continue;
+                }
+                colors = (Color[]) colorMap.get(null);
+                if (colors == null) {
+                    continue;
+                }
+            }
+            range = category.getRange();
+            lower = ((Number) range.getMinValue()).intValue();
+            upper = ((Number) range.getMaxValue()).intValue();
+            if (!range.isMinIncluded())
+                lower++;
+            if (range.isMaxIncluded())
+                upper++;
+            category = category.recolor(colors);
+            if (!categories[j].equals(category)) {
+                categories[j] = category;
+                changed = true;
+            }
+        }
+        return changed ? new GridSampleDimension(dimension.getDescription(),
+                categories, dimension.getUnits()) : dimension;
+    }
+}

Added: trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.java
===================================================================
--- trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/org/geotools/renderer/lite/gridcoverage2d/RasterSymbolizerSupport.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,477 @@
+/*
+ *    GeoTools - OpenSource mapping toolkit
+ *    http://geotools.org
+ *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.renderer.lite.gridcoverage2d;
+
+// J2SE dependencies
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.media.jai.util.Range;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.Category;
+import org.geotools.coverage.FactoryFinder;
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.resources.i18n.Vocabulary;
+import org.geotools.resources.i18n.VocabularyKeys;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.ColorMapEntry;
+import org.geotools.styling.RasterSymbolizer;
+import org.geotools.util.NumberRange;
+import org.opengis.coverage.SampleDimension;
+import org.opengis.coverage.grid.GridCoverage;
+import org.opengis.filter.expression.Expression;
+
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.lang.LangUtil;
+
+/**
+ * A helper class for {@link GridCoverage} objects rendering SLD stylers
+ * support.
+ * 
+ * @author Alessio Fabiani
+ * @author Simone Giannecchini
+ * 
+ * @task Optimize and complete
+ * @source $URL:
+ *         http://svn.geotools.org/geotools/branches/coverages_branch/trunk/gt/module/render/src/org/geotools/renderer/lite/RasterSymbolizerSupport.java $
+ */
+public final class RasterSymbolizerSupport {
+//ms-01.sn
+    private static Logger LOGGER = Logger.getLogger( RasterSymbolizerSupport.class.getName() );
+    /** Types for the recoloring process. */
+    public static enum RECOLOR_MODE_TYPE {
+      /** The way of geotools 2.4.4. The color map is used only for
+       *  the {@link SampleDimension (Grid)SampleDimensions} already defined
+       *  in the {@link GridCoverage2D}. */
+      GT_CLASSIC,
+      /** Alternative way of <a href="">Martin Schmitz</a>. The {@link GridCoverage2D}
+       *  is resampled accordingly to the given color map. */ 
+      MS_EXTENTION
+    }
+    /** The way the recoloring is processed. */
+    public static RECOLOR_MODE_TYPE RECOLOR_MODE = RECOLOR_MODE_TYPE.MS_EXTENTION; 
+//ms-01.en
+    
+    /** The Styled Layer Descriptor * */
+    private final RasterSymbolizer symbolizer;
+
+    /**
+     * Constructor
+     * 
+     * @param symbolizer
+     * 
+     */
+    public RasterSymbolizerSupport(final RasterSymbolizer symbolizer) {
+        this.symbolizer = symbolizer;
+    }
+
+    public GridCoverage recolorCoverage(GridCoverage grid)
+            throws IllegalArgumentException {
+        if (!(grid instanceof GridCoverage2D)) {
+            throw new IllegalArgumentException(
+                    "Cannot Recolor GridCoverage: GridCoverage2D is needed.");
+        }
+        
+        // final ColorMap map = symbolizer.getColorMap();
+        // if (map == null)
+        // throw new IllegalArgumentException(
+        // "Cannot Recolor GridCoverage: ColorMap is needed.");
+        // final ColorMapEntry[] entries = map.getColorMapEntries();
+        // if (entries == null || entries.length <= 0)
+        // throw new IllegalArgumentException(
+        // "Cannot Recolor GridCoverage: ColorMapEntries are needed.");
+        // final int length = entries.length;
+        // final Map colorMap = new HashMap();
+        // for (int i = 0; i < length; i++)
+        // colorMap.put(entries[i].getQuantity().getValue(null), entries[i]
+        // .getQuantity().getValue(null));
+        // return Operations.DEFAULT.recolor(grid, new Map[] { Collections
+        // .singletonMap(null, colorMap) });
+
+        final GridCoverage2D gridCoverage = (GridCoverage2D) grid;
+        final int numBands = gridCoverage.getNumSampleDimensions();
+        final GridSampleDimension[] targetBands = new GridSampleDimension[numBands];
+        final Map[] colorMaps = new Map[numBands];
+        for (int band = 0; band < numBands; band++) { // TODO get separated
+//ms-01.sn
+//            LOGGER.debug("===== Debug original dimension =====");
+//            for ( Category c : (List<Category>)((GridSampleDimension)gridCoverage.getSampleDimension(band)).getCategories() )
+//              LOGGER.debug( c.toString() );
+            if ( RECOLOR_MODE == RECOLOR_MODE.MS_EXTENTION ) {
+              targetBands[band] = transformColormap(
+                  (GridSampleDimension)gridCoverage.getSampleDimension(band), this.symbolizer);
+              if ( targetBands[band] != gridCoverage.getSampleDimension(band))
+                continue;
+            }
+//ms-01.en
+            // R,G,B colorMaps from
+            // symbolizer
+            final Map categories = getCategories(band);
+            colorMaps[band] = categories;
+
+            /**
+             * Temporary solution, until the Recolor Operation is ported ...
+             */
+            targetBands[band] = (GridSampleDimension) transformColormap(band,
+                    gridCoverage.getSampleDimension(band), colorMaps);
+        }
+
+        GridCoverage2D createed = FactoryFinder.getGridCoverageFactory(null).create(
+                gridCoverage.getName(), gridCoverage.getRenderedImage(),
+                (GridGeometry2D) gridCoverage.getGridGeometry(), targetBands,
+                new GridCoverage[] { gridCoverage }, null);
+        return createed;
+    }
+
+    /**
+     * HELPER FUNCTIONS
+     */
+    public float getOpacity() {
+        float alpha = 1.0f;
+        Expression exp = this.symbolizer.getOpacity();
+        if (exp == null){
+            return alpha;
+        }
+        Number number = (Number) exp.evaluate(null,Float.class);
+        if (number == null){
+            return alpha;
+        }
+        return number.floatValue();
+    }
+
+    public Map getCategories(final int band) {
+        final String[] labels = getLabels(band);
+        final Color[] colors = getColors(band);
+
+        final Map categories = new HashMap();
+
+        /**
+         * Checking Categories
+         */
+        final int labelsLength = labels.length;
+        Color[] oldCmap;
+        int length;
+        Color[] newCmap;
+        for (int i = 0; i < labelsLength; i++) {
+            if (!categories.containsKey(labels[i])) {
+                categories.put(labels[i], new Color[] { colors[i] });
+            } else {
+                oldCmap = (Color[]) categories.get(labels[i]);
+                length = oldCmap.length;
+                newCmap = new Color[length + 1];
+                System.arraycopy(oldCmap, 0, newCmap, 0, length);
+                newCmap[length] = colors[i];
+                categories.put(labels[i], newCmap);
+            }
+        }
+
+        return categories;
+    }
+
+    public String[] getLabels(final int band) {
+        String[] labels = null;
+        if (this.symbolizer.getColorMap() != null) {
+            final ColorMapEntry[] colors = this.symbolizer.getColorMap()
+                    .getColorMapEntries();
+            final int numColors = colors.length;
+            labels = new String[numColors];
+            for (int ci = 0; ci < numColors; ci++) {
+                labels[ci] = colors[ci].getLabel();
+            }
+        }
+
+        return labels;
+    }
+
+    public Color[] getColors(final int band) {
+        Color[] colorTable = null;
+        if (this.symbolizer.getColorMap() != null) {
+            final ColorMapEntry[] colors = this.symbolizer.getColorMap()
+                    .getColorMapEntries();
+            final int numColors = colors.length;
+            colorTable = new Color[numColors];
+            double opacity;
+            for (int ci = 0; ci < numColors; ci++) {
+                opacity = toOpacity( colors[ci].getOpacity() );
+                colorTable[ci] = toColor( colors[ci].getColor(), opacity );
+                if (colorTable[ci] == null){
+                    return null;
+                }
+            }
+        }
+
+        return colorTable;
+    }
+    public static Color toColor( Expression exp, double opacity ){
+        if (exp == null){
+            return null;
+        }
+        Color color = (Color) exp.evaluate(null, Color.class);
+        int alpha = new Double(Math.ceil(255.0 * opacity)).intValue();        
+        if( color != null ){                       
+            return new Color( color.getRed(), color.getGreen(), color.getBlue(), alpha );            
+        }
+        else {
+            // the value morphing code failed us .. let's try by hand
+            Object obj = exp.evaluate( null );
+            if (obj == null){
+                return null;
+            }
+            Integer  intval = Integer.decode((String) obj);
+            int i = intval.intValue();            
+            return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF, alpha);
+        }
+    }
+    public static double toOpacity( Expression opacity ) {
+        if (opacity == null) {
+            return 1.0;
+        }
+         
+        Double value = (Double) opacity.evaluate(null, Double.class);
+        if (value == null) {
+            return 1.0;
+        }
+        return value.doubleValue();
+//
+//        opacity = (colors[ci].getOpacity() != null
+//                ? (colors[ci].getOpacity().getValue(null) instanceof String
+//                        ? Double.valueOf((String) colors[ci].getOpacity().getValue(null))
+//                        : (Double) colors[ci].getOpacity().getValue(null))
+//                : new Double(1.0));
+    }
+//ms-01.sn
+    /**
+     * Transforms an {@link Expression} to a double value.
+     */
+    public static double toQuantiy( Expression quantity ) {
+      if (quantity == null)
+          return 0.0;
+      Double value = (Double) quantity.evaluate(null, Double.class);
+      if (value == null)
+          return 0.0;
+      return value.doubleValue();    
+    }
+
+    /**
+     * Extends an array with new values.
+     * @param source source array 
+     * @param newElem new elements the array is extended with
+     * @return a new array object
+     */
+    private static <T> T[] extendArray(T[] source, T... newElem) {
+      return LangUtil.extendArray(source, newElem);
+//      T[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+//      for (int i=0; i<newElem.length; i++)
+//        newArray[source.length+i] = newElem[i];
+//      return newArray;
+    }
+    
+    public GridSampleDimension transformColormap(GridSampleDimension dimension, RasterSymbolizer symbolizer) {
+      if ( symbolizer == null || symbolizer.getColorMap() == null )
+        return dimension;
+
+      ColorMap             colorMap       = symbolizer.getColorMap();
+      ColorMapEntry[]      colorMapEntry  = colorMap.getColorMapEntries();
+      Map<String,Category> categories     = new HashMap<String,Category>();
+      int                  sampleQuantMin = Integer.MAX_VALUE;
+      int                  sampleQuantMax = Integer.MIN_VALUE;
+      double               geoQuantMin    = Integer.MAX_VALUE;
+      double               geoQuantMax    = Integer.MIN_VALUE;
+
+      //////////////////////////////////////////////////////////
+      // Workaround, so that "sampleQuan" is unique, even if
+      // the grid values are near to each other (e.g. 0.8 and
+      // 1.2)
+      int sampleQuanFactor = 10;
+      for (int i=1; i<colorMapEntry.length; i++) {
+        double geoQuant0 = toQuantiy(colorMapEntry[i-1].getQuantity());
+        geoQuant0        = Math.abs(geoQuant0);
+        double geoQuant1 = toQuantiy(colorMapEntry[i].getQuantity());
+        geoQuant1        = Math.abs(geoQuant1);
+        int sampleQuan0  = (int)Math.round(geoQuant0*sampleQuanFactor) + 1;
+        int sampleQuan1  = (int)Math.round(geoQuant1*sampleQuanFactor) + 1;
+        if ( sampleQuan0 == sampleQuan1 )
+          sampleQuanFactor *= 10;
+      }
+      //////////////////////////////////////////////////////////
+      
+      for ( ColorMapEntry cme : colorMap.getColorMapEntries() ) {
+        double opacity     = toOpacity(cme.getOpacity());
+        Color  color       = toColor(cme.getColor(), opacity);
+        String label       = cme.getLabel();
+        if ( label == null )
+          label = "";
+        // the color map defines the geophysics value
+        double geoQuant    = toQuantiy(cme.getQuantity());
+        geoQuantMin        = Math.min(geoQuantMin, geoQuant);
+        geoQuantMax        = Math.max(geoQuantMax, geoQuant);
+        // create a sample quantity which is different from the geophysics value,
+        // so a GeophysicsCategory is created
+        //int    sampleQuan  = (int)Math.round(geoQuant)*10 + 1;  
+        int    sampleQuan  = (int)Math.round(geoQuant*sampleQuanFactor) + 1;
+        sampleQuantMin     = Math.min(sampleQuantMin, sampleQuan);
+        sampleQuantMax     = Math.max(sampleQuantMax, sampleQuan);
+        
+        // if a category for the label already exists, treat this
+        // as a range definition; otherwise create a single value
+        // Category
+        Category oldCategory = categories.get(label);
+        if ( oldCategory != null ) {
+          int     newMax    = Math.max(sampleQuan, (int)oldCategory.getRange().getMaximum());
+          int     newMin    = Math.min(sampleQuan, (int)oldCategory.getRange().getMinimum());
+          double  newMaxGeo = Math.max(geoQuant, oldCategory.geophysics(true).getRange().getMaximum());
+          double  newMinGeo = Math.min(geoQuant, oldCategory.geophysics(true).getRange().getMinimum());
+          Color[] newColors = extendArray(oldCategory.getColors(), color);
+          categories.put(label, new Category(
+              label,
+              newColors,
+              new NumberRange(newMin,    newMax),
+              new NumberRange(newMinGeo, newMaxGeo)
+          ));
+        } else
+          categories.put(label, new Category(
+              label,
+              new Color[] {color},
+              new NumberRange(sampleQuan, sampleQuan),
+              new NumberRange(geoQuant,   geoQuant)
+          ));
+        
+      }
+      
+      // if no categories are defined by color map do not resample; instead use
+      // the existing categories (and the grid/image internal color definition) 
+      if ( categories.isEmpty() )
+        return dimension;
+
+      // If NoData-Values are set, create a additional Categories
+      double[] noDataValues = dimension.getNoDataValues();
+      if ( noDataValues == null )
+        noDataValues = new double[0];
+      for (double noDataValue : noDataValues ) {
+        int value   = sampleQuantMax + 10; // value not already used
+        sampleQuantMin = Math.min(sampleQuantMin, (int)noDataValue);
+        sampleQuantMax = Math.max(sampleQuantMax, (int)noDataValue);
+        categories.put(String.valueOf("NoData_"+noDataValue), createNoDataCategory(value, noDataValue));
+      }
+      
+      // Declare "all" values smaller/greater the color map values
+      // automatically as NoData
+      final int lowerStart = Integer.MIN_VALUE;
+      final int higherEnd  = Integer.MAX_VALUE;
+      categories.put("_autoNoData1_", new Category(
+          "_autoNoData1_",
+          new Color[] { new Color(0,0,0,0) },
+          new NumberRange(sampleQuantMax+1, sampleQuantMax+2),
+          new NumberRange(geoQuantMax+1,    higherEnd)
+      ));
+      if ( sampleQuantMin <= sampleQuantMax )
+        categories.put("_autoNoData2_", new Category(
+            "_autoNoData2_",
+            new Color[] { new Color(0,0,0,0) },
+            //new NumberRange(colorMapMin-2, colorMapMin-1), // logischer, aber klappt nicht!?
+            new NumberRange(sampleQuantMax+3, sampleQuantMax+4),
+            new NumberRange(lowerStart,       geoQuantMin-1)
+        ));
+      //categories.put("_autoNoData2_", Category.NODATA);
+      
+      // Create the GridSampleDimension
+      GridSampleDimension gsd = new GridSampleDimension(
+          dimension.getDescription().toString(),
+          categories.values().toArray(new Category[0]),
+          dimension.getUnits()
+      ).geophysics(true);
+
+//      LOGGER.debug("===== Debug new dimension =====");
+//      for ( Category c : (List<Category>)gsd.getCategories() )
+//        LOGGER.debug( c.toString() );
+
+      return gsd;
+    }
+
+    /**
+     * Creates a {@link Category} for a NoData-value, which will be displayed transparently.
+     * @param value sample value
+     * @param geoValue geophysics value representing the NoData
+     */
+    public static Category createNoDataCategory(int value, double geoValue) {
+      return StylingUtil.createNoDataCategory(value, geoValue);
+//      return new Category(
+//          Vocabulary.formatInternational(VocabularyKeys.NODATA),
+//          new Color[] { new Color(0,0,0,0) },
+//          new NumberRange(value, value),
+//          new NumberRange(geoValue, geoValue)
+//      );
+    }
+//ms-01.en
+    
+    /**
+     * Transform the supplied RGB colors.
+     */
+    public SampleDimension transformColormap(final int band,
+            SampleDimension dimension, final Map[] colorMaps) {
+        if (colorMaps == null || colorMaps.length == 0) {
+            return dimension;
+        }
+        boolean changed = false;
+        final Map colorMap = colorMaps[Math.min(band, colorMaps.length - 1)];
+        final List categoryList = ((GridSampleDimension) dimension)
+                .getCategories();
+        if (categoryList == null) {
+            return dimension;
+        }
+        final Category categories[] = (Category[]) categoryList.toArray();
+        Category category;
+        Color[] colors;
+        final int length = categories.length;
+        Range range;
+        int lower;
+        int upper;
+        for (int j = length; --j >= 0;) {
+            category = categories[j];
+            colors = (Color[]) colorMap.get(category.getName().toString());
+            if (colors == null) {
+                if (!category.isQuantitative()) {
+                    continue;
+                }
+                colors = (Color[]) colorMap.get(null);
+                if (colors == null) {
+                    continue;
+                }
+            }
+            range = category.getRange();
+            lower = ((Number) range.getMinValue()).intValue();
+            upper = ((Number) range.getMaxValue()).intValue();
+            if (!range.isMinIncluded())
+                lower++;
+            if (range.isMaxIncluded())
+                upper++;
+            category = category.recolor(colors);
+            if (!categories[j].equals(category)) {
+                categories[j] = category;
+                changed = true;
+            }
+        }
+        return changed ? new GridSampleDimension(dimension.getDescription(),
+                categories, dimension.getUnits()) : dimension;
+    }
+}

Added: trunk/src/overview_schmitzm.html
===================================================================
--- trunk/src/overview_schmitzm.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/overview_schmitzm.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+ xmlns:w="urn:schemas-microsoft-com:office:word"
+ xmlns="http://www.w3.org/TR/REC-html40">
+<head>
+  <meta http-equiv="Content-Type"
+ content="text/html; charset=windows-1252">
+  <meta name="ProgId" content="Word.Document">
+  <meta name="Generator" content="Microsoft Word 11">
+  <meta name="Originator" content="Microsoft Word 11">
+  <link rel="File-List" href="overview_schmitzm-Dateien/filelist.xml">
+<!--[if gte mso 9]><xml>
+ <o:DocumentProperties>
+  <o:Author>Martin</o:Author>
+  <o:LastAuthor>Martin</o:LastAuthor>
+  <o:Revision>2</o:Revision>
+  <o:Created>2007-05-12T23:24:00Z</o:Created>
+  <o:LastSaved>2007-05-12T23:24:00Z</o:LastSaved>
+  <o:Pages>1</o:Pages>
+  <o:Words>135</o:Words>
+  <o:Characters>852</o:Characters>
+  <o:Lines>7</o:Lines>
+  <o:Paragraphs>1</o:Paragraphs>
+  <o:CharactersWithSpaces>986</o:CharactersWithSpaces>
+  <o:Version>11.6360</o:Version>
+ </o:DocumentProperties>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:WordDocument>
+  <w:GrammarState>Clean</w:GrammarState>
+  <w:HyphenationZone>21</w:HyphenationZone>
+  <w:ValidateAgainstSchemas/>
+  <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>
+  <w:IgnoreMixedContent>false</w:IgnoreMixedContent>
+  <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
+  <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel>
+ </w:WordDocument>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:LatentStyles DefLockedState="false" LatentStyleCount="156">
+ </w:LatentStyles>
+</xml><![endif]-->
+  <style>
+<!--
+ /* Font Definitions */
+ @font-face
+	{font-family:Wingdings;
+	panose-1:5 0 0 0 0 0 0 0 0 0;
+	mso-font-charset:2;
+	mso-generic-font-family:auto;
+	mso-font-pitch:variable;
+	mso-font-signature:0 268435456 0 0 -2147483648 0;}
+ /* Style Definitions */
+ p.MsoNormal, li.MsoNormal, div.MsoNormal
+	{mso-style-parent:"";
+	margin:0cm;
+	margin-bottom:.0001pt;
+	mso-pagination:widow-orphan;
+	font-size:12.0pt;
+	font-family:"Times New Roman";
+	mso-fareast-font-family:"Times New Roman";}
+h1
+	{mso-margin-top-alt:auto;
+	margin-right:0cm;
+	mso-margin-bottom-alt:auto;
+	margin-left:0cm;
+	mso-pagination:widow-orphan;
+	mso-outline-level:1;
+	font-size:24.0pt;
+	font-family:"Times New Roman";}
+ at page Section1
+	{size:595.3pt 841.9pt;
+	margin:70.85pt 70.85pt 2.0cm 70.85pt;
+	mso-header-margin:35.4pt;
+	mso-footer-margin:35.4pt;
+	mso-paper-source:0;}
+div.Section1
+	{page:Section1;}
+ /* List Definitions */
+ @list l0
+	{mso-list-id:1092622279;
+	mso-list-template-ids:-556385494;}
+ at list l0:level1
+	{mso-level-number-format:bullet;
+	mso-level-text:\F0B7;
+	mso-level-tab-stop:36.0pt;
+	mso-level-number-position:left;
+	text-indent:-18.0pt;
+	mso-ansi-font-size:10.0pt;
+	font-family:Symbol;}
+ at list l0:level2
+	{mso-level-number-format:bullet;
+	mso-level-text:o;
+	mso-level-tab-stop:72.0pt;
+	mso-level-number-position:left;
+	text-indent:-18.0pt;
+	mso-ansi-font-size:10.0pt;
+	font-family:"Courier New";
+	mso-bidi-font-family:"Times New Roman";}
+ol
+	{margin-bottom:0cm;}
+ul
+	{margin-bottom:0cm;}
+-->
+  </style><!--[if gte mso 10]>
+<style>
+ /* Style Definitions */
+ table.MsoNormalTable
+	{mso-style-name:"Normale Tabelle";
+	mso-tstyle-rowband-size:0;
+	mso-tstyle-colband-size:0;
+	mso-style-noshow:yes;
+	mso-style-parent:"";
+	mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
+	mso-para-margin:0cm;
+	mso-para-margin-bottom:.0001pt;
+	mso-pagination:widow-orphan;
+	font-size:10.0pt;
+	font-family:"Times New Roman";
+	mso-ansi-language:#0400;
+	mso-fareast-language:#0400;
+	mso-bidi-language:#0400;}
+</style>
+<![endif]--><!--[if gte mso 9]><xml>
+ <o:shapedefaults v:ext="edit" spidmax="1026"/>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <o:shapelayout v:ext="edit">
+  <o:idmap v:ext="edit" data="1"/>
+ </o:shapelayout></xml><![endif]-->
+</head>
+<body style="" lang="DE" link="blue" vlink="blue">
+<div class="Section1">
+<i></i>
+<p class="MsoNormal">Die Klassenbibliothek wurde von Martin Schmitz
+erstellt (<a href="mailto:schmitzm at bonn.edu">schmitzm at bonn.edu</a>) und
+basiert auf dem Java 2 JDK von <a href="http://java.sun.com">Sun</a>
+(JDK 1.5 oder höher). Entsprechend stellen
+die Pakete unterhalb von {@code schmitz} Erweiterungen der jeweiligen
+Java 2
+Packages dar.<br>
+Dabei bauen Teile der
+schmitzm-Klassenbibliothek
+auf folgenden zusätzliche Bibliotheken auf:</p>
+<ul type="disc">
+  <li class="MsoNormal" style=""><span
+ style="font-family: &quot;Courier New&quot;;">schmitzm.geotools.*</span>, <span
+ style="font-family: &quot;Courier New&quot;;">org.geotools.*<o:p></o:p></span></li>
+  <ul type="circle">
+    <li class="MsoNormal" style=""><i>Geotools 2</i> - <a
+ target="_blank" href="http://www.geotools.org">http://www.geotools.org</a></li>
+    <li class="MsoNormal" style=""><i>Geotools 2 <a
+ href="http://www.mojays.de/stuff/ms/schmitzm/lib/gt2-arcgrid-2.3.0-M0.jar">ArcGridRaster-Extention<br>
+      </a><br>
+      </i></li>
+  </ul>
+  <li class="MsoNormal" style=""><span
+ style="font-family: &quot;Courier New&quot;;">schmitzm.swing.log4j.*</span></li>
+  <ul type="circle">
+    <li class="MsoNormal" style=""><i>Apache Log4j</i> - <a
+ target="_blank" href="http://logging.apache.org/log4j">http://logging.apache.org/log4j<br>
+      <br>
+      </a></li>
+  </ul>
+  <li class="MsoNormal" style=""><span
+ style="font-family: &quot;Courier New&quot;;">schmitzm.lang.LangUtil</span></li>
+  <ul type="circle">
+    <li class="MsoNormal" style="margin-bottom: 12pt;"><i>JINI </i>- <a
+ target="_blank" href="http://www.jini.org">http://www.jini.org</a> (es
+reicht: <a
+ href="http://www.mojays.de/stuff/ms/schmitzm/lib/jini-ext.jar">jini-ext.jar</a>)</li>
+  </ul>
+  <li class="MsoNormal" style="">Ein paar Klassen in <span
+ style="font-family: &quot;Courier New&quot;;">schmitzm.swing.*</span>, <span
+ style="font-family: monospace;">schmitzm</span><span
+ style="font-family: &quot;Courier New&quot;;">.geotools.swing.*</span></li>
+  <ul type="circle">
+    <li class="MsoNormal" style=""><i>Projektgruppe <a
+ href="http://www.mojays.de/stuff/ms/schmitzm/lib/AdagiosJavaLib.jar">ADAGIOS</a>
+der Universität Bonn (Institut für Informatik III)<a
+ href="http://www.mojays.de/stuff/ms/schmitzm/lib/AdagiosJavaLib.jar"><br>
+      </a></i></li>
+  </ul>
+</ul>
+</div>
+</body>
+</html>

Added: trunk/src/schmitzm/data/AbstractReadableGrid.java
===================================================================
--- trunk/src/schmitzm/data/AbstractReadableGrid.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/AbstractReadableGrid.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,406 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import appl.data.LoadingException;
+
+/**
+ * Diese Klasse bildet eine Basis-Implementierung von {@link ReadableGrid}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractReadableGrid implements ReadableGrid {
+  /** Enthaelt das CRS des Rasters */
+  protected CoordinateReferenceSystem crs = null;
+
+  /**
+   * Erzeugt ein AbstractReadableGrid.
+   * @param crs CoordinateReferenceSystem
+   */
+  public AbstractReadableGrid(CoordinateReferenceSystem crs) {
+    this.crs = (crs == null) ? DefaultGeographicCRS.WGS84 : crs;
+  }
+
+  /**
+   * Creates an abstract ReadableGrid with a default CRS (WGS84)
+   */
+  public AbstractReadableGrid() {
+    this(null);
+  }
+
+	/**
+	 * This class does not support late loading itself!
+	 *
+	 * @see appl.data.LateLoadable#unloadData()
+	 * @return false;
+	 * @see appl.data.LateLoadable#isLateLoadable()
+	 */
+	public boolean isLateLoadable() {
+		return false;
+	}
+
+	/**
+	 * Does nothing!
+	 *
+	 * This class  does not support late loading!
+	 *
+	 * @see appl.data.LateLoadable#unloadData()
+	 * @see appl.data.LateLoadable#loadData()
+	 */
+	public void loadData() throws LoadingException {
+	}
+
+	/**
+	 * Does nothing!
+	 *
+	 * This class does not support late loading!
+	 *
+	 * @see appl.data.LateLoadable#unloadData()
+	 */
+	public void unloadData(){
+	}
+
+  /**
+   * Liefert das CRS des Rasters.
+   */
+  public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+    return this.crs;
+  }
+
+
+   /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealWidth() / getWidth()</code>
+   */
+  public double getCellWidth() {
+    return getRealWidth() / getWidth();
+  }
+
+  /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealHeight() / getHeight()</code>
+   */
+  public double getCellHeight() {
+    return getRealHeight() / getHeight();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public short getRasterSampleAsShort(int... cell) {
+    return ((Number)getRasterSample(cell)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public byte getRasterSampleAsByte(int... cell) {
+    return ((Number)getRasterSample(cell)).byteValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public int getRasterSampleAsInt(int... cell) {
+    return ((Number)getRasterSample(cell)).intValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public long getRasterSampleAsLong(int... cell) {
+    return ((Number)getRasterSample(cell)).longValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public float getRasterSampleAsFloat(int... cell) {
+    return ((Number)getRasterSample(cell)).floatValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public double getRasterSampleAsDouble(int... cell) {
+    return ((Number)getRasterSample(cell)).doubleValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * Liegt der Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen,
+   * wird die naechst groessere Zelle gewaehlt (ausser die Grenze entspricht
+   * dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public Object getGridSample(double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    return getRasterSample(cellX,cellY);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public short getGridSampleAsShort(double... coord) {
+    return ((Number)getGridSample(coord)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public byte getGridSampleAsByte(double... coord){
+    return ((Number)getGridSample(coord)).byteValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public int getGridSampleAsInt(double... coord){
+    return ((Number)getGridSample(coord)).intValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public long getGridSampleAsLong(double... coord){
+    return ((Number)getGridSample(coord)).longValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public float getGridSampleAsFloat(double... coord){
+    return ((Number)getGridSample(coord)).floatValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public double getGridSampleAsDouble(double... coord){
+    return ((Number)getGridSample(coord)).doubleValue();
+  }
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen
+   * @param coord Georeferenz-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public int convertRealToRaster(final double coord, final int dim) {
+    return convertRealToRaster(this,coord,dim);
+  }
+
+  /**
+   * Konvertiert eine Zellennummer in reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.
+   * @param cell  Rasterzellen-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public double convertRasterToReal(final int cell, final int dim) {
+    return convertRasterToReal(this,cell,dim);
+  }
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen.
+   * @param grid  Raster fuer das die Umrechnung vorgenommen wird
+   * @param coord Georeferenz-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public static int convertRealToRaster(ReadableGrid grid, double coord, int dim) {
+    if ( dim < 0 || dim >= RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" is the maximum dimension!");
+
+    int    rasterMin    = (dim==0) ? grid.getMinX()      : grid.getMinY();
+    double realMin      = (dim==0) ? grid.getX()         : grid.getY();
+    int    rasterLength = (dim==0) ? grid.getWidth()     : grid.getHeight();
+    double realLength   = (dim==0) ? grid.getRealWidth() : grid.getRealHeight();
+    double cellSize     = (dim==0) ? grid.getCellWidth() : grid.getCellHeight();
+
+    int cell = convertRealToRaster(coord, realMin, realLength, rasterMin, rasterLength);
+    // In Y-Richtung liegt der Raster-Ursprung eines Grids OBEN, waehrend die
+    // Latitude-Referenz die UNTERE Grid-Kante beschreibt
+    // -> Umrechnen
+    if ( dim == 1 )
+      cell = rasterMin+rasterLength - cell - 1;
+    return cell;
+  }
+
+  /**
+   * Konvertiert eine Zellennummer in eine reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.
+   * @param grid  Raster fuer das die Umrechnung vorgenommen wird
+   * @param cell  Rasterzellen-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public static double convertRasterToReal(ReadableGrid grid, int cell, int dim) {
+    if ( dim < 0 || dim >= RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" is the maximum dimension!");
+
+    int    rasterMin    = (dim==0) ? grid.getMinX()      : grid.getMinY();
+    double realMin      = (dim==0) ? grid.getX()         : grid.getY();
+    int    rasterLength = (dim==0) ? grid.getWidth()     : grid.getHeight();
+    double realLength   = (dim==0) ? grid.getRealWidth() : grid.getRealHeight();
+    double cellSize     = (dim==0) ? grid.getCellWidth() : grid.getCellHeight();
+
+    // In Y-Richtung liegt der Raster-Ursprung eines Grids OBEN, waehrend die
+    // Longitude-Referenz die UNTERE Grid-Kante beschreibt
+    // -> Y-Koordinate umrechnen in "Sicht aus UNTERER Ecke"
+    if ( dim == 1 )
+      cell = rasterMin+rasterLength - cell - 1;
+
+    return convertRasterToReal(cell,rasterMin,realMin,cellSize);
+  }
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen.<br>
+   * Diese Methode kann fuer alle Dimensionen (X oder Y oder ...) verwendet
+   * werden. Es muessen lediglich die entsprechenden Raster-Informationen fuer
+   * die jeweilige Dimension angegeben werden.<br>
+   * <b>Bemerkung:</b>
+   * Da diese Methode unabhaengig von der Dimension (X,Y-Richtung) arbeitet,
+   * nimmt sie an, dass Raster-Ursprung und lat/lon-Referenz in der selben
+   * Ecke des Grids liegen.
+   * @param coord Georeferenz-Koordinate, die umgerechnet werden soll
+   * @param realMin    Minimale Welt-Koordinate (in der gewuenschten Richtung/Dimension) des
+   *                   Grids in dem sich die Zelle befindet
+   * @param realLength Reale Laenge (in der gewuenschten Richtung/Dimension) des
+   *                   Grids in dem sich die Zelle befindet
+   * @param rasterMin    Minimale Zellen-Nummer (in der gewuenschten Richtung/Dimension) des
+   *                     Grids in dem sich die Zelle befindet
+   * @param rasterLength Laenge in Zellen (in der gewuenschten Richtung/Dimension) des
+   *                     Grids in dem sich die Zelle befindet
+   * @exception UnsupportedOperationException falls als {@code rasterLength} 0
+   *            uebergeben wird
+   */
+  public static int convertRealToRaster(final double coord, final double realMin, final double realLength, final int rasterMin, final int rasterLength) {
+    if ( rasterLength <= 0 )
+      throw new UnsupportedOperationException("Length of raster must be > 0!");
+    final double cellSize = realLength / rasterLength;
+
+    // Wenn Koordinate genau den Rasterrand trifft, wird die letzte
+    // Zelle zurueckgegeben...
+    if ( coord == realMin + realLength )
+      return rasterMin + rasterLength - 1;
+    // ... sonst die naechste Zelle
+    // Bemerkung: Einfaches Abschneiden (int) reicht nicht, es muss die
+    //            naechstkleinere Zahl ermittelt werden, denn sonst gibt
+    //            es die Zelle 0 zweimal, z.B.
+    //              (coord-realMin)/cellSize =  0.8 --> Zelle 0 (korrekt)
+    //              (coord-realMin)/cellSize = -0.8 --> Zelle 0 (falsch)
+    //            Im zweiten Fall muss auf -1 "gerundet" werden!
+    return ((int)Math.floor( (coord-realMin) / cellSize ) ) + rasterMin;
+  }
+
+  /**
+   * Konvertiert eine Zellennummer in eine reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.<br>
+   * Diese Methode kann fuer alle Dimensionen (X oder Y oder ...) verwendet
+   * werden. Es muessen lediglich die entsprechenden Raster-Informationen fuer
+   * die jeweilige Dimension angegeben werden.
+   * <b>Bemerkung:</b>
+   * Da diese Methode unabhaengig von der Dimension (X,Y-Richtung) arbeitet,
+   * nimmt sie an, dass Raster-Ursprung und lat/lon-Referenz in der selben
+   * Ecke des Grids liegen.
+   * @param cell       Rasterzellen-Koordinate, die umgerechnet werden soll
+   * @param rasterMin  Minimale Raster-Koordinate (in der gewuenschten Richtung/Dimension) des
+   *                   Grids in dem sich die Zelle befindet
+   * @param realMin    Minimale Welt-Koordinate (in der gewuenschten Richtung/Dimension) des
+   *                   Grids in dem sich die Zelle befindet
+   * @param cellSize   Zellengroesse (in der gewuenschten Richtung/Dimension) des
+   *                   Grids in dem sich die Zelle befindet
+   */
+  public static double convertRasterToReal(final int cell, final int rasterMin, final double realMin, final double cellSize) {
+    return realMin + (cell-rasterMin)*cellSize + 0.5*cellSize;
+  }
+  /**
+   * Vergleicht zwei Raster auf gleiche Struktur (Hoehe, Breite, Zell-Hoehe,
+   * Zell-Breite, Sample-Type).
+   * @param grid1 Raster 1
+   * @param desc1 Beschreibung von Raster 1 (fuer Fehlermeldung)
+   * @param grid2 Raster 2
+   * @param desc2 Beschreibung von Raster 2 (fuer Fehlermeldung)
+   * @param checkType bestimmt, ob auch der Sample-Type uebereinstimmen muss
+   * @exception UnsupportedOperationException falls die Raster nicht gleich sind
+   */
+  public static void compareStructure(ReadableGrid grid1, String desc1, ReadableGrid grid2, String desc2, boolean checkType) {
+    if ( grid1.getWidth() != grid2.getWidth() )
+      throw new UnsupportedOperationException(createCompareMessage(DataUtil.RESOURCE.getString("RasterDim.GridWidth"),desc1,desc2));
+    if ( grid1.getHeight() != grid2.getHeight() )
+      throw new UnsupportedOperationException(createCompareMessage(DataUtil.RESOURCE.getString("RasterDim.GridHeight"),desc1,desc2));
+    if ( grid1.getCellWidth() != grid2.getCellWidth() )
+      throw new UnsupportedOperationException(createCompareMessage(DataUtil.RESOURCE.getString("RasterDim.CellWidth"),desc1,desc2));
+    if ( grid1.getCellHeight() != grid2.getCellHeight() )
+      throw new UnsupportedOperationException(createCompareMessage(DataUtil.RESOURCE.getString("RasterDim.CellHeight"),desc1,desc2));
+    if ( checkType && grid1.getSampleType() != grid2.getSampleType() )
+      throw new UnsupportedOperationException(createCompareMessage(DataUtil.RESOURCE.getString("RasterDim.SampleType"),desc1,desc2));
+  }
+
+  /**
+   * Erzeugt eine Fehlermeldung fuer eine bestimmte inkompatible Raster-Struktur.
+   */
+  private static String createCompareMessage(String compType, String objDesc1, String objDesc2) {
+    return DataUtil.RESOURCE.getString("RasterDim.ErrorMess",compType,objDesc1,objDesc2);
+  }
+}

Added: trunk/src/schmitzm/data/AbstractWritableGrid.java
===================================================================
--- trunk/src/schmitzm/data/AbstractWritableGrid.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/AbstractWritableGrid.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,56 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import appl.data.LoadingException;
+import schmitzm.data.WritableGrid;
+
+/**
+ * Diese Klasse bildet eine Basis-Implementierung von {@link WritableGrid}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractWritableGrid extends AbstractReadableGrid implements WritableGrid {
+  /**
+   * Erzeugt ein AbstractWritableGrid.
+   * @param crs CoordinateReferenceSystem
+   */
+  public AbstractWritableGrid(CoordinateReferenceSystem crs) {
+    super(crs);
+  }
+
+  /**
+   * Creates an abstract WritableGrid with a default CRS (WGS84)
+   */
+  public AbstractWritableGrid() {
+    this(null);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+   * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public void setGridSample(Object value, double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    setRasterSample(value,cellX,cellY);
+  }
+}

Added: trunk/src/schmitzm/data/DataUtil.java
===================================================================
--- trunk/src/schmitzm/data/DataUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/DataUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,19 @@
+package schmitzm.data;
+
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.ResourceProvider;
+import java.util.Locale;
+
+/**
+ * Diese Klasse enthaelt statische Hilfs-Methoden fuer das Package
+ * {@code schmitzm.data}.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class DataUtil {
+  /** {@link ResourceProvider}, der die Lokalisation fuer Komponenten
+   *  des Package {@code schmitzm.data} zur Verfuegung stellt. Diese sind
+   *  in properties-Datein unter {@code schmitzm.data.resource.locales}
+   *  hinterlegt. */
+  public static ResourceProvider RESOURCE = new ResourceProvider( LangUtil.extendPackagePath(DataUtil.class,"resource.locales.DataResourceBundle"), Locale.ENGLISH );
+}

Added: trunk/src/schmitzm/data/ObjectStructure.java
===================================================================
--- trunk/src/schmitzm/data/ObjectStructure.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/ObjectStructure.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,76 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import java.util.Enumeration;
+// nur fuer Doku
+import schmitzm.data.property.Property;
+import schmitzm.data.property.ScalarProperty;
+import schmitzm.data.property.ListProperty;
+import schmitzm.data.property.Properties;
+import schmitzm.data.property.PropertySet;
+
+/**
+ * Diese Klasse stellt die Struktur (nicht den Inhalt) eines "komplexen" Objekts
+ * (Datentyps) dar. Ein "komplexes" Objekt kann z.B. eine {@linkplain ListProperty Liste}
+ * sein, oder eine {@linkplain Properties Menge von Eigenschaften}.
+ * Auch eine {@linkplain ScalarProperty skalare Eigenschaft} kann
+ * bereits als komplexes Objekt aufgefasst werden, da sie einen Namen und ein
+ * Unterobjekt enthaelt (welches wiederum komplex sein kann!).
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ObjectStructure {
+  /**
+   * Liefert die Anzahl an Attributen, die der Datentyp hat. Bei einem
+   * {@link Properties}-Objekt entspricht dies z.B. der Anzahl an Propertys.
+   * Bei einer {@linkplain ListProperty Liste} von einfachen Elementen ist
+   * die Anzahl 1.
+   */
+  public int getAttrCount();
+
+  /**
+   * Liefert die Typen aller Attribute des Objekttyps. Dies kann wiederum
+   * eine <code>ObjectStructure</code> sein, oder eine <code>Class</code>-Instanz,
+   * falls das Attribut nicht weiter strukturiert werden kann.
+   * @return Aufzaehlung, in der jedes Element vom Typ <code>Class</code> oder
+   *         <code>ObjectStructure</code> ist
+   */
+  public Enumeration getAttrTypes();
+
+  /**
+   * Prueft, ob der Objekttyp einen Namen besitzt. z.B. ist eine {@link Property}
+   * i.d.R. benannt, ein {@link PropertySet} jedoch nicht.
+   */
+  public boolean isStructureNamed();
+
+  /**
+   * Liefert den Namen der <code>ObjectStructure</code>, falls sie benannt ist.
+   * @return <code>null</code> falls die Struktur unbenannt ist
+   * @see #isStructureNamed()
+   */
+  public String getStructureName();
+
+  /**
+   * Prueft, ob die Objektstruktur mehrere Instanzen aufnehmen kann. Dies ist
+   * z.B. fuer {@linkplain ListProperty Listen} der Fall, nicht jedoch fuer
+   * ein {@link PropertySet}, da dieses zwar verschiedene Attributwerte aufnimmt,
+   * fuer jedes Attribut jedoch immer nur eine Auspraegung!
+   */
+  public boolean containsMultipleValues();
+
+  /**
+   * Prueft, ob die Objekt-Struktur mit einer anderen identisch ist.
+   * @param object andere <code>ObjectStructure</code>
+   */
+  public boolean equalsInStructure(Object object);
+}

Added: trunk/src/schmitzm/data/ObjectStructureUtil.java
===================================================================
--- trunk/src/schmitzm/data/ObjectStructureUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/ObjectStructureUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,128 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import java.util.Enumeration;
+
+/**
+ * Diese Klasse stellt statische Methoden fuer die Arbeit mit
+ * {@linkplain ObjectStructure Objekt-Strukturen} zur Verfuegung.
+ * Hierzu gehoeren insbesondere Vergleichsmethoden, mit denen auf strukturelle
+ * Gleichkeit oder Implikation getestet werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ObjectStructureUtil {
+  /** Rueckgabe-Konstante fuer "zwei Strukturen sind identisch". */
+  public static final int EQUAL = 1;
+  /** Rueckgabe-Konstante fuer "die linke Strukturen umfasst die rechte". */
+  public static final int LEFT_CONTAINS_RIGHT = 2;
+  /** Rueckgabe-Konstante fuer "die rechte Strukturen umfasst die linke". */
+  public static final int RIGHT_CONTAINS_LEFT = 3;
+  /** Rueckgabe-Konstante fuer "weder umfasst die linke Strukur die rechte,
+   *  noch umgekehrt" (Ueberschneidung ist dennoch moeglich!). */
+  public static final int UNEQUAL = 4;
+  /** Rueckgabe-Konstante fuer "die beiden Strukituren haben eine leere Schnittmenge".<br>
+   *  <b>Bemerke</b>: wird z.Z. noch nicht verwendet!! */
+  public static final int DISJOINT = 5;
+
+  /**
+   * Vergleicht zwei Objekt-Strukturen.
+   * @param o1 "linke" Objekt-Struktur
+   * @param o2 "rechte" Objekt-Struktur
+   * @return eine der Konstanten {@link #EQUAL}, {@link #LEFT_CONTAINS_RIGHT},
+   *         {@link #RIGHT_CONTAINS_LEFT}, {@link #UNEQUAL}
+   * @see #checkStructureContainsStructure(ObjectStructure,ObjectStructure)
+   */
+  public static int compareObjectStructures(ObjectStructure o1, ObjectStructure o2) {
+    boolean o1_cont_o2 = checkStructureContainsStructure(o1,o2);
+    boolean o2_cont_o1 = checkStructureContainsStructure(o2,o1);
+    if ( o1_cont_o2 && o2_cont_o1 ) return EQUAL;
+    if ( o1_cont_o2  ) return LEFT_CONTAINS_RIGHT;
+    if ( o2_cont_o1  ) return RIGHT_CONTAINS_LEFT;
+    // Teil-Ueberschneidung wird (noch) nicht geprueft
+    return UNEQUAL;
+  }
+
+  /**
+   * Prueft, ob eine Objekt-Struktur eine andere umfasst.<br>
+   * <u>Vorgehensweise</u>:<br>
+   * <ol>
+   * <li>Kann die eine Struktur mehrere Werte aufnehmen und die andere nicht
+   *     ({@link ObjectStructure#containsMultipleValues()}), kann die linke
+   *     Struktur die rechte auf keinen Fall enthalten.</li>
+   * <li>Enhaelt die rechte Struktur mehr Attribute, als die linke, so kann die
+   *     linke Struktur die rechte auf keinen Fall enthalten.</li>
+   * <li>Enthaelt die linke Struktur ein Attribut, welches (wiederum strukturell)
+   *     nicht in der rechten Struktur vorhanden ist, so kann die linke Struktur
+   *     die rechte nicht enthalten.<br>
+   *     Beim Vergleich der Attribute wird wiederum auf strukturelle Implikation
+   *     geprueft:
+   *     <ul>
+   *     <li>Handelt es sich um zwei strukturierte Attribute, muss das "linke"
+   *         Attribut das "rechte" strukturell umfassen (Rekursion!).</li>
+   *     <li>Handelt es sich um zwei statische Attribute (Typ <code>Class</code>),
+   *         muss die "linke" Klasse eine Spezialisierung (Ableitung) der
+   *         "rechten" Klasse sein.</li>
+   *     <li>Treffen beide Bedingungen nicht zu, kann das linke Attribut das
+   *         rechte nicht umfassen.<li>
+   *     </ul></li>
+   * </ol>
+   * @param o1 "linke" Objekt-Struktur
+   * @param o2 "rechte" Objekt-Struktur
+   */
+  public static boolean checkStructureContainsStructure(ObjectStructure o1, ObjectStructure o2) {
+    if ( o1.containsMultipleValues() != o2.containsMultipleValues() ||
+         o2.getAttrCount() > o1.getAttrCount() )
+      return false;
+
+    // Pruefen, ob fuer jedes Attribut in o2 ein Attribut in o1 vorhanden ist,
+    //  welches o2 beinhaltet
+    for (Enumeration attr2 = o2.getAttrTypes(); attr2.hasMoreElements();)
+      if ( !findAttrTypeInStrcucture(attr2.nextElement(),o1) )
+        return false;
+
+    return true;
+  }
+
+  /**
+   * Prueft, ob ein Attribut in einer Objekt-Struktur (strukturell) enthalten ist.
+   * Erlaeuterungen ueber die Vorgehensweise sind in der Dokumenation zu
+   * {@link #checkStructureContainsStructure(ObjectStructure,ObjectStructure)}
+   * zu finden.
+   * @param type1 Attribut (<code>ObjectStructure</code>- oder
+   *              <code>Class</code>-Instanz)
+   * @param o2    Objekt-Struktur
+   */
+  private static boolean findAttrTypeInStrcucture(Object type1, ObjectStructure o2) {
+    for (Enumeration attr2 = o2.getAttrTypes(); attr2.hasMoreElements();) {
+      Object type2 = attr2.hasMoreElements();
+
+      // wenn es sich bei beiden um Strukturen handelt und Typ2 den gesuchten
+      // Typ1 impliziert, wurde Typ1 in der Struktur o2 gefunden
+      if (type1 instanceof ObjectStructure &&
+          type2 instanceof ObjectStructure &&
+          checkStructureContainsStructure((ObjectStructure)type2,(ObjectStructure)type1) )
+        return true;
+
+      // wenn es sich bei beiden um einfache Klassen handelt und Typ2 den gesuchten
+      // Typ1 impliziert, wurde Typ1 in der Struktur o2 gefunden
+      else if (type1 instanceof Class &&
+               type2 instanceof Class &&
+               ((Class)type2).isAssignableFrom((Class)type1) )
+             return true;
+    }
+
+    return false;
+  }
+
+}

Added: trunk/src/schmitzm/data/RasterCalculator.java
===================================================================
--- trunk/src/schmitzm/data/RasterCalculator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterCalculator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,91 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+
+/**
+ * Diese Klasse implementiert Rechen-Operationen auf Rastern.
+ * @see RasterOperationTree
+ * @see RasterOperationTreeParser
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterCalculator {
+  /////////////////////////////////////////////////////////////////////
+  /////////////////////   FORMEL AUSFUEHREN   /////////////////////////
+  /////////////////////////////////////////////////////////////////////
+  /**
+   * Berechnet eine Formel auf jeder Zelle der Eingabe-Raster und speichert das
+   * Ergebnis in der jeweiligen Zelle des Ausgabe-Rasters. Hierfuer wird der
+   * Formel-String durch den {@link RasterOperationTreeParser} in einen
+   * {@link RasterOperationTree} umgewandelt, der auf dann fuer jede Rasterzelle
+   * des Output-Rasters ausgewertet wird.
+   * @param rule       Formel
+   * @param inRaster   Eingabe-Raster
+   * @param inFilter   Eingabe-Filter
+   * @param outRaster  Ausgabe-Raster
+   * @see RasterOperationTreeParser
+   */
+  public static void calculate(String rule, ReadableGrid[] inRaster, RasterFilter[] inFilter, WritableGrid outRaster ) {
+    RasterOperationTree opTree = new RasterOperationTreeParser().parse(rule);
+    if ( opTree == null )
+      return;
+
+    int minX = outRaster.getMinX();
+    int minY = outRaster.getMinY();
+    int maxX = minX + outRaster.getWidth() - 1;
+    int maxY = minY + outRaster.getHeight() - 1;
+
+    for (int y=minY; y<=maxY; y++)
+      for (int x=minX; x<=maxX; x++) {
+        // Berechnung starten mit dem ersten Operand
+        double result = opTree.evaluate( x, y, inRaster, inFilter );
+        outRaster.setRasterSample( (float)result, x, y );
+      }
+  }
+
+  /**
+   * Berechnet eine Formel auf jeder Zelle der Eingabe-Raster und speichert das
+   * Ergebnis in der jeweiligen Zelle des Ausgabe-Rasters. Hierfuer wird der
+   * Formel-String durch den {@link RasterOperationTreeParser} in einen
+   * {@link RasterOperationTree} umgewandelt, der auf dann fuer jede Rasterzelle
+   * des Output-Rasters ausgewertet wird.
+   * @param rule      Formel
+   * @param inRaster  Eingabe-Raster
+   * @param outRaster Ausgabe-Raster
+   * @see RasterOperationTreeParser
+   */
+  public static void calculate(String rule, ReadableGrid[] inRaster, WritableGrid outRaster ) {
+    calculate( rule, inRaster, null, outRaster);
+  }
+  /**
+   * Prueft die Syntax einer Raster-Formel auf Korrektheit.
+   * @param rule Raster-Formel.
+   */
+  public static boolean checkRule(String rule) {
+    try {
+      checkRuleAndError(rule);
+    } catch( Exception err ) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Prueft die Syntax einer Raster-Formel auf Korrektheit.
+   * @param rule Raster-Formel.
+   * @exception IllegalArgumentException bei einem Fehler
+   */
+  public static void checkRuleAndError(String rule) {
+    new RasterOperationTreeParser().parse(rule);
+  }
+}

Added: trunk/src/schmitzm/data/RasterFilter.java
===================================================================
--- trunk/src/schmitzm/data/RasterFilter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterFilter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,180 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+/**
+ * Diese Klasse stellt eine (2-dimensionale) Filter-Matrix dar. Diese muss
+ * in beiden Dimensionen eine ungerade Laenge haben, damit die Filter-Matrix
+ * ein eindeutiges Zentrum besitzt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterFilter {
+  /** Speichert die Filter-Matrix. */
+  protected double[][] filter = null;
+  /** Speichert die horizontale Laenge der Filter-Matrix. */
+  protected int sizeX;
+  /** Speichert die vertikale Laenge der Filter-Matrix. */
+  protected int sizeY;
+  /** Speichert die Summe ueber alle Eintraege der Filter-Matrix. */
+  protected double weight;
+
+  /**
+   * Erzeugt einen neuen Raster-Filter. Die Filter-Matrix muss ein eindeutiges
+   * Zentrum haben, d.h. die Array-Dimensionen muessen ungerade Laenge haben!
+   * @param filter Filter-Matrix
+   */
+  public RasterFilter(double[][] filter) {
+    if ( filter == null )
+      throw new UnsupportedOperationException("null-Filter not allowed!");
+    if ( filter.length % 2 != 1 || filter[0].length % 2 != 1 )
+      throw new UnsupportedOperationException("Filter size must be odd in each dimension!");
+    for (double[] filterCol : filter)
+      if ( filterCol.length != filter[0].length )
+        throw new UnsupportedOperationException("Filter must have the same size in each column!");
+
+    this.filter = filter;
+    this.sizeX  = filter.length;
+    this.sizeY  = filter[0].length;
+    this.weight = calcFilterWeight();
+  }
+
+  /**
+   * Erzeugt einen leeren Raster-Filter. {@code sizeX} und {@code sizeY}
+   * muessen ungerade sein, damit die Filter-Matrix ein eindeutiges Zentrum hat.
+   * @param sizeX horizontale Laenge der Filter-Matrix
+   * @param sizeY vertikale Laenge der Filter-Matrix
+   */
+  public RasterFilter(int sizeX, int sizeY) {
+    this( new double[sizeX][sizeY] );
+  }
+
+  /**
+   * Erzeugt einen leeren quadratischen Raster-Filter. {@code size} muss
+   * ungerade sein, damit die Filter-Matrix ein eindeutiges Zentrum hat.
+   * @param size horizontale/vertikale Laenge der Filter-Matrix
+   */
+  public RasterFilter(int size) {
+    this( size, size );
+  }
+
+  /**
+   * Liefert die horizontale Laenge der Filter-Matrix.
+   */
+  public int getFilterSizeX() {
+    return sizeX;
+  }
+
+  /**
+   * Liefert die vertikale Laenge der Filter-Matrix.
+   */
+  public int getFilterSizeY() {
+    return sizeY;
+  }
+
+  /**
+   * Liefert die Summe ueber alle Eintraege der Filter-Matrix.
+   */
+  public double getFilterWeight() {
+    return weight;
+  }
+
+  /**
+   * Berechnet die Summe ueber alle Eintraege der Filter-Matrix.
+   */
+  public double calcFilterWeight() {
+    double sum = 0;
+    for (double[] filterCol : filter)
+      for (double v : filterCol)
+        sum += v;
+    return sum;
+  }
+
+  /**
+   * Liefert einen Wert der Filter-Matrix.
+   * @param x X-Koorindinate
+   * @param y Y-Koordinate
+   */
+  public double getFilterValue(int x, int y) {
+    return filter[x][y];
+  }
+
+  /**
+   * Setzt einen Wert der Filter-Matrix und berechnet das
+   * {@linkplain #getFilterWeight() Filter-Gewicht} neu.
+   * @param value neuer Wert
+   * @param x X-Koorindinate
+   * @param y Y-Koordinate
+   */
+  public void setFilterValue(double value, int x, int y) {
+    weight -= filter[x][y]; // Gesamtsumme um alten Wert reduzieren
+    filter[x][y] = value;
+    weight += value; // Gesamtsumme um neuen Wert erhoehen
+  }
+
+  /**
+   * Wertet den Filter auf einer Rasterzelle aus, in dem die Summe der mit
+   * den Filterwerten gewichteten Rasterwerte (um die Rasterzelle) gebildet
+   * wird und diese mit der Summe der Filterwerte normiert wird (geteilt wird).
+   * Am Rand des Rasters wird so verfahren, dass die Zellen ausserhalb des
+   * Rasters ignoriert werden, sowohl in der gewichteten Summe, als auch in
+   * der Filter-Summe.
+   * @param raster Raster
+   * @param x      X-Koordinate im Raster (Spaltenindex)
+   * @param y      Y-Koordinate im Raster (Zeilenindex)
+   * @return {@code Double.NaN} falls die Rasterzelle diesen Wert besitzt
+   * @exception IllegalArgumentException falls ein {@code null}-Raster uebergeben wird
+   *            oder die Koordinate ausserhalb des Rasters liegt
+   */
+  public double evaluate(ReadableGrid raster, int x, int y) {
+    if ( raster == null )
+      throw new IllegalArgumentException("Filter can not be evaluated on null-Raster!");
+
+    // Aus Performanzgruenden werden moeglichst alle Berechnungen nur
+    // einmal durchgefuehrt!
+    final int minX = raster.getMinX();
+    final int maxX = raster.getMinX() + raster.getWidth() - 1;
+    final int minY = raster.getMinY();
+    final int maxY = raster.getMinY() + raster.getHeight() - 1;
+    if ( x < minX || x > maxX || y < minY || y > maxY )
+      throw new IllegalArgumentException("Cell ("+x+"/"+y+") out of raster!");
+    if ( Double.isNaN( raster.getRasterSampleAsDouble(x,y) ) )
+      return Double.NaN;
+
+    // Erste relevate Raster-Koordinate
+    final int startX = x - sizeX/2;
+    final int startY = y - sizeY/2;
+
+    // Filter ueber Raster legen und Summen berechnen
+    double filterSum   = 0; // Summe der tatsaechlich verwendeten Filterwerte (RAND!!)
+    double cellSum     = 0; // Mit Filter gewichtete Summe der Rasterzellen
+    int    cellX       = 0; // betrachtete X-Koordinate im Raster
+    int    cellY       = 0; // betrachtete Y-Koordinate im Raster
+    double cellValue   = 0; // Wert der betrachteten Zelle
+    double filterValue = 0; // Wert der betrachteten Filter-Zelle
+    for (int fy = 0; fy < sizeY; fy++) {
+      cellY = startY + fy; // Raster-Koordinate
+      if ( cellY >= minY && cellY <= maxY )
+        for (int fx = 0; fx < sizeX; fx++) {
+          cellX       = startX + fx; // Raster-Koordinate
+          cellValue   = raster.getRasterSampleAsDouble(cellX,cellY);
+          filterValue = filter[fx][fy];
+          if ( cellX >= minX && cellX <= maxX && filterValue != 0 && !Double.isNaN(cellValue) ) {
+            filterSum += filterValue;
+            cellSum   += filterValue * cellValue;
+          }
+        }
+    }
+    // "Gefilterter" Zellenwert
+    return (filterSum == 0) ? 0.0 : cellSum / filterSum;
+  }
+}

Added: trunk/src/schmitzm/data/RasterOperationTree.071011.double
===================================================================
--- trunk/src/schmitzm/data/RasterOperationTree.071011.double	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterOperationTree.071011.double	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,267 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.TreeNode;
+
+/**
+ * Diese Klasse stellt einen Operator-Baum dar, in dem neben den von der
+ * Oberklasse definitierten Operationen, Referenzen auf Raster und Filter
+ * enthalten sein koennen.<br>
+ * Der Operator-Baum wird auf einer bestimmten Raster-Koordinate ausgewertet.<br>
+ * <br>
+ * <b>Raster-Referenz:</b> {@link RasterReferenceNode}<br>
+ * Eine Raster-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code #} eingeleitet wird (z.B. {@code #0}). Bei der Auswertung des Operatorbaums
+ * (auf einer bestimmten Raster-Koordinate) wird diese Referenz durch den
+ * entsprechenden Zell-Wert des referenzierten Rasters ersetzt.<br>
+ * <br>
+ * <b>Filter-Referenz:</b> {@link FilterReferenceNode}<br>
+ * Eine Filter-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code F} eingeleitet wird (z.B. {@code F1(.)}) Die Auswertung eines
+ * Filters kann nur direkt auf einer Raster-Referenz erfolgen, da ansonsten
+ * keine zellen-weise Abarbeitung des Rasters mehr moeglich ist, sondern
+ * komplette Raster als Zwischenergebnisse ausgewertet werden muessten.<br>
+ * <b>Koordinaten-Referenz:</b> {@code X} oder {@code Y}<br>
+ * Stellt einen Alias fuer die X- bzw. Y-Koordinate der Rasterzelle dar, die
+ * verarbeitet wird.<br>
+ * <b>NoData:</b> Konstante {@code NoData} und Funktion {@code isNoData(.)}<br>
+ * Stellen einen Alias fuer {@code NaN}, bzw. {@code isNaN(.)} dar.
+ * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterOperationTree extends OperationTree {
+  private int                x           = 0;
+  private int                y           = 0;
+  private WritableGrid[]     inputRaster = null;
+  private RasterFilter[]     inputFilter = null;
+
+  /**
+   * Erzeugt einen neuen Raster-Operatorbaum
+   * @param root Wurzelknoten
+   */
+  public RasterOperationTree(TreeNode root) {
+    super(root);
+  }
+
+  /**
+   * Nicht unterstuetzt!
+   * @deprecated
+   * @exception UnsupportedOperationException bei jedem Aufruf
+   */
+  public double evaluate() {
+    throw new UnsupportedOperationException("RasterOperationTree only supports evaluate(WritableGrid[],int,int)");
+  }
+
+  /**
+   * Wertet den Operatorbaum auf einer bestimmten Raster-Koordinate aus.
+   * @param x X-Koordinate
+   * @param y Y-Koordinate
+   * @param inputRaster Eingabe-Raster auf die die #-Referenzen gemappt werden
+   * @param inputFilter Eingabe-Filter auf die die F-Referenzen gemappt werden
+   */
+  public double evaluate(int x, int y, WritableGrid[] inputRaster, RasterFilter[] inputFilter) {
+    this.x = x;
+    this.y = y;
+    this.inputRaster = inputRaster;
+    this.inputFilter = inputFilter;
+    return super.evaluate();
+  }
+
+  /**
+   * Wertet den Operatorbaum auf einer bestimmten Raster-Koordinate aus.
+   * @param x X-Koordinate
+   * @param y Y-Koordinate
+   * @param inputRaster Eingabe-Raster auf die die Referenzen gemappt werden
+   */
+  public double evaluate(int x, int y, WritableGrid[] inputRaster) {
+    return evaluate(x,y,inputRaster);
+  }
+
+  /**
+   * Wertet einen Knoten des Operator-Baums aus.
+   * @param opTreeNode BinaryTreeNode
+   * @return double
+   */
+  protected double evaluate(TreeNode opTreeNode) {
+    // Referenznummer auf Raster
+    if ( opTreeNode instanceof RasterReferenceNode )
+      return getInputRaster( ((RasterReferenceNode)opTreeNode).getObject() ).getRasterSampleAsDouble(x, y);
+
+    // Referenznummer auf Filter
+    if ( opTreeNode instanceof FilterReferenceNode ) {
+      FilterReferenceNode filterRefereceNode = (FilterReferenceNode)opTreeNode;
+      RasterFilter   filter      = getInputFilter( filterRefereceNode.getFilterNumber() );
+      BinaryTreeNode operandNode = filterRefereceNode.getLeftChild();
+      if ( !(operandNode instanceof RasterOperationTree.RasterReferenceNode) )
+        throw new UnsupportedOperationException("Filter can only be applied on a raster reference");
+      WritableGrid   raster      = getInputRaster( ((RasterReferenceNode)operandNode).getObject() );
+      return filter.evaluate(raster,x, y);
+    }
+    return super.evaluate(opTreeNode);
+  }
+
+  /**
+   * Wertet einen 1-stelligen Operator aus.
+   * Erweitert die Methode der Oberklasse, um die folgenden Operatoren:
+   * <ul>
+   *   <li>{@code isNoData(.)}: Alias fuer die Funktion "isNaN(.)"</li>
+   * </ul>
+   * @param operator 1-stelliger Operator
+   * @param operand  Operand auf den der Operator angewendet wird
+   * @return <i>operator</li>( {@code operand} )
+   */
+  protected double performOperation(String operator, double operand) {
+    operator = operator.toUpperCase();
+    if ( operator.equals("ISNODATA") )
+      return super.performOperation("ISNAN",operand);
+
+    return super.performOperation(operator,operand);
+  }
+
+  /**
+   * Wertet einen 0-stelligen Operator (Alias oder Variable) aus.
+   * Erweitert die Methode der Oberklasse, um die folgenden Operatoren:
+   * <ul>
+   *   <li>{@code X}: Alias fuer die horizontale Raster-Koordinate</li>
+   *   <li>{@code Y}: Alias fuer die vertikale Raster-Koordinate</li>
+   *   <li>{@code NoData}: Alias fuer die Konstante "NaN"</li>
+   * </ul>
+   * @param operator 0-stelliger Operator
+   */
+  protected double performOperation(String operator) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equalsIgnoreCase("X") )
+      return x;
+    if ( operator.equalsIgnoreCase("Y") )
+      return y;
+    if ( operator.equals("NODATA") )
+      return super.performOperation("NAN");
+
+    return super.performOperation(operator);
+  }
+
+  /**
+   * Liefert eines der zur Verfuegung stehenden Eingabe-Raster.
+   * @param rasterNo Raster-Nummer
+   * @exception IllegalArgumentException falls eine ungueltige Raster-Nummer
+   *            uebergeben wird
+   */
+  private WritableGrid getInputRaster(int rasterNo) {
+    if ( rasterNo >= inputRaster.length ) {
+      String allowedStr = ( inputRaster.length > 0 ) ? "allowed: 0 to "+(inputRaster.length-1) : "none allowed";
+      throw new IllegalArgumentException("Invalid input raster reference '#"+rasterNo+"' ("+allowedStr+")");
+    }
+    return inputRaster[rasterNo];
+  }
+
+  /**
+   * Liefert einen der zur Verfuegung stehenden Eingabe-Filter.
+   * @param rasterNo Filter-Nummer
+   * @exception IllegalArgumentException falls eine ungueltige Filter-Nummer
+   *            uebergeben wird
+   */
+  private RasterFilter getInputFilter(int filterNo) {
+    if ( filterNo >= inputFilter.length ) {
+      String allowedStr = ( inputFilter.length > 0 ) ? "allowed: 0 to "+(inputFilter.length-1) : "none allowed";
+      throw new IllegalArgumentException("Invalid input filter reference 'F"+inputFilter+"' ("+allowedStr+")");
+    }
+    return inputFilter[filterNo];
+  }
+
+
+  /**
+   * Diese Knoten repraesentiert eine Referenz auf ein Raster im Operatorbaum.
+   * Da es sich bei einem Zellwert um eine Konstante handelt, hat der Knoten
+   * keine Kind-Knoten. Die Referenz wird durch einen {@code int} dargestellt.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class RasterReferenceNode extends BinaryTreeNode<Integer> {
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param rasterNo Referenz-Nummer
+     * @param parent   Vater-Knoten
+     */
+    public RasterReferenceNode(int rasterNo, BinaryTreeNode parent) {
+      super(rasterNo, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param rasterNo Referenz-Nummer
+     */
+    public RasterReferenceNode(int rasterNo) {
+      this(rasterNo, null);
+    }
+
+    /**
+     * Macht nichts, da {@code RasterReferenceNode} immer einen Blatt-Knoten
+     * darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<Integer> child) {
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert eine Referenz auf einen Filter im Operatorbaum.
+   * Als Operator-Kennzeichen wird "F" verwendet. Die Referenz auf den Filter
+   * wird in einem zusaetzlichen {@code int}-Wert gespeichert.
+   * @see #getFilterNumber()
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class FilterReferenceNode extends OperationTree.UnaryOperatorNode {
+    /** Speichert die Referenznummer auf den Filter. */
+    protected int filterNo = 0;
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param filterNo Referenz-Nummer
+     * @param parent   Vater-Knoten
+     * @param operand  Knoten der den (linker) Operand repraesentiert
+     */
+    public FilterReferenceNode(int filterNo, BinaryTreeNode parent, BinaryTreeNode operand) {
+      super("F", parent, operand);
+      setFilterNumber( filterNo );
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param filterNo Referenz-Nummer
+     * @param operand  Knoten der den (linker) Operand repraesentiert
+     */
+    public FilterReferenceNode(int filterNo, BinaryTreeNode operand) {
+      this(filterNo, null, operand);
+    }
+
+    /**
+     * Setzt die Referenznummer auf einen Filter.
+     * @param filterNo Referenznummer auf einen Filter
+     */
+    public void setFilterNumber(int filterNo) {
+      this.filterNo = filterNo;
+    }
+
+    /**
+     * Liefert die Referenznummer auf einen Filter.
+     */
+    public int getFilterNumber() {
+      return filterNo;
+    }
+  }
+}

Added: trunk/src/schmitzm/data/RasterOperationTree.java
===================================================================
--- trunk/src/schmitzm/data/RasterOperationTree.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterOperationTree.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,274 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.TreeNode;
+
+/**
+ * Diese Klasse stellt einen Operator-Baum dar, in dem neben den von der
+ * Oberklasse definitierten Operationen, Referenzen auf Raster und Filter
+ * enthalten sein koennen.<br>
+ * Der Operator-Baum wird auf einer bestimmten Raster-Koordinate ausgewertet.<br>
+ * <br>
+ * <b>Raster-Referenz:</b> {@link RasterReferenceNode}<br>
+ * Eine Raster-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code #} eingeleitet wird (z.B. {@code #0}). Bei der Auswertung des Operatorbaums
+ * (auf einer bestimmten Raster-Koordinate) wird diese Referenz durch den
+ * entsprechenden Zell-Wert des referenzierten Rasters ersetzt.<br>
+ * <br>
+ * <b>Filter-Referenz:</b> {@link FilterReferenceNode}<br>
+ * Eine Filter-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code F} eingeleitet wird (z.B. {@code F1(.)}) Die Auswertung eines
+ * Filters kann nur direkt auf einer Raster-Referenz erfolgen, da ansonsten
+ * keine zellen-weise Abarbeitung des Rasters mehr moeglich ist, sondern
+ * komplette Raster als Zwischenergebnisse ausgewertet werden muessten.<br>
+ * <b>Koordinaten-Referenz:</b> {@code X} oder {@code Y}<br>
+ * Stellt einen Alias fuer die X- bzw. Y-Koordinate der Rasterzelle dar, die
+ * verarbeitet wird.<br>
+ * <b>NoData:</b> Konstante {@code NoData} und Funktion {@code isNoData(.)}<br>
+ * Stellen einen Alias fuer {@code NaN}, bzw. {@code isNaN(.)} dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterOperationTree extends OperationTree {
+  private int                x           = 0;
+  private int                y           = 0;
+  private ReadableGrid[]     inputRaster = null;
+  private RasterFilter[]     inputFilter = null;
+
+  /**
+   * Erzeugt einen neuen Raster-Operatorbaum
+   * @param root Wurzelknoten
+   */
+  public RasterOperationTree(TreeNode root) {
+    super(root);
+  }
+
+  /**
+   * Nicht unterstuetzt!
+   * @deprecated
+   * @exception UnsupportedOperationException bei jedem Aufruf
+   */
+  public Object evaluate() {
+    throw new UnsupportedOperationException("RasterOperationTree only supports evaluate(ReadableGrid[],int,int)");
+  }
+
+  /**
+   * Wertet den Operatorbaum auf einer bestimmten Raster-Koordinate aus.
+   * @param x X-Koordinate
+   * @param y Y-Koordinate
+   * @param inputRaster Eingabe-Raster auf die die #-Referenzen gemappt werden
+   * @param inputFilter Eingabe-Filter auf die die F-Referenzen gemappt werden
+   */
+  public Double evaluate(int x, int y, ReadableGrid[] inputRaster, RasterFilter[] inputFilter) {
+    this.x = x;
+    this.y = y;
+    this.inputRaster = inputRaster;
+    this.inputFilter = inputFilter;
+    Object result = super.evaluate();
+    if ( result instanceof Boolean )
+      result = (Boolean)result ? 1 : 0;
+    if ( !(result instanceof Number) )
+      throw new UnsupportedOperationException("RasterCalculator can only handle numeric results: "+result);
+    return ((Number)result).doubleValue();
+  }
+
+  /**
+   * Wertet den Operatorbaum auf einer bestimmten Raster-Koordinate aus.
+   * @param x X-Koordinate
+   * @param y Y-Koordinate
+   * @param inputRaster Eingabe-Raster auf die die Referenzen gemappt werden
+   */
+  public Double evaluate(int x, int y, ReadableGrid[] inputRaster) {
+    return evaluate(x,y,inputRaster);
+  }
+
+  /**
+   * Wertet einen Knoten des Operator-Baums aus.
+   * @param opTreeNode BinaryTreeNode
+   * @return double
+   */
+  protected Object evaluate(TreeNode opTreeNode) {
+    // Referenznummer auf Raster
+    if ( opTreeNode instanceof RasterReferenceNode )
+      return getInputRaster( ((RasterReferenceNode)opTreeNode).getObject() ).getRasterSampleAsDouble(x, y);
+
+    // Referenznummer auf Filter
+    if ( opTreeNode instanceof FilterReferenceNode ) {
+      FilterReferenceNode filterRefereceNode = (FilterReferenceNode)opTreeNode;
+      RasterFilter   filter      = getInputFilter( filterRefereceNode.getFilterNumber() );
+      BinaryTreeNode operandNode = filterRefereceNode.getLeftChild();
+      if ( !(operandNode instanceof RasterOperationTree.RasterReferenceNode) )
+        throw new UnsupportedOperationException("Filter can only be applied on a raster reference");
+      ReadableGrid   raster      = getInputRaster( ((RasterReferenceNode)operandNode).getObject() );
+      return filter.evaluate(raster,x, y);
+    }
+    return super.evaluate(opTreeNode);
+  }
+
+  /**
+   * Wertet einen 1-stelligen Operator aus.
+   * Erweitert die Methode der Oberklasse, um die folgenden Operatoren:
+   * <ul>
+   *   <li>{@code isNoData(.)}: Alias fuer die Funktion "isNaN(.)"</li>
+   * </ul>
+   * @param operator 1-stelliger Operator
+   * @param operand  Operand auf den der Operator angewendet wird
+   * @return <i>operator</li>( {@code operand} )
+   */
+  protected Double performOperation(String operator, Object operand) {
+    operator = operator.toUpperCase();
+    if ( operator.equals("ISNODATA") )
+      operator = "ISNAN";
+
+    Object result = super.performOperation(operator,operand);
+    return result instanceof Number ? ((Number)result).doubleValue() : 0.0;
+  }
+
+  /**
+   * Wertet einen 0-stelligen Operator (Alias oder Variable) aus.
+   * Erweitert die Methode der Oberklasse, um die folgenden Operatoren:
+   * <ul>
+   *   <li>{@code X}: Alias fuer die horizontale Raster-Koordinate</li>
+   *   <li>{@code Y}: Alias fuer die vertikale Raster-Koordinate</li>
+   *   <li>{@code NoData}: Alias fuer die Konstante "NaN"</li>
+   * </ul>
+   * @param operator 0-stelliger Operator
+   */
+  protected Double performOperation(String operator) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equalsIgnoreCase("X") )
+      return Double.valueOf(x);
+    if ( operator.equalsIgnoreCase("Y") )
+      return Double.valueOf(y);
+    if ( operator.equals("NODATA") )
+      operator = "NAN";
+
+    Object result = super.performOperation(operator);
+    return result instanceof Number ? ((Number)result).doubleValue() : 0.0;
+  }
+
+  /**
+   * Liefert eines der zur Verfuegung stehenden Eingabe-Raster.
+   * @param rasterNo Raster-Nummer
+   * @exception IllegalArgumentException falls eine ungueltige Raster-Nummer
+   *            uebergeben wird
+   */
+  private ReadableGrid getInputRaster(int rasterNo) {
+    if ( rasterNo >= inputRaster.length ) {
+      String allowedStr = ( inputRaster.length > 0 ) ? "allowed: 0 to "+(inputRaster.length-1) : "none allowed";
+      throw new IllegalArgumentException("Invalid input raster reference '#"+rasterNo+"' ("+allowedStr+")");
+    }
+    return inputRaster[rasterNo];
+  }
+
+  /**
+   * Liefert einen der zur Verfuegung stehenden Eingabe-Filter.
+   * @param rasterNo Filter-Nummer
+   * @exception IllegalArgumentException falls eine ungueltige Filter-Nummer
+   *            uebergeben wird
+   */
+  private RasterFilter getInputFilter(int filterNo) {
+    if ( filterNo >= inputFilter.length ) {
+      String allowedStr = ( inputFilter.length > 0 ) ? "allowed: 0 to "+(inputFilter.length-1) : "none allowed";
+      throw new IllegalArgumentException("Invalid input filter reference 'F"+inputFilter+"' ("+allowedStr+")");
+    }
+    return inputFilter[filterNo];
+  }
+
+
+  /**
+   * Diese Knoten repraesentiert eine Referenz auf ein Raster im Operatorbaum.
+   * Da es sich bei einem Zellwert um eine Konstante handelt, hat der Knoten
+   * keine Kind-Knoten. Die Referenz wird durch einen {@code int} dargestellt.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class RasterReferenceNode extends BinaryTreeNode<Integer> {
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param rasterNo Referenz-Nummer
+     * @param parent   Vater-Knoten
+     */
+    public RasterReferenceNode(int rasterNo, BinaryTreeNode parent) {
+      super(rasterNo, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param rasterNo Referenz-Nummer
+     */
+    public RasterReferenceNode(int rasterNo) {
+      this(rasterNo, null);
+    }
+
+    /**
+     * Macht nichts, da {@code RasterReferenceNode} immer einen Blatt-Knoten
+     * darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<Integer> child) {
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert eine Referenz auf einen Filter im Operatorbaum.
+   * Als Operator-Kennzeichen wird "F" verwendet. Die Referenz auf den Filter
+   * wird in einem zusaetzlichen {@code int}-Wert gespeichert.
+   * @see #getFilterNumber()
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class FilterReferenceNode extends OperationTree.UnaryOperatorNode {
+    /** Speichert die Referenznummer auf den Filter. */
+    protected int filterNo = 0;
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param filterNo Referenz-Nummer
+     * @param parent   Vater-Knoten
+     * @param operand  Knoten der den (linker) Operand repraesentiert
+     */
+    public FilterReferenceNode(int filterNo, BinaryTreeNode parent, BinaryTreeNode operand) {
+      super("F", parent, operand);
+      setFilterNumber( filterNo );
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param filterNo Referenz-Nummer
+     * @param operand  Knoten der den (linker) Operand repraesentiert
+     */
+    public FilterReferenceNode(int filterNo, BinaryTreeNode operand) {
+      this(filterNo, null, operand);
+    }
+
+    /**
+     * Setzt die Referenznummer auf einen Filter.
+     * @param filterNo Referenznummer auf einen Filter
+     */
+    public void setFilterNumber(int filterNo) {
+      this.filterNo = filterNo;
+    }
+
+    /**
+     * Liefert die Referenznummer auf einen Filter.
+     */
+    public int getFilterNumber() {
+      return filterNo;
+    }
+  }
+}

Added: trunk/src/schmitzm/data/RasterOperationTreeParser.071011.double
===================================================================
--- trunk/src/schmitzm/data/RasterOperationTreeParser.071011.double	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterOperationTreeParser.071011.double	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,94 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.OperationTreeParser;
+import schmitzm.data.RasterOperationTree.*;
+
+/**
+ * Diese Klasse stellt einen Parser fuer einen {@linkplain RasterOperationTree Raster-Operatorbaum}
+ * dar. Dieser erstellt einen {@linkplain RasterOperationTree Raster-Operatorbaum} aus einem
+ * Formel-String, der neben den in {@link OperationTreeParser} beschriebenen
+ * Komponenten Raster- und Filter-Referenzen enthalten darf.<br>
+ * <br>
+ * <b>Raster-Referenz:</b> {@link RasterReferenceNode}<br>
+ * Eine Raster-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code #} eingeleitet wird (z.B. {@code #0}). Bei der Auswertung des Operatorbaums
+ * (auf einer bestimmten Raster-Koordinate) wird diese Referenz durch den
+ * entsprechenden Zell-Wert des referenzierten Rasters ersetzt.<br>
+ * <br>
+ * <b>Filter-Referenz:</b> {@link FilterReferenceNode}<br>
+ * Eine Filter-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code F} eingeleitet wird (z.B. {@code F1(.)}) Die Auswertung eines Filters kann nur
+ * direkt auf einer Raster-Referenz erfolgen, da ansonsten keine zellen-weise
+ * Abarbeitung des Rasters mehr moeglich ist, sondern komplette Raster als
+ * Zwischenergebnisse ausgewertet werden muessten.<br>
+ * <b>Koordinaten-Referenz:</b> {@code X} oder {@code Y}<br>
+ * Stellt einen Alias fuer die X- bzw. Y-Koordinate der Rasterzelle dar, die
+ * verarbeitet wird.<br>
+ * <b>NoData:</b> Konstante {@code NoData} und Funktion {@code isNoData(.)}<br>
+ * Stellen einen Alias fuer {@code NaN}, bzw. {@code isNaN(.)} dar.
+ * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterOperationTreeParser extends OperationTreeParser {
+  /**
+   * Erstellt einen Raster-Operator-Baum aus einem Formel-String.
+   * @param rule Formel
+   */
+  public RasterOperationTree parse(String rule) {
+    return new RasterOperationTree( super.parse(rule).getRoot() );
+  }
+
+  /**
+   * Parst das naechste Literal aus dem Tokenizer {@link #tok}.
+   * Erweitert die Funktionalitaet der Oberklasse, so dass neben Konstanten
+   * auch Referenznummern auf ein Raster (eingeleitet durch {@code #}) und
+   * Referenznummern auf einen Filter (eingeleitet durch {@code F})
+   * angegeben werden koennen. Zudem koennen die Konstanten {@code X} und {@code Y}
+   * verwendet werden, um die horizontale und vertikale Raster-Koordinate zu
+   * referenzieren und {@code NoData}, bzw. {@code isNoData(.)} als Alias fuer
+   * {@code NaN}, bzw. {@code isNaN} verwendet werden.
+   */
+  protected BinaryTreeNode parseLiteral() {
+    String token = tok.nextToken().trim();
+
+    // Filter
+    if (token.startsWith("F")) {
+      BinaryTreeNode operandNode = parseRulePart();
+      if ( !(operandNode instanceof RasterOperationTree.RasterReferenceNode) )
+        throw new UnsupportedOperationException("Filter can only be applied on a raster reference");
+      return new RasterOperationTree.FilterReferenceNode(getIntFromString(token.substring(1)),operandNode);
+    }
+
+    // Raster-Indikator
+    if (token.startsWith("#"))
+      return new RasterOperationTree.RasterReferenceNode(getIntFromString(token.substring(1)));
+
+    // X- oder Y-Koordinate, NODATA
+    if ( token.equalsIgnoreCase("X") || token.equalsIgnoreCase("Y") ||
+         token.equalsIgnoreCase("NODATA") ) {
+      return new OperationTree.ConstantAliasNode(token);
+    }
+
+    // Check auf NoData
+    if ( token.equalsIgnoreCase("ISNODATA") )
+      return new OperationTree.UnaryOperatorNode(token, parseRulePart());
+
+    // sonst: auf Literale der Oberklasse zurueckgreifen
+    tok.pushback();
+    return super.parseLiteral();
+  }
+}
+

Added: trunk/src/schmitzm/data/RasterOperationTreeParser.java
===================================================================
--- trunk/src/schmitzm/data/RasterOperationTreeParser.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/RasterOperationTreeParser.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,103 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.OperationTreeParser;
+import schmitzm.data.RasterOperationTree.*;
+
+/**
+ * Diese Klasse stellt einen Parser fuer einen {@linkplain RasterOperationTree Raster-Operatorbaum}
+ * dar. Dieser erstellt einen {@linkplain RasterOperationTree Raster-Operatorbaum} aus einem
+ * Formel-String, der neben den in {@link OperationTreeParser} beschriebenen
+ * Komponenten Raster- und Filter-Referenzen enthalten darf.<br>
+ * <br>
+ * <b>Raster-Referenz:</b> {@link RasterReferenceNode}<br>
+ * Eine Raster-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code #} eingeleitet wird (z.B. {@code #0}). Bei der Auswertung des Operatorbaums
+ * (auf einer bestimmten Raster-Koordinate) wird diese Referenz durch den
+ * entsprechenden Zell-Wert des referenzierten Rasters ersetzt.<br>
+ * <br>
+ * <b>Filter-Referenz:</b> {@link FilterReferenceNode}<br>
+ * Eine Filter-Referenz wird durch einen {@code int}-Wert dargestellt, der von
+ * einem {@code F} eingeleitet wird (z.B. {@code F1(.)}) Die Auswertung eines Filters kann nur
+ * direkt auf einer Raster-Referenz erfolgen, da ansonsten keine zellen-weise
+ * Abarbeitung des Rasters mehr moeglich ist, sondern komplette Raster als
+ * Zwischenergebnisse ausgewertet werden muessten.<br>
+ * <b>Koordinaten-Referenz:</b> {@code X} oder {@code Y}<br>
+ * Stellt einen Alias fuer die X- bzw. Y-Koordinate der Rasterzelle dar, die
+ * verarbeitet wird.<br>
+ * <b>NoData:</b> Konstante {@code NoData} und Funktion {@code isNoData(.)}<br>
+ * Stellen einen Alias fuer {@code NaN}, bzw. {@code isNaN(.)} dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RasterOperationTreeParser extends OperationTreeParser {
+  /**
+   * Erstellt einen Raster-Operator-Baum aus einem Formel-String.
+   * @param rule Formel
+   */
+  public RasterOperationTree parse(String rule) {
+    return new RasterOperationTree( super.parse(rule).getRoot() );
+  }
+
+//  /**
+//   * Liefert einen LeerString, da der Strings im RasterCalculator keinen Sinn
+//   * machen.
+//   */
+//  @Override
+//  public String getStringEncapsulationChars() {
+//    return "";
+//  }
+
+  /**
+   * Parst das naechste Literal aus dem Tokenizer {@link #tok}.
+   * Erweitert die Funktionalitaet der Oberklasse, so dass neben Konstanten
+   * auch Referenznummern auf ein Raster (eingeleitet durch {@code #}) und
+   * Referenznummern auf einen Filter (eingeleitet durch {@code F})
+   * angegeben werden koennen. Zudem koennen die Konstanten {@code X} und {@code Y}
+   * verwendet werden, um die horizontale und vertikale Raster-Koordinate zu
+   * referenzieren und {@code NoData}, bzw. {@code isNoData(.)} als Alias fuer
+   * {@code NaN}, bzw. {@code isNaN} verwendet werden.
+   */
+  protected BinaryTreeNode parseLiteral() {
+    String token = nextNonWSToken();
+
+    // Filter
+    if (token.startsWith("F")) {
+      BinaryTreeNode operandNode = parseRulePart();
+      if ( !(operandNode instanceof RasterOperationTree.RasterReferenceNode) )
+        throw new UnsupportedOperationException("Filter can only be applied on a raster reference");
+      return new RasterOperationTree.FilterReferenceNode(getIntFromString(token.substring(1)),operandNode);
+    }
+
+    // Raster-Indikator
+    if (token.startsWith("#"))
+      return new RasterOperationTree.RasterReferenceNode(getIntFromString(token.substring(1)));
+
+    // X- oder Y-Koordinate, NODATA
+    if ( token.equalsIgnoreCase("X") || token.equalsIgnoreCase("Y") ||
+         token.equalsIgnoreCase("NODATA") ) {
+      return new OperationTree.ConstantAliasNode(token);
+    }
+
+    // Check auf NoData
+    if ( token.equalsIgnoreCase("ISNODATA") )
+      return new OperationTree.UnaryOperatorNode(token, parseRulePart());
+
+    // sonst: auf Literale der Oberklasse zurueckgreifen
+    pushbackWithWSToken();
+    return super.parseLiteral();
+  }
+}
+

Added: trunk/src/schmitzm/data/ReadableGrid.java
===================================================================
--- trunk/src/schmitzm/data/ReadableGrid.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/ReadableGrid.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,216 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import appl.data.LateLoadable;
+
+/**
+ * Dieses Interface stellt die Basis fuer ein georeferenziertes
+ * Raster dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ReadableGrid extends LateLoadable {
+  /**
+   * Die Dimension des Rasters (2).
+   */
+  public static final int RASTER_DIM = 2;
+
+  /**
+   * Zerstoert das Raster und gibt alle Ressourcen wieder frei.
+   */
+  public void dispose();
+
+  /**
+   * Liefert die Breite des Rasters (in Zellen).
+   */
+  public int getWidth();
+
+  /**
+   * Liefert die Hoehe des Rasters (in Zellen).
+   */
+  public int getHeight();
+
+  /**
+   * Liefert den Index der ersten (Südwest) Zelle in X-Richtung.
+   */
+  public int getMinX();
+
+  /**
+   * Liefert den Index der ersten Zelle (Südwest) in Y-Richtung.
+   */
+  public int getMinY();
+
+  /**
+   * Liefert das {@link CoordinateReferenceSystem} in dem das Raster
+   * dargestellt ist.
+   */
+  public CoordinateReferenceSystem getCoordinateReferenceSystem();
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealWidth();
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealHeight();
+
+  /**
+   * Liefert die reale Breite einer Zelle.<br>
+   * Sollte <code>{@link #getRealWidth()} / {@link #getWidth()} entsprechen!
+   */
+  public double getCellWidth();
+
+  /**
+   * Liefert die reale Hoehe einer Zelle.<br>
+   * Sollte <code>{@link #getRealHeight()} / {@link #getHeight()} entsprechen!
+   */
+  public double getCellHeight();
+
+  /**
+   * Liefert die X-Koordinate (Longitude) der Georeferenz der linken unteren Ecke des Rasters (Südwest).
+   */
+  public double getX();
+
+  /**
+   * Liefert die Y-Koordinate (Latitude) der Georeferenz der linken unteren Ecke des Rasters (Südwest).
+   */
+  public double getY();
+
+  /**
+   * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+   * wird durch eine der TYPE-Konstanten in {@link java.awt.image.DataBuffer}
+   * repraesentiert.
+   */
+  public int getSampleType();
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public Object getRasterSample(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public short getRasterSampleAsShort(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public byte getRasterSampleAsByte(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public int getRasterSampleAsInt(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public long getRasterSampleAsLong(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public float getRasterSampleAsFloat(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public double getRasterSampleAsDouble(int... cell);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public Object getGridSample(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public short getGridSampleAsShort(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public byte getGridSampleAsByte(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public int getGridSampleAsInt(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public long getGridSampleAsLong(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public float getGridSampleAsFloat(double... coord);
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public double getGridSampleAsDouble(double... coord);
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen
+   * @param coord Georeferenz-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public int convertRealToRaster(double coord, int dim);
+
+  /**
+   * Konvertiert eine Zellennummer in reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.
+   * @param cell  Rasterzellen-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public double convertRasterToReal(int cell, int dim);
+}

Added: trunk/src/schmitzm/data/WritableGrid.java
===================================================================
--- trunk/src/schmitzm/data/WritableGrid.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/WritableGrid.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,35 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+/**
+ * Dieses Interface stellt die Basis fuer ein georeferenziertes, schreibbares
+ * Raster dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface WritableGrid extends ReadableGrid {
+  /**
+   * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+   * @param value neuer Wert
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public void setRasterSample(Object value, int... cell);
+
+  /**
+   * Setzt einen Wert im Raster ueber Geo-Koordinaten.
+   * @param value neuer Wert
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public void setGridSample(Object value, double... coord);
+}

Added: trunk/src/schmitzm/data/WritableGridArray.java
===================================================================
--- trunk/src/schmitzm/data/WritableGridArray.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/WritableGridArray.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,1213 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferDouble;
+import java.awt.image.RenderedImage;
+import java.awt.image.Raster;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.data.WritableGrid;
+
+import appl.util.RasterMetaData;
+
+/**
+ * Diese Klasse stellt eine Implementierung von {@link WritableGrid} dar und
+ * basiert auf einem einfachen {@code float}-Array.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class WritableGridArray extends AbstractWritableGrid implements WritableGrid, Serializable {
+  /** Die Dimension des Rasters (2). */
+  public static final int RASTER_DIM = 2;
+  /** Speichert die Geo-Referenz (Latitude, Longitude, Breite, Hoehe) des
+   *  Rasters. */
+  protected transient Rectangle2D envelope;
+  /** Speichert die kleinste horizontale Raster-Koordinate. */
+  protected int minX = 0;
+  /** Speichert die kleinste vertikale Raster-Koordinate. */
+  protected int minY = 0;
+  /** Speichert die Breite des Rasters in Zellen. */
+  protected int rasterWidth = 0;
+  /** Speichert die Hoehe des Rasters in Zellen. */
+  protected int rasterHeight = 0;
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   */
+  public WritableGridArray(int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    super(crs);
+    this.minX = minX;
+    this.minY = minY;
+    this.rasterWidth  = rasterWidth;
+    this.rasterHeight = rasterHeight;
+    this.envelope = envelope;
+  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   */
+//  public WritableGridArray(int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope) {
+//    this(minX, minY,rasterWidth,rasterHeight,envelope,null);
+//  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param lon Georeferenz (Longitude = X-Koordinate)
+   * @param lat Georeferenz (Latitude = Y-Koordinate)
+   * @param realWidth Breite des Rasters (in Metern)
+   * @param realHeight Hoehe des Rasters (in Metern)
+   */
+//  public WritableGridArray(int minX, int minY, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight) {
+//    this( minX, minY, rasterWidth, rasterHeight, new Rectangle2D.Double(lon,lat,realWidth,realHeight) );
+//  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param lon Georeferenz (Longitude = X-Koordinate)
+   * @param lat Georeferenz (Latitude = Y-Koordinate)
+   * @param realWidth Breite des Rasters (in Metern)
+   * @param realHeight Hoehe des Rasters (in Metern)
+   */
+//  public WritableGridArray(int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight) {
+//    this(0,0,rasterWidth,rasterHeight,lon,lat,realWidth,realHeight);
+//  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   */
+//  public WritableGridArray(int rasterWidth, int rasterHeight, Rectangle2D envelope) {
+//    this(0,0,rasterWidth,rasterHeight,envelope);
+//  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param image Daten
+   * @param envelope Georeferenz und Ausdehnung des Rasters
+   * @param crs CoordinateReferenceSystem fuer das Raster
+   * @return Ein {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} oder
+   *         {@link WritableGridArray.Double}, je nach Datentyp des {@code image}-DataBuffers
+   */
+  public static WritableGridArray create(RenderedImage image, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    return create(image.getData(),envelope,crs);
+//    int width  = image.getWidth();
+//    int height = image.getHeight();
+//    switch (image.getData().getDataBuffer().getDataType()) {
+//      case DataBuffer.TYPE_BYTE:
+//      case DataBuffer.TYPE_SHORT:
+//      case DataBuffer.TYPE_USHORT:
+//      case DataBuffer.TYPE_INT:    return new WritableGridArray.Integer(width,height,envelope,crs,image.getData());
+//      case DataBuffer.TYPE_FLOAT:  return new WritableGridArray.Float(width,height,envelope,crs,image.getData());
+//      case DataBuffer.TYPE_DOUBLE: return new WritableGridArray.Double(width,height,envelope,crs,image.getData());
+//
+//    }
+//    throw new UnsupportedOperationException("Data type of image not yet supported: "+image.getData().getDataBuffer().getDataType());
+  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param image Daten
+   * @param envelope Georeferenz und Ausdehnung des Rasters
+   * @return Ein {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} oder
+   *         {@link WritableGridArray.Double}, je nach Datentyp des {@code image}-DataBuffers
+   */
+  public static WritableGridArray create(RenderedImage image, Rectangle2D envelope) {
+    return create(image,envelope,null);
+  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param image Daten
+   * @param envelope Georeferenz und Ausdehnung des Rasters
+   * @param crs CoordinateReferenceSystem fuer das Raster
+   * @return Ein {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} oder
+   *         {@link WritableGridArray.Double}, je nach Datentyp des {@code image}-DataBuffers
+   */
+  public static WritableGridArray create(Raster raster, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    int width  = raster.getWidth();
+    int height = raster.getHeight();
+    switch (raster.getDataBuffer().getDataType()) {
+      case DataBuffer.TYPE_BYTE:
+      case DataBuffer.TYPE_SHORT:
+      case DataBuffer.TYPE_USHORT:
+      case DataBuffer.TYPE_INT:    return new WritableGridArray.Integer(width,height,envelope,crs,raster);
+      case DataBuffer.TYPE_FLOAT:  return new WritableGridArray.Float(width,height,envelope,crs,raster);
+      case DataBuffer.TYPE_DOUBLE: return new WritableGridArray.Double(width,height,envelope,crs,raster);
+
+    }
+    throw new UnsupportedOperationException("Data type of image not yet supported: "+raster.getDataBuffer().getDataType());
+  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param raster Daten
+   * @param envelope Georeferenz und Ausdehnung des Rasters
+   * @return Ein {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} oder
+   *         {@link WritableGridArray.Double}, je nach Datentyp des {@code raster}-DataBuffers
+   */
+  public static WritableGridArray create(Raster raster, Rectangle2D envelope) {
+    return create(raster,envelope,null);
+  }
+
+  /**
+   * Creates an empty GridArray out of a {@link RasterMetaData} object.
+   * @param metaData the MetaData
+   * @param crs CoordinateReferenceSystem fuer das Raster
+   * @return A {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} or
+   *         {@link WritableGridArray.Double}, depending on the Datatype
+   */
+  public static WritableGridArray createEmpty(RasterMetaData metaData, CoordinateReferenceSystem crs) {
+    switch (metaData.getDataType()) {
+      case DataBuffer.TYPE_INT:    return new WritableGridArray.Integer(metaData.getMinX(),metaData.getMinY(),metaData.getWidth(),metaData.getHeight(),metaData.getX(),metaData.getY(),metaData.getRealWidth(),metaData.getRealHeight(),crs,null);
+      case DataBuffer.TYPE_FLOAT:  return new WritableGridArray.Float(metaData.getMinX(),metaData.getMinY(),metaData.getWidth(),metaData.getHeight(),metaData.getX(),metaData.getY(),metaData.getRealWidth(),metaData.getRealHeight(),crs,null);
+      case DataBuffer.TYPE_DOUBLE: return new WritableGridArray.Double(metaData.getMinX(),metaData.getMinY(),metaData.getWidth(),metaData.getHeight(),metaData.getX(),metaData.getY(),metaData.getRealWidth(),metaData.getRealHeight(),crs,null);
+
+    }
+    throw new UnsupportedOperationException("Data type of image not yet supported!");
+  }
+
+  /**
+   * Creates an empty GridArray out of a {@link RasterMetaData} object. WGS84 is
+   * used for CRS.
+   * @param metaData the MetaData
+   * @return A {@link WritableGridArray.Integer}, {@link WritableGridArray.Float} or
+   *         {@link WritableGridArray.Double}, depending on the Datatype
+   */
+  public static WritableGridArray createEmpty(RasterMetaData metaData) {
+    return createEmpty(metaData,metaData.getCoordinateReferenceSystem());
+  }
+
+  /**
+   * Liefert eine Referenz auf den kompletten Inhalt des Rasters in Form eines
+   * {@link DataBuffer}.
+   */
+  public abstract DataBuffer getDataBuffer();
+
+  /**
+   * Liefert eine Referenz auf den kompletten Inhalt des Rasters.
+   * @return einen 1-dimensionalen Array eines Basis-Datentyps
+   */
+  public abstract Object getData();
+
+  /**
+   * Liefert eine Kopie des kompletten Inhalts des Rasters.
+   * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+   * @return einen 2-dimensionaler Array eines Basis-Datentyps (z.B. {@code float[height][width]})
+   */
+  public abstract Object getData(Object data);
+
+  /**
+   * Macht zur Zeit noch nichts.
+   * @todo WritableGridArray.dispose() implementieren
+   */
+  public void dispose() {
+  }
+
+  /**
+   * Liefert die X-Koordinate der Georeferenz (Longitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   */
+  public double getX() {
+    return envelope.getX();
+  }
+
+  /**
+   * Liefert die Y-Koordinate der Georeferenz (Latitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   */
+  public double getY() {
+    return envelope.getY();
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealWidth() {
+    return envelope.getWidth();
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealHeight() {
+    return envelope.getHeight();
+  }
+
+  /**
+   * Liefert die Georeferenz (Lat/Lon) und Ausdehnung des Rasters.
+   */
+  public Rectangle2D getEnvelope() {
+    return (Rectangle2D)envelope.clone();
+  }
+
+  /**
+   * Liefert die Breite des Rasters (in Zellen).
+   */
+  public int getWidth() {
+    return this.rasterWidth;
+  }
+
+  /**
+   * Liefert die Hoehe des Rasters (in Zellen).
+   */
+  public int getHeight() {
+    return this.rasterHeight;
+  }
+
+  /**
+   * Liefert den Index der ersten (Südwest) Zelle in X-Richtung.
+   */
+  public int getMinX() {
+    return this.minX;
+  }
+
+  /**
+   * Liefert den Index der ersten Zelle (Südwest) in Y-Richtung.
+   */
+  public int getMinY() {
+    return this.minY;
+  }
+
+  /** Serializes the Grid
+   * @author Dominik Appl
+   * @param s
+   * @throws IOException
+   */
+  private synchronized void writeObject( java.io.ObjectOutputStream s )
+  throws IOException
+  {
+	  //writing all attributes to the outputstream
+	  s.defaultWriteObject();
+	  //writing the transient envelope (which is not serializable)
+	  s.writeDouble(envelope.getX());
+	  s.writeDouble(envelope.getY());
+	  s.writeDouble(envelope.getHeight());
+	  s.writeDouble(envelope.getWidth());
+
+  }
+
+  /** Deserializes the Grid
+   * @author Dominik Appl
+   * @param s
+   * @throws IOException
+   */
+  private synchronized void readObject( java.io.ObjectInputStream s )
+   throws IOException, ClassNotFoundException
+   {
+	  //reading standard fields
+	  s.defaultReadObject();
+	  //reading envelope
+	  double lat = s.readDouble();
+	  double lon = s.readDouble();
+	  double realHeight = s.readDouble();
+	  double realWidth = s.readDouble();
+	  envelope = new Rectangle2D.Double(lat,lon,realWidth,realHeight);
+   }
+
+  /////////////////////////////////////////////////////////////////////////
+  ///////////////////////////   Float - Raster ////////////////////////////
+  /////////////////////////////////////////////////////////////////////////
+  public static class Float extends WritableGridArray {
+    /** Speichert zeilenweise die Daten des {@code float}-Rasters ({@code data[y*w+x])} */
+    protected float[] data = null;
+
+    /**
+     * Erzeugt ein neues {@code float}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, float[] data) {
+      super(minX,minY,rasterWidth,rasterHeight,envelope,crs);
+      if ( data == null )
+        data = new float[rasterHeight*rasterWidth];
+      if ( data.length < rasterHeight*rasterWidth )
+        throw new IllegalArgumentException("Array for raster content to small!");
+      this.data = data;
+    }
+
+    /**
+     * Erzeugt ein neues {@code float}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int minX, int minY, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, float[] data) {
+      this( minX, minY, rasterWidth, rasterHeight, new Rectangle2D.Double(lon,lat,realWidth,realHeight),crs, data );
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code float}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, float[] data) {
+      this(0,0,rasterWidth,rasterHeight,lon,lat,realWidth,realHeight,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code float}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, float[] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code float}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, float[][] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          this.data[y*rasterWidth+x] = data[x][y];
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code float}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss das Raster
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Float(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, Raster data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      data.getPixels(0,0,rasterWidth,rasterHeight,this.data);
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Raster.
+     * @return {@code float[height*width]}-Array.
+     */
+    public float[] getData() {
+      return this.data;
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Rasters in Form eines
+     * {@link DataBuffer}.
+     */
+    public DataBufferFloat getDataBuffer() {
+      return new DataBufferFloat(data,data.length);
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return einen {@code float[height][width]}-Array.
+     */
+    public float[][] getData(Object data) {
+      return getData((float[][])data);
+    }
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code float[height][width]}-Array.
+     */
+    public float[][] getData(float[][] data) {
+      if ( data == null )
+        data = new float[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code float}-Werte
+     * des Rasters werden einfach zu {@code int} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code int[height][width]}-Array.
+     */
+    public int[][] getData(int[][] data) {
+      if ( data == null )
+        data = new int[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = (int)this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code float}-Werte
+     * des Rasters werden einfach zu {@code double} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code double[height][width]}-Array.
+     */
+    public double[][] getData(double[][] data) {
+      if ( data == null )
+        data = new double[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+     * wird durch eine der TYPE-Konstanten in {@link DataBuffer}
+     * repraesentiert.
+     * @return immer {@link DataBuffer#TYPE_FLOAT DataBuffer.TYPE_FLOAT}
+     */
+    public int getSampleType() {
+      return DataBuffer.TYPE_FLOAT;
+    }
+
+    /**
+     * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+     * @param value neuer Wert
+     * @param cell  2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *              {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(Object value, int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM +
+                                                " Coordinates expected");
+      if (value == null)
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " can not store null-Objects");
+      if (! (value instanceof java.lang.Number))
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " only can not store "+value.getClass().getSimpleName()+"-Objects");
+
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = ((Number)value).floatValue();
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public java.lang.Float getRasterSample(int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM + " Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public float getRasterSampleAsFloat(int... cell) {
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(float value, int... cell) {
+      if ( cell.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = value;
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Geo-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public float getGridSampleAsFloat(double... coord){
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      return data[cellY*rasterWidth + cellX];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+     * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+     * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+     * Greift direkt auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setGridSample(float value, int... coord) {
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      data[cellY*rasterWidth+cellX] = value;
+    }
+  }
+
+  /////////////////////////////////////////////////////////////////////////
+  ///////////////////////////   Double - Raster ////////////////////////////
+  /////////////////////////////////////////////////////////////////////////
+  public static class Double extends WritableGridArray {
+    /** Speichert zeilenweise die Daten des {@code double}-Rasters ({@code data[y*w+x])} */
+    protected double[] data = null;
+
+    /**
+     * Erzeugt ein neues {@code double}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, double[] data) {
+      super(minX,minY,rasterWidth,rasterHeight,envelope,crs);
+      if ( data == null )
+        data = new double[rasterHeight*rasterWidth];
+      if ( data.length < rasterHeight*rasterWidth )
+        throw new IllegalArgumentException("Array for raster content to small!");
+      this.data = data;
+    }
+
+    /**
+     * Erzeugt ein neues {@code double}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int minX, int minY, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, double[] data) {
+      this( minX, minY, rasterWidth, rasterHeight, new Rectangle2D.Double(lon,lat,realWidth,realHeight), crs, data );
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code double}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, double[] data) {
+      this(0,0,rasterWidth,rasterHeight,lon,lat,realWidth,realHeight,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code double}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, double[] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code double}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, double[][] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          this.data[y*rasterWidth+x] = data[x][y];
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code double}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss das Raster
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Double(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, Raster data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      data.getPixels(0,0,rasterWidth,rasterHeight,this.data);
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Raster.
+     * @return {@code double[height*width]}-Array.
+     */
+    public double[] getData() {
+      return this.data;
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Rasters in Form eines
+     * {@link DataBuffer}.
+     */
+    public DataBufferDouble getDataBuffer() {
+      return new DataBufferDouble(data,data.length);
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return einen {@code double[height][width]}-Array.
+     */
+    public double[][] getData(Object data) {
+      return getData((double[][])data);
+    }
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code double[height][width]}-Array.
+     */
+    public double[][] getData(double[][] data) {
+      if ( data == null )
+        data = new double[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code double}-Werte
+     * des Rasters werden einfach zu {@code float} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code float[height][width]}-Array.
+     */
+    public float[][] getData(float[][] data) {
+      if ( data == null )
+        data = new float[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = (float)this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code double}-Werte
+     * des Rasters werden einfach zu {@code int} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code int[height][width]}-Array.
+     */
+    public int[][] getData(int[][] data) {
+      if ( data == null )
+        data = new int[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = (int)this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+     * wird durch eine der TYPE-Konstanten in {@link DataBuffer}
+     * repraesentiert.
+     * @return immer {@link DataBuffer#TYPE_DOUBLE DataBuffer.TYPE_DOUBLE}
+     */
+    public int getSampleType() {
+      return DataBuffer.TYPE_DOUBLE;
+    }
+
+    /**
+     * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+     * @param value neuer Wert
+     * @param cell  2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *              {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(Object value, int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM + " Coordinates expected");
+      if (value == null)
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " can not store null-Objects");
+      if (! (value instanceof java.lang.Number))
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " only can not store "+value.getClass().getSimpleName()+"-Objects");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = ((java.lang.Number)value).doubleValue();
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public java.lang.Double getRasterSample(int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM + " Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public double getRasterSampleAsDouble(int... cell) {
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(double value, int... cell) {
+      if ( cell.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = value;
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Geo-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public double getGridSampleAsDouble(double... coord){
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      return data[cellY*rasterWidth + cellX];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+     * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+     * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+     * Greift direkt auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setGridSample(double value, int... coord) {
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      data[cellY*rasterWidth+cellX] = value;
+    }
+  }
+
+  /////////////////////////////////////////////////////////////////////////
+  ///////////////////////////   Integer - Raster ////////////////////////////
+  /////////////////////////////////////////////////////////////////////////
+  public static class Integer extends WritableGridArray {
+    /** Speichert zeilenweise die Daten des {@code int}-Rasters ({@code data[y*w+x])} */
+    protected int[] data = null;
+
+    /**
+     * Erzeugt ein neues {@code int}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, int[] data) {
+      super(minX,minY,rasterWidth,rasterHeight,envelope,crs);
+      if ( data == null )
+        data = new int[rasterHeight*rasterWidth];
+      if ( data.length < rasterHeight*rasterWidth )
+        throw new IllegalArgumentException("Array for raster content to small!");
+      this.data = data;
+    }
+
+    /**
+     * Erzeugt ein neues {@code int}-Raster.
+     * @param minX Raster-Index der ersten Zelle in X-Richtung
+     * @param minY Raster-Index der ersten Zelle in Y-Richtung
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int minX, int minY, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, int[] data) {
+      this( minX, minY, rasterWidth, rasterHeight, new Rectangle2D.Double(lon,lat,realWidth,realHeight),crs , data );
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code int}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param lon Georeferenz (Longitude = X-Koordinate)
+     * @param lat Georeferenz (Latitude = Y-Koordinate)
+     * @param realWidth Breite des Rasters (in Metern)
+     * @param realHeight Hoehe des Rasters (in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs, int[] data) {
+      this(0,0,rasterWidth,rasterHeight,lon,lat,realWidth,realHeight,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code int}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, int[] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,data);
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code int}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss der Array
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, int[][] data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          this.data[y*rasterWidth+x] = data[x][y];
+    }
+
+    /**
+     * Erzeugt ein neues leeres {@code int}-Raster.
+     * @param rasterWidth Breite des Rasters (in Zellen)
+     * @param rasterHeight Hoehe des Rasters (in Zellen)
+     * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+     * @param crs Referenzsystem fuer das Raster
+     * @param data Inhalt des Rasters (wenn nicht {@code null}, muss das Raster
+     *             mindestens die Groesse {@code rasterWidth * rasterHeight} haben)
+     */
+    public Integer(int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs, Raster data) {
+      this(0,0,rasterWidth,rasterHeight,envelope,crs,null);
+      // Daten in Array uebernehmen
+      data.getPixels(0,0,rasterWidth,rasterHeight,this.data);
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Raster.
+     * @return {@code int[height*width]}-Array.
+     */
+    public int[] getData() {
+      return this.data;
+    }
+
+    /**
+     * Liefert eine Referenz auf den kompletten Inhalt des Rasters in Form eines
+     * {@link DataBuffer}.
+     */
+    public DataBufferInt getDataBuffer() {
+      return new DataBufferInt(data,data.length);
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return einen {@code int[height][width]}-Array.
+     */
+    public int[][] getData(Object data) {
+      return getData((int[][])data);
+    }
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code int[height][width]}-Array.
+     */
+    public int[][] getData(int[][] data) {
+      if ( data == null )
+        data = new int[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code int}-Werte
+     * des Rasters werden einfach zu {@code float} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code float[height][width]}-Array.
+     */
+    public float[][] getData(float[][] data) {
+      if ( data == null )
+        data = new float[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert eine Kopie des kompletten Inhalts des Rasters. Die {@code int}-Werte
+     * des Rasters werden einfach zu {@code double} gecastet.
+     * @param data Array in den die Daten geschrieben werden (kann {@code null} sein!)
+     * @return {@code double[height][width]}-Array.
+     */
+    public double[][] getData(double[][] data) {
+      if ( data == null )
+        data = new double[rasterHeight][rasterWidth];
+      for (int y=0; y<rasterHeight; y++)
+        for (int x=0; x<rasterWidth; x++)
+          data[y][x] = this.data[y*rasterWidth+x];
+      return data;
+    }
+
+    /**
+     * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+     * wird durch eine der TYPE-Konstanten in {@link DataBuffer}
+     * repraesentiert.
+     * @return immer {@link DataBuffer#TYPE_INT DataBuffer.TYPE_INT}
+     */
+    public int getSampleType() {
+      return DataBuffer.TYPE_INT;
+    }
+
+    /**
+     * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+     * @param value neuer Wert
+     * @param cell  2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *              {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(Object value, int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM + " Coordinates expected");
+      if (value == null)
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " can not store null-Objects");
+      if (! (value instanceof java.lang.Number))
+        throw new UnsupportedOperationException(getClass().getSimpleName() +
+                                                " only can not store "+value.getClass().getSimpleName()+"-Objects");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = ((java.lang.Number)value).intValue();
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public java.lang.Integer getRasterSample(int ...cell) {
+      if (cell.length < RASTER_DIM)
+        throw new UnsupportedOperationException(RASTER_DIM + " Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public int getRasterSampleAsInt(int... cell) {
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      return data[y*rasterWidth+x];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Raster-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+     *             {@link #getMinX()} und {@link #getMinY()})
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setRasterSample(int value, int... cell) {
+      if ( cell.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      int x = cell[0] - minX;
+      int y = cell[1] - minY;
+      data[y*rasterWidth+x] = value;
+    }
+
+    /**
+     * Liefert einen Wert des Rasters ueber Geo-Koordinaten. Greift direkt
+     * auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public int getGridSampleAsInt(double... coord){
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      return data[cellY*rasterWidth + cellX];
+    }
+
+    /**
+     * Setzt einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+     * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+     * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+     * Greift direkt auf den Daten-Array zu und nutzt nicht die generischen Methoden der
+     * Oberklasse.
+     * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+     * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+     *            Koordinaten angegeben werden
+     */
+    public void setGridSample(int value, int... coord) {
+      if ( coord.length < RASTER_DIM )
+        throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+      // Koordinaten umrechnen in Zellen-Index
+      int cellX = convertRealToRaster(coord[0],0);
+      int cellY = convertRealToRaster(coord[1],1);
+      data[cellY*rasterWidth+cellX] = value;
+    }
+  }
+}

Added: trunk/src/schmitzm/data/WritableGridRaster.java
===================================================================
--- trunk/src/schmitzm/data/WritableGridRaster.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/WritableGridRaster.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,511 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data;
+
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.WritableRaster;
+import java.awt.image.DataBuffer;
+
+import java.awt.Point;
+import java.awt.geom.Rectangle2D;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import appl.data.LoadingException;
+
+import schmitzm.data.WritableGrid;
+import schmitzm.geotools.GTUtil;
+
+/**
+ * Diese Klasse stellt eine Implementierung von {@link WritableGrid} dar und
+ * basiert auf der Standard-Java Raster-Implementierung {@link WritableRaster}.
+ * Auch wenn {@link WritableRaster} prinzipell mehrere Dimensionen (Baender)
+ * zulaesst, sind die Zugriffsmethoden dieser Klasse auf ein Band beschraenkt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class WritableGridRaster extends WritableRaster implements WritableGrid {
+  /**
+   * Die Dimension des Rasters (2).
+   */
+  public static final int RASTER_DIM = 2;
+
+  private static final int band = 0;
+
+  /**
+   * Speichert die Geo-Referenz (Longitude, Latitude, Breite, Hoehe) des
+   * Rasters.
+   */
+  protected Rectangle2D envelope;
+
+  /**
+   * Speichert das CRS des Rasters. Default ist WGS84.
+   */
+  protected CoordinateReferenceSystem crs = GTUtil.WGS84;
+
+  /**
+   * Erzeugt ein georeferenziertes Raster aus einem nicht-georeferenzierten.
+   * @param raster   Datenbasis fuer das Raster
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   * @param crs      CoordinateReferenceSystem des Rasters
+   * @see DataBuffer
+   */
+  public WritableGridRaster(WritableRaster raster, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    super( new ComponentSampleModel(raster.getDataBuffer().getDataType(),
+                                    raster.getWidth(),
+                                    raster.getHeight(),
+                                    1,
+                                    raster.getWidth(),
+                                    new int[] {0}),
+           raster.getDataBuffer(),
+           new Point(raster.getMinX(), raster.getMinY())
+    );
+    this.envelope = envelope;
+    this.crs      = ( crs == null ) ? GTUtil.WGS84 : crs;
+  }
+
+  /**
+   * Erzeugt ein georeferenziertes Raster aus einem nicht-georeferenzierten.
+   * Als CRS wird WGS84 verwendet.
+   * @param raster   Datenbasis fuer das Raster
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   * @see DataBuffer
+   */
+  public WritableGridRaster(WritableRaster raster, Rectangle2D envelope) {
+    this(raster,envelope,null);
+  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param dataType Im Raster zu speichernder Datentyp (siehe {@link DataBuffer})
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   * @param crs CoordinateReferenceSystem des Rasters
+   * @see DataBuffer
+   */
+  public WritableGridRaster(int dataType, int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    super( new ComponentSampleModel(dataType,
+                                    rasterWidth,
+                                    rasterHeight,
+                                    1,
+                                    rasterWidth,
+                                    new int[] {0}),
+           new Point(minX,minY)
+    );
+    this.envelope = envelope;
+    this.crs      = ( crs == null ) ? GTUtil.WGS84 : crs;
+  }
+
+  /**
+   * Erzeugt ein neues Raster in WGS84.
+   * @param dataType Im Raster zu speichernder Datentyp (siehe {@link DataBuffer})
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   * @see DataBuffer
+   */
+  public WritableGridRaster(int dataType, int minX, int minY, int rasterWidth, int rasterHeight, Rectangle2D envelope) {
+    this(dataType,minX,minY,rasterWidth,rasterHeight,envelope,null);
+  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param dataType Im Raster zu speichernder Datentyp (siehe {@link DataBuffer})
+   * @param minX Raster-Index der ersten Zelle in X-Richtung
+   * @param minY Raster-Index der ersten Zelle in Y-Richtung
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param lon Georeferenz (Longitude = X-Koordinate)
+   * @param lat Georeferenz (Latitude = Y-Koordinate)
+   * @param realWidth Breite des Rasters (in Metern)
+   * @param realHeight Hoehe des Rasters (in Metern)
+   * @param crs CoordinateReferenceSystem des Rasters
+   * @see DataBuffer
+   */
+  public WritableGridRaster(int dataType, int minX, int minY, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs) {
+    this( dataType, minX, minY, rasterWidth, rasterHeight, new Rectangle2D.Double(lon,lat,realWidth,realHeight), crs );
+  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param dataType Im Raster zu speichernder Datentyp (siehe {@link DataBuffer})
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param lon Georeferenz (Longitude = X-Koordinate)
+   * @param lat Georeferenz (Latitude = Y-Koordinate)
+   * @param realWidth Breite des Rasters (in Metern)
+   * @param realHeight Hoehe des Rasters (in Metern)
+   * @param crs CoordinateReferenceSystem des Rasters
+   * @see DataBuffer
+   */
+  public WritableGridRaster(int dataType, int rasterWidth, int rasterHeight, double lon, double lat, double realWidth, double realHeight, CoordinateReferenceSystem crs) {
+    this(dataType,0,0,rasterWidth,rasterHeight,lon,lat,realWidth,realHeight,crs);
+  }
+
+  /**
+   * Erzeugt ein neues Raster.
+   * @param dataType Im Raster zu speichernder Datentyp (siehe {@link DataBuffer})
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Georeferenz des Rasters (Longitude = X-Koordinate, Latitude = Y-Koordinate, Breite und Hoehe des Rasters in Metern)
+   * @param crs CoordinateReferenceSystem des Rasters
+   * @see DataBuffer
+   */
+  public WritableGridRaster(int dataType, int rasterWidth, int rasterHeight, Rectangle2D envelope, CoordinateReferenceSystem crs) {
+    this(dataType,0,0,rasterWidth,rasterHeight,envelope,crs);
+  }
+
+  /**
+   * Macht zur Zeit noch nichts.
+   * @todo WritableGridRaster.dispose() implementieren
+   */
+  public void dispose() {
+  }
+
+  /**
+   * Liefert das {@link CoordinateReferenceSystem} in dem das Raster
+   * dargestellt ist.
+   */
+  public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+    return this.crs;
+  }
+
+  /**
+   * Liefert die X-Koordinate der Georeferenz (Longitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   * ((offtopic: Südwest ist das Gegenteil von Nördost))
+   */
+  public double getX() {
+    return envelope.getX();
+  }
+
+  /**
+   * Liefert die Y-Koordinate der Georeferenz (Latitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   */
+  public double getY() {
+    return envelope.getY();
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealWidth() {
+    return envelope.getWidth();
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealHeight() {
+    return envelope.getHeight();
+  }
+
+  /**
+   * Liefert die Georeferenz (Lat/Lon) und Ausdehnung des Rasters.
+   */
+  public Rectangle2D getEnvelope() {
+    return (Rectangle2D)envelope.clone();
+  }
+
+  /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealWidth() / getWidth()</code>
+   */
+  public double getCellWidth() {
+    return getRealWidth() / getWidth();
+  }
+
+  /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealHeight() / getHeight()</code>
+   */
+  public double getCellHeight() {
+    return getRealHeight() / getHeight();
+  }
+
+  /**
+   * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+   * wird durch eine der TYPE-Konstanten in {@link DataBuffer}
+   * repraesentiert.
+   */
+  public int getSampleType() {
+    return getDataBuffer().getDataType();
+  }
+
+  /**
+   * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+   * @param value neuer Wert
+   * @param cell  2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *              {@link #getMinX()} und {@link #getMinY()})
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public void setRasterSample(Object value, int... cell) {
+    if ( cell.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+//    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+//    // linken oberen Ecke. Im Grid soll die Zaehlung jedoch in der linken unteren
+//    // Ecke beginnen -> Y-Koordinate umrechnen
+//    int x = cell[0];
+//    int y = getMinY() + getHeight()-1 - cell[1];
+    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+    // linken oberen Ecke. Im Grid soll es genauso sein!!
+    int x = cell[0];
+    int y = cell[1];
+
+    switch ( getSampleType() ) {
+      case DataBuffer.TYPE_BYTE:
+      case DataBuffer.TYPE_SHORT:
+      case DataBuffer.TYPE_USHORT:
+      case DataBuffer.TYPE_INT: setSample(x,y,band,((Number)value).intValue());
+                                break;
+      case DataBuffer.TYPE_FLOAT: setSample(x,y,band,((Number)value).floatValue());
+                                  break;
+      case DataBuffer.TYPE_DOUBLE: setSample(x,y,band,((Number)value).doubleValue());
+                                   break;
+    }
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   * @return je nach {@linkplain #getSampleType() Art} des Rasters einen
+   *         <code>int</code>, <code>float</code> oder <code>double</code>
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public Object getRasterSample(int... cell) {
+    if ( cell.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+//    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+//    // linken oberen Ecke. Im Grid soll die Zaehlung jedoch in der linken unteren
+//    // Ecke beginnen -> Y-Koordinate umrechnen
+//    int x = cell[0];
+//    int y = getMinY() + getHeight()-1 - cell[1];
+    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+    // linken oberen Ecke. Im Grid soll es genauso sein!!
+    int x = cell[0];
+    int y = cell[1];
+    switch ( getSampleType() ) {
+      case DataBuffer.TYPE_BYTE:
+      case DataBuffer.TYPE_SHORT:
+      case DataBuffer.TYPE_USHORT:
+      case DataBuffer.TYPE_INT: return getSample(x,y,band);
+      case DataBuffer.TYPE_FLOAT: return getSampleFloat(x,y,band);
+      case DataBuffer.TYPE_DOUBLE: return getSampleDouble(x,y,band);
+    }
+    return getSample(x,y,band);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public short getRasterSampleAsShort(int... cell) {
+    return ((Number)getRasterSample(cell)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public byte getRasterSampleAsByte(int... cell) {
+    return ((Number)getRasterSample(cell)).byteValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public int getRasterSampleAsInt(int... cell) {
+    return ((Number)getRasterSample(cell)).intValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public long getRasterSampleAsLong(int... cell) {
+    return ((Number)getRasterSample(cell)).longValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public float getRasterSampleAsFloat(int... cell) {
+    return ((Number)getRasterSample(cell)).floatValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public double getRasterSampleAsDouble(int... cell) {
+    return ((Number)getRasterSample(cell)).doubleValue();
+  }
+
+  /**
+   * Setzt einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+   * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public void setGridSample(Object value, double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    setRasterSample(value,cellX,cellY);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * Liegt der Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen,
+   * wird die naechst groessere Zelle gewaehlt (ausser die Grenze entspricht
+   * dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public Object getGridSample(double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    return getRasterSample(cellX,cellY);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public short getGridSampleAsShort(double... coord) {
+    return ((Number)getGridSample(coord)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public byte getGridSampleAsByte(double... coord){
+    return ((Number)getGridSample(coord)).byteValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public int getGridSampleAsInt(double... coord){
+    return ((Number)getGridSample(coord)).intValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public long getGridSampleAsLong(double... coord){
+    return ((Number)getGridSample(coord)).longValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public float getGridSampleAsFloat(double... coord){
+    return ((Number)getGridSample(coord)).floatValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public double getGridSampleAsDouble(double... coord){
+    return ((Number)getGridSample(coord)).doubleValue();
+  }
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen
+   * @param coord Georeferenz-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public int convertRealToRaster(double coord, int dim) {
+    return AbstractWritableGrid.convertRealToRaster(this,coord,dim);
+  }
+
+  /**
+   * Konvertiert eine Zellennummer in reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.
+   * @param cell  Rasterzellen-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public double convertRasterToReal(int cell, int dim) {
+    return AbstractWritableGrid.convertRasterToReal(this,cell,dim);
+  }
+
+  /**
+   * This class does not support late loading itself!
+   * @return false;
+   * @see appl.data.LateLoadable#unloadData()
+   * @see appl.data.LateLoadable#isLateLoadable()
+   */
+  public boolean isLateLoadable() {
+    return false;
+  }
+
+  /**
+   * This class  does not support late loading!
+   * @see appl.data.LateLoadable#unloadData()
+   * @see appl.data.LateLoadable#loadData()
+   */
+  public void loadData() throws LoadingException {
+  }
+
+  /**
+   * Does nothing!
+   * This class does not support late loading!
+   * @see appl.data.LateLoadable#unloadData()
+   */
+  public void unloadData() {
+  }
+
+}

Added: trunk/src/schmitzm/data/event/AbstractObjectEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/AbstractObjectEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/AbstractObjectEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,127 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Diese Klasse stellt eine Basis-Implementierung fuer ein Ereignis dar, das von einem Objekt
+ * ausgeloest wird. Spezielle Ereignisse (z.B. "Objekt wurde geschlossen" oder
+ * "Objekt hat sich geaendert" oder "Eine Eigenschaft eines Objekts hat sich
+ * geaendert") werden durch Spezialisierungen dieser Klasse ausgedrueckt.<br>
+ * <br>
+ * Haeufig ziehen einzelne Objekt-Aenderungen andere nach sich. z.B.
+ * Wertaenderung eines Listen-Elements einer ListProperty zieht die Aenderung
+ * der Liste mit sich und somit die Aenderung des Objekts, welches
+ * die ListProperty beinhaltet. Propagiert ein Objekt eine Aenderung also weiter
+ * nach unten erzeugt es (fuer seine Listener) ein neues Event:<br>
+ * <blockquote><pre>
+ * ...
+ * public void performObjectEvent(ObjectEvent e) {
+ *     if ( e instanceof ObjectChangeEvent ) {
+ *         ObjectChangeEvent oce = (ObjectChangeEvent)e;
+ *         fireEvent( new ObjectChangeEvent(
+ *             new Invoker( this,oce.getSource() ),
+ *             oce.getOldValue(),
+ *             oce.getNewValue()
+ *         ) );
+ *     }
+ * }
+ * ...
+ * </pre></blockquote>
+ * Aus diesem Grund ist <code>ObjectEvent.getObject()</code> nicht immer das
+ * Element, welches die Aenderung ausgeloest hat. Diese kann jedoch durch
+ * <code>source.getRoot()</code> ermittelt werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractObjectEvent implements ObjectEvent {
+  /**
+   * Ereignis-Typ, der anzeigt, dass sich der Zustand eines Objekts
+   * geaendert hat.
+   */
+  public static final int STATE_CHANGED = 10;
+
+  /**
+   * Ereignis-Typ, der anzeigt, dass ein Objekt geschlossen wird.
+   */
+  public static final int OBJECT_CLOSED = 20;
+
+  /**
+   * Speichert die ID des Events.
+   */
+  protected int type = 0;
+
+  /**
+   * Speichert die Objekt-Hierarchie, die von diesem Ereignis betroffen ist
+   */
+  protected Invoker source;
+
+  /**
+   * Erzeugt ein neues Event.
+   * @param source Ausloeser fuer dieses Event.
+   * @param type   Type-ID des Events
+   */
+  public AbstractObjectEvent(Invoker source, int type) {
+    this.source = source;
+    this.type   = type;
+  }
+
+  /**
+   * Erzeugt ein neues Event.
+   * @param source Ausloeser fuer dieses Event. Fuer dieses Objekt wird ein neuer
+   *               Root-Invoker gebildet.
+   * @param type   ID des Events
+   */
+  public AbstractObjectEvent(Object source, int type) {
+    this(new Invoker(source),type);
+  }
+
+  /**
+   * Liefert die ID des Events.
+   */
+  public int getType() {
+    return this.type;
+  }
+
+  /**
+   * Liefert die Objekt-Hierarchie, die von dem Ereignis betroffen ist.
+   * Deren Wurzel ist das Objekt, welches das Ereignis ausgeloest hat.<br>
+   * <b>Beispiel:</b><br>
+   * <ul>
+   * <li><code>getInvoker().getObject()</code> stellt das "letzte" von einer
+   *     Datenaenderung betroffene Objekt dar<br>
+   *     z.B. ein <code>XuluObject</code></li>
+   * <li><code>getInvoker().getInvoker().getObject()</code> stellt das
+   *     "darueberliegende" Objekt dar<br>
+   *     z.B. eine <code>ListProperty</code> des <code>XuluObject</code></li>
+   * <li><code>getInvoker().getRoot()</code> liefert das Objekt, was sich
+   *     urspruenglich geaendert und somit das Event ausgeloest hat ("die Wurzel")<br>
+   *     z.B. ein Element der <code>ListProperty</code> des <code>XuluObject</code></li>
+   * </ul>
+   * @see schmitzm.data.event.Invoker
+   */
+  public Invoker getSource() {
+    return this.source;
+  }
+
+  /**
+   * Erweitert die Objekt-Hierarchie, die von dem Ereignis betroffen ist.
+   * Das Objekt wird dabei der Hierarchie vorangestellt. Im folgenden ist es
+   * ueber <code>getInvoker().getObject()</code> referenzierbar. Das bisher
+   * ueber diesen Aufruf referenzierte Objekt erhaelt man dann ueber
+   * <code>getInvoker().getInvoker().getObject()</code>.
+   * @param object neues betroffenes Objekt
+   */
+  public void expandSource(Object object) {
+    this.source = new Invoker(object,this.source);
+  }
+
+}

Added: trunk/src/schmitzm/data/event/AbstractObjectTraceable.java
===================================================================
--- trunk/src/schmitzm/data/event/AbstractObjectTraceable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/AbstractObjectTraceable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,168 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+import java.util.Vector;
+
+/**
+ * Diese (abstrakte) Klasse bildet eine Basis-Implementierung fuer ein
+ * <code>ObjectTraceable</code>-Objekt.<br>
+ * Sie speichert alle <code>ObjectListener</code> in einem <code>java.util.Vector</code>
+ * und informiert diese bei einem <code>fireEvent(..)</code>-Aufruf.<br>
+ * Die Klasse ist abstrakt, weil es keine Sinn macht sie alleine zu
+ * Instanziieren. Sie ist dazu da, andere Objekte davon abzuleiten.
+ * @see schmitzm.data.event.ObjectTraceable
+ * @see schmitzm.data.event.ObjectListener
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractObjectTraceable implements ObjectTraceable {
+  private Vector  listeners     = new Vector();
+  private boolean firingEnabled = true;
+
+  /**
+   * Fuegt dem Objekt einen Listener hinzu, der bei Aenderungen informiert wird.
+   */
+  public void addObjectListener(ObjectListener listener) {
+//    System.out.println(listener.getClass().getSimpleName()+" koppelt an "+this.getClass().getSimpleName());
+    listeners.add(listener);
+  }
+
+  /**
+   * Entfernt einen Listener von dem Objekt.
+   */
+  public void removeObjectListener(ObjectListener listener) {
+//    System.out.println(listener.getClass().getSimpleName()+" koppelt ab von "+this.getClass().getSimpleName());
+    listeners.remove(listener);
+  }
+
+  /**
+   * Entfernt alle Listener von dem Objekt.
+   */
+  protected void removeAllObjectListeners() {
+    listeners.clear();
+  }
+
+  /**
+   * Prueft, ob ein bestimmter Listener auf das Objekt horcht.
+   */
+  public boolean containsObjectListener(ObjectListener listener) {
+    return listeners.contains(listener);
+  }
+
+  /**
+   * Liefert alle Listener eines bestimmten Typs.
+   * @param type Art des Listeners (Filter)
+   */
+  public ObjectListener[] getObjectListener(Class type) {
+    Vector listenersOfType = new Vector<ObjectListener>();
+    for (int i=0; i<listeners.size(); i++) {
+      ObjectListener l = (ObjectListener)listeners.elementAt(i);
+      if (type.isAssignableFrom(l.getClass()))
+        listenersOfType.add(l);
+    }
+    return (ObjectListener[])listenersOfType.toArray( new ObjectListener[0] );
+  }
+
+  /**
+   * Informiert alle Listener, dass sich das Objekt (this) geaendert hat.
+   * Dies geschieht jedoch nur, wenn das EventFiring aktiviert ist.
+   * @see #setEventFiringEnabled(boolean)
+   */
+  public void fireEvent(ObjectEvent e) {
+    if ( !isEventFiringEnabled() )
+      return;
+
+    for (int i=0; i<listeners.size(); i++)
+      ((ObjectListener)listeners.elementAt(i)).performObjectEvent(e);
+  }
+
+  /**
+   * Informiert alle Listener einer bestimmten Klasse, dass sich das Objekt
+   * (this) geaendert hat.
+   * Dies geschieht jedoch nur, wenn das EventFiring aktiviert ist.
+   * @see #setEventFiringEnabled(boolean)
+   */
+  public void fireEvent(ObjectEvent e, Class c) {
+    if ( !isEventFiringEnabled() )
+      return;
+
+    for (int i=0; i<listeners.size(); i++) {
+      ObjectListener l = (ObjectListener)listeners.elementAt(i);
+      // c muss dieselbe oder eine Superklasse von l sein
+      if ( c.isAssignableFrom( l.getClass() ) )
+        l.performObjectEvent(e);
+    }
+  }
+
+  /**
+   * (De)Aktiviert jegliches Werfen von Events durch
+   * {@link #fireEvent(ObjectEvent) fireEvent(ObjectEvent)}
+   * und {@link #fireEvent(ObjectEvent,Class) fireEvent(ObjectEvent,Class)}.
+   * Dies kann sinnvoll sein, wenn zunaechst eine grosse Anzahl an Aenderungen
+   * durchgefuehrt werden soll, bevor ein generelles Event geworfen wird.<br>
+   * Standardmaessig ist das Werfen von Events <b>aktiviert</b>.
+   * @see #fireGeneralEvent()
+   */
+  public void setEventFiringEnabled(boolean firingEnabled) {
+    this.firingEnabled = firingEnabled;
+  }
+
+  /**
+   * Prueft, ob das Werfen von Events durch
+   * {@link #fireEvent(ObjectEvent) fireEvent(ObjectEvent)}
+   * und {@link #fireEvent(ObjectEvent,Class) fireEvent(ObjectEvent,Class)}
+   * aktiviert ist.
+   * @see #setEventFiringEnabled(boolean)
+   */
+  public boolean isEventFiringEnabled() {
+    return this.firingEnabled;
+  }
+
+  /**
+   * Erzeugt ein generelles Event, mit welchem die Listener ueber Aenderungen
+   * am Objekt informiert werden, die eine generelle Reorganisation nach sich
+   * ziehen muss.<br>
+   * Ruft lediglich <code>return new GeneralObjectChangeEvent( new Invoker(this) );</code>
+   * auf. Sub-Klassen koennen diese Methode ueberschreiben, wenn ein spezielleres
+   * Event geworfen werden soll.
+   * @see GeneralObjectChangeEvent
+   */
+  protected ObjectEvent createGeneralEvent() {
+    return new GeneralObjectChangeEvent( new Invoker(this) );
+  }
+
+  /**
+   * Informiert alle Listener, dass eine Aenderung am Objekt (this)
+   * stattgefunden hat, die eine generelle Reorganisation nach sich ziehen
+   * muss.
+   * Dies geschieht jedoch nur, wenn das EventFiring aktiviert ist.
+   * @see #setEventFiringEnabled(boolean)
+   */
+  public void fireGeneralEvent() {
+    fireEvent( createGeneralEvent() );
+  }
+
+  /**
+   * Informiert alle Listener eine bestimmten Klasse, dass eine generelle
+   * Aenderung am Objekt (this) stattgefunden hat.
+   * Informiert alle Listener einer bestimmten Klasse, dass eine Aenderung
+   * am Objekt (this) stattgefunden hat, die eine generelle Reorganisation
+   * nach sich ziehen muss.
+   * Dies geschieht jedoch nur, wenn das EventFiring aktiviert ist.
+   * @see #setEventFiringEnabled(boolean)
+   */
+  public void fireGeneralEvent(Class c) {
+    fireEvent( createGeneralEvent(), c );
+  }
+
+}

Added: trunk/src/schmitzm/data/event/GeneralObjectChangeEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/GeneralObjectChangeEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/GeneralObjectChangeEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,37 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Ereignis steht fuer eine generelle Objekt-Aenderung, welche beim
+ * Listener eine generelle Restrukturierung ausloesen sollte.
+ * {@link #getOldValue()} und {@link #getNewValue()} liefern immer <code>null</code>.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeneralObjectChangeEvent extends ObjectChangeEvent {
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param source Ausloesendes Objekt
+   */
+  public GeneralObjectChangeEvent(Invoker source) {
+    super(source,null,null);
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param source Ausloesendes Objekt
+   */
+  public GeneralObjectChangeEvent(Object source) {
+    this(new Invoker(source));
+  }
+}

Added: trunk/src/schmitzm/data/event/Invoker.java
===================================================================
--- trunk/src/schmitzm/data/event/Invoker.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/Invoker.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,120 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Diese Klasse stellt eine Hierarchie von Objekten dar, welche z.B. für das
+ * Ausloesen eines Ereignisses verantwortlich waren, oder welche von der
+ * Aenderung eines Objekts betroffen wurden.<br>
+ * Ein Invoker besteht aus
+ * <ul>
+ * <li>einem "betroffenen" Objekt</li>
+ * <li>einem "verantwortlichen" Vater-Object, wiederum dargestellt durch einen Invoker.</li>
+ * </ul>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class Invoker {
+  /**
+   * Speichert das "betroffene" Objekt.
+   */
+  protected Object obj     = null;
+
+  /**
+   * Speichert das "Ausloeser"-Objekt.
+   */
+  protected Invoker source = null;
+
+  /**
+   * Speichert das Ursprungsausloeser-Objekt. Koennte beim Aufruf von
+   * <code>getRoot()</code> auch rekursiv ermittelt werden. Aus
+   * Effizienzgruenden ist eine Speicherung jedoch sinnvoller.
+   */
+  protected Object rootObj = null;
+
+  /**
+   * Erzeugt einen neuen Invoker.
+   * @param obj "betroffenes" Objekt
+   * @param source "ausloesendes" Objekt.
+   */
+  public Invoker(Object obj, Invoker source) {
+    this.obj     = obj;
+    this.source  = source;
+    // Hat das Objekt keinen Ausloeser, ist es selbst die Wurzel,
+    // ansonsten ist die Wurzel beim Ausloeser zu finden
+    this.rootObj = (source==null) ? obj : source.getRoot();
+  }
+
+  /**
+   * Erzeugt einen neuen Wurzel-Invoker. Hierbei stellt das betroffene Objekt
+   * gleichzeitig den Ausloeser dar.
+   * @param obj "betroffenes" Objekt
+   */
+  public Invoker(Object obj) {
+    this(obj,null);
+  }
+
+  /**
+   * Liefert das betroffene Objekt.
+   */
+  public Object getObject() {
+    return this.obj;
+  }
+
+  /**
+   * Liefert den Auslöser, oder <code>null</code>, falls das Objekt selbst
+   * die "Wurzel" darstellt.
+   */
+  public Invoker getSource() {
+    return this.source;
+  }
+
+  /**
+   * Liefert <code>true</code>, gdw. das Objekt die Wurzel darstellt.
+   */
+  public boolean isRoot() {
+    return source == null;
+  }
+
+  /**
+   * Liefert das Wurzel-Objekt, welches z.B. den Ursprung einer
+   * Aenderungspropagierung darstellt. Dies kann auch das Objekt selbst sein.
+   */
+  public Object getRoot() {
+    return rootObj;
+  }
+
+  /**
+   * Prueft, ob ein Objekt in der Invoker-Kette (bis zur Wurzel) der
+   * "betroffenen Objekte" enthalten ist.
+   */
+  public boolean contains(Object obj) {
+    if ( getObject() == obj )
+      return true;
+    if ( isRoot() )
+      return false;
+    return getSource().contains(obj);
+  }
+
+  /**
+   * Liefert den Invoker aus der Invoker-Kette (bis zur Wurzel), der das
+   * angegebene "betroffene" Objekt enthaelt.
+   * @return <code>null</code>, falls es keinen solchen Invoker gibt
+   */
+  public Invoker getInvoker(Object obj) {
+    if ( getObject() == obj )
+      return this;
+    if ( isRoot() )
+      return null;
+    return getSource().getInvoker(obj);
+  }
+}

Added: trunk/src/schmitzm/data/event/NameChangeEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/NameChangeEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/NameChangeEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,51 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Ereignis wird an die <code>ObjectListener</code> propagiert, wenn
+ * sich der Name (bzw. die Beschreibung) eines Objekts geaendert hat.
+ * @see schmitzm.data.event.ObjectListener
+ * @see schmitzm.data.event.ObjectEvent
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class NameChangeEvent extends ObjectChangeEvent {
+  /**
+   * Erzeugt ein neues <code>NameChangeEvent</code>.
+   * <code>source.getRoot()<code> stellt das Objekt dar, welches seinen Namen
+   * geaendert hat.<br>
+   * Siehe auch Erlauterung zu <code>ObjectEvent</code>.
+   * @param source   "Kette" zu veraendertem Objekt
+   * @param oldName  Name vor Veraenderung
+   * @param newName  Name nach Veraenderung
+   * @see schmitzm.data.event.ObjectEvent
+   */
+  public NameChangeEvent(Invoker source, String oldName, String newName) {
+    super(source,oldName,newName);
+  }
+
+  /**
+   * Liefert den Namen des Objekts <u>vor</u> der Aenderung.
+   */
+  public String getOldValue() {
+    return (String)super.getOldValue();
+  }
+
+  /**
+   * Liefert den Namen des Objekts <u>nach</u> der Aenderung.
+   */
+  public String getNewValue() {
+    return (String)super.getNewValue();
+  }
+
+}

Added: trunk/src/schmitzm/data/event/ObjectChangeEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/ObjectChangeEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/ObjectChangeEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,63 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Ereignis wird an die <code>ObjectListener</code> propagiert, wenn
+ * sich "irgendetwas" an einem Objekt geaendert hat.
+ * @see schmitzm.data.event.ObjectListener
+ * @see schmitzm.data.event.ObjectEvent
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ObjectChangeEvent extends AbstractObjectEvent {
+  /**
+   * Speichert den alten Wert bei einer Wertaenderung.
+   */
+  protected Object oldValue = null;
+
+  /**
+   * Speichert den neuen Wert bei einer Wertaenderung.
+   */
+  protected Object newValue = null;
+
+  /**
+   * Erzeugt ein neues <code>ObjectChangeEvent</code>.
+   * <code>source.getRoot()<code> stellt das Objekt dar, welches sich geaendert
+   * hat.<br>
+   * Siehe auch Erlauterung zu <code>ObjectEvent</code>.
+   * @param source   "Kette" zu veraendertem Objekt
+   * @param oldValue Wert vor Veraenderung
+   * @param newValue Wert nach Veraenderung
+   * @see schmitzm.data.event.ObjectEvent
+   */
+  public ObjectChangeEvent(Invoker source, Object oldValue, Object newValue) {
+    super(source,AbstractObjectEvent.STATE_CHANGED);
+    this.oldValue = oldValue;
+    this.newValue = newValue;
+  }
+
+  /**
+   * Liefert den Wert des Objekts <u>vor</u> der Aenderung.
+   */
+  public Object getOldValue() {
+    return this.oldValue;
+  }
+
+  /**
+   * Liefert den Wert des Objekts <u>nach</u> der Aenderung.
+   */
+  public Object getNewValue() {
+    return this.newValue;
+  }
+
+}

Added: trunk/src/schmitzm/data/event/ObjectCloseEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/ObjectCloseEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/ObjectCloseEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,35 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Ereignis wird an die <code>ObjectListener</code> propagiert, wenn
+ * ein Objekt geschlossen (zerstoert) wird.
+ * @see schmitzm.data.event.ObjectListener
+ * @see schmitzm.data.event.ObjectEvent
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ObjectCloseEvent extends AbstractObjectEvent {
+
+  /**
+   * Erzeugt ein neues <code>ObjectCloseEvent</code>.
+   * <code>source.getRoot()<code> stellt das Objekt dar, welches sich geaendert
+   * hat.<br>
+   * Siehe auch Erlauterung zu <code>ObjectEvent</code>.
+   * @param source "Kette" zum geschlossenen Objekt
+   * @see schmitzm.data.event.ObjectEvent
+   */
+  public ObjectCloseEvent(Invoker source) {
+    super(source,AbstractObjectEvent.OBJECT_CLOSED);
+  }
+}

Added: trunk/src/schmitzm/data/event/ObjectEvent.java
===================================================================
--- trunk/src/schmitzm/data/event/ObjectEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/ObjectEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,81 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Diese Klasse stellt ein allgemeines Ereignis dar, das  von einem Objekt
+ * ausgeloest wird. Spezielle Ereignisse (z.B. "Objekt wurde geschlossen" oder
+ * "Objekt hat sich geaendert" oder "Eine Eigenschaft eines Objekts hat sich
+ * geaendert") werden durch Spezialisierungen dieser Klasse ausgedrueckt.<br>
+ * <br>
+ * Haeufig ziehen einzelne Objekt-Aenderungen andere nach sich. z.B.
+ * Wertaenderung eines Listen-Elements einer ListProperty zieht die Aenderung
+ * der Liste mit sich und somit die Aenderung des Objekts, welches
+ * die ListProperty beinhaltet. Propagiert ein Objekt eine Aenderung also weiter
+ * nach unten erzeugt es (fuer seine Listener) ein neues Event:<br>
+ * <blockquote><pre>
+ * ...
+ * public void performObjectEvent(ObjectEvent e) {
+ *     if ( e instanceof ObjectChangeEvent ) {
+ *         ObjectChangeEvent oce = (ObjectChangeEvent)e;
+ *         fireEvent( new ObjectChangeEvent(
+ *             new Invoker( this,oce.getSource() ),
+ *             oce.getOldValue(),
+ *             oce.getNewValue()
+ *         ) );
+ *     }
+ * }
+ * ...
+ * </pre></blockquote>
+ * Aus diesem Grund ist <code>ObjectEvent.getObject()</code> nicht immer das
+ * Element, welches die Aenderung ausgeloest hat. Diese kann jedoch durch
+ * <code>source.getRoot()</code> ermittelt werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ObjectEvent {
+  /**
+   * Liefert die ID des Events. Mit dieser koennen Unterereignisse differenziert
+   * werden (z.B. "Objekt hinzugefuegt", oder "Objekt entfernt" fuer Datenpool-Event)
+   */
+  public int getType();
+
+  /**
+   * Liefert die Objekt-Hierarchie, die von dem Ereignis betroffen ist.
+   * Deren Wurzel ist das Objekt, welches das Ereignis ausgeloest hat.<br>
+   * <b>Beispiel:</b><br>
+   * <ul>
+   * <li><code>getInvoker().getObject()</code> stellt das "letzte" von einer
+   *     Datenaenderung betroffene Objekt dar<br>
+   *     z.B. ein <code>XuluObject</code></li>
+   * <li><code>getInvoker().getInvoker().getObject()</code> stellt das
+   *     "darueberliegende" Objekt dar<br>
+   *     z.B. eine <code>ListProperty</code> des <code>XuluObject</code></li>
+   * <li><code>getInvoker().getRoot()</code> liefert das Objekt, was sich
+   *     urspruenglich geaendert und somit das Event ausgeloest hat ("die Wurzel")<br>
+   *     z.B. ein Element der <code>ListProperty</code> des <code>XuluObject</code></li>
+   * </ul>
+   * @see schmitzm.data.event.Invoker
+   */
+  public Invoker getSource();
+
+  /**
+   * Erweitert die Objekt-Hierarchie, die von dem Ereignis betroffen ist.
+   * Das Objekt wird dabei der Hierarchie vorangestellt. Im folgenden ist es
+   * ueber <code>getInvoker().getObject()</code> referenzierbar. Das bisher
+   * ueber diesen Aufruf referenzierte Objekt erhaelt man dann ueber
+   * <code>getInvoker().getInvoker().getObject()</code>.
+   * @param object neues betroffenes Objekt
+   */
+  public void expandSource(Object object);
+
+}

Added: trunk/src/schmitzm/data/event/ObjectListener.java
===================================================================
--- trunk/src/schmitzm/data/event/ObjectListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/ObjectListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,27 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Interface muessen alle Objekte implementieren, die von
+ * Aenderungen (z.B. Wertaenderung oder Schliessen) eines Objekts
+ * (<code>ObjectTracable</code>) informiert werden "wollen".
+ * @see schmitzm.data.event.ObjectTraceable
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ObjectListener {
+  /**
+   * Wird aufgerufen, sobald sich an einem Objekt "etwas aendert".
+   */
+  public void performObjectEvent(ObjectEvent e);
+}

Added: trunk/src/schmitzm/data/event/ObjectTraceable.java
===================================================================
--- trunk/src/schmitzm/data/event/ObjectTraceable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/ObjectTraceable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,65 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.event;
+
+/**
+ * Dieses Interface muessen alle Objekte implementieren, die Aenderungen
+ * "an sich selbst" an <code>ObjectListener</code> propagieren sollen.
+ * Eine Aenderung kann eine Eigenschaftsmodifikation sein, oder auch die
+ * "Zerstoerung" (Dispose) des Objekts. Eine weitere Moeglichkeit ist die
+ * Mitgliedschaft in eine Menge. Wird ein Objekt eine Menge hinzugefuegt, oder
+ * daraus entfernt, kann es dies an <code>ObjectListener</code> melden.
+ * Die Art der Aenderung wird durch ein entsprechendes <code>ObjectEvent</code>
+ * spezifiziert.
+ * @see schmitzm.data.event.ObjectListener
+ * @see schmitzm.data.event.ObjectEvent
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ObjectTraceable {
+  /**
+   * Fuegt dem Objekt einen Listener hinzu, der bei Aenderungen informiert wird.
+   */
+  public void addObjectListener(ObjectListener listener);
+
+  /**
+   * Entfernt einen Listener von dem Objekt.
+   */
+  public void removeObjectListener(ObjectListener listener);
+
+//  /**
+//   * Entfernt alle Listener von dem Objekt.
+//   */
+//  protected void removeAllObjectListeners();
+
+  /**
+   * Checkt, ob der angegebene Listener dem Objekt zugeordnet ist.
+   */
+  public boolean containsObjectListener(ObjectListener l);
+
+  /**
+   * Liefert alle Listener eines bestimmten Typs.
+   * @param type Art des Listeners (Filter)
+   */
+  public ObjectListener[] getObjectListener(Class type);
+
+  /**
+   * Informiert alle Listener, dass sich das Objekt (this) geaendert hat.
+   */
+  public void fireEvent(ObjectEvent e);
+
+  /**
+   * Informiert alle Listener eine bestimmten Klasse, dass sich das Objekt
+   * (this) geaendert hat.
+   */
+  public void fireEvent(ObjectEvent e, Class c);
+}

Added: trunk/src/schmitzm/data/event/package.html
===================================================================
--- trunk/src/schmitzm/data/event/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/event/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,8 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen für die Ereignissteuerung. Implementiert ein Objekt das Interface
+	{@link schmitzm.data.event.ObjectTraceable} können daran {@link schmitzm.data.event.ObjectListener}
+	angeschlossen werden, welche automatisch informiert werden, wenn sich das <code>ObjectTraceable</code>-Objekt
+	ändert.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/data/package.html
===================================================================
--- trunk/src/schmitzm/data/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,5 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen für die allgemeine Verwaltung von Daten-Objekten.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/data/property/Access.java
===================================================================
--- trunk/src/schmitzm/data/property/Access.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/Access.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,162 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Klasse stellt ein allgemeines Zugriffsrecht fuer eine Eigenschaft
+ * ({@link Property Property}) dar. Dieses dient dazu, nur bestimmten Zugriff auf
+ * das jeweilige Objekt zu erlauben (z.B. nur Lesezugriff). Aus diesem
+ * Grund gibt es auch keine <code>getObject()</code>-Methode, ueber welche ein
+ * direkter (kompletter) Zugriff auf das Objekt moeglich waere.<br>
+ * Welche und vor allem wie viele (gleichzeitige) Zugriffsrechte ein Objekt
+ * erlaubt, wird von der jeweiligen Objektklasse selbst definiert.<br>
+ * Ein Zugriffsrecht kann vom Besitzer zurueckgegeben werden
+ * ({@link #release() release()}) oder auch von einem globaler Rechteverwalter
+ * entzogen werden ({@link #dispose() dispose()}).
+ * In beiden Faellen sollen saemtliche folgenden Zugriffsmethoden auf
+ * das Objekt mit Exceptions abgebrochen werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class Access {
+  /** Speichert den Besitzer des Zugriffsrechts */
+  protected Object     owner  = null;
+  /** Speichert das Objekt, ueber das das Zugriffsrecht ausgesprochen wurde. */
+  protected Accessible object = null;
+  /** Gibt an, ob das Recht noch gueltig ist */
+  protected boolean    disposed = false;
+  /** Diese Konstante steht fuer unbegrenzte Anzahl an Zugriffen auf das Recht */
+  public static final int UNLIMITED_ACCESSTIMES = -1;
+  /** Speichert, wie of ein Objekt das Zugrifsrecht bereits benutzt hat. */
+  protected int        accessTimes = 0;
+  /** Speichert, wie of ein Objekt das Zugrifsrecht nutzen darf. */
+  protected int        maxAccessTimes = UNLIMITED_ACCESSTIMES;
+
+  /**
+   * Erzeugt ein neues Zugriffsrecht.
+   * @param object Objekt auf das sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @param maxAccessTimes Anzahl an (Methoden-)Zugriffen, die der Rechtebesitzer
+   *                       taetigen darf, bevor das Recht automatisch entzogen wird
+   */
+  public Access(Accessible object, Object owner, int maxAccessTimes) {
+    this.object         = object;
+    this.owner          = owner;
+    this.maxAccessTimes = maxAccessTimes;
+    this.object.applyAccess(this);
+  }
+
+  /**
+   * Erzeugt ein neues Zugriffsrecht. Der Besitzer kann dieses Recht beliebig
+   * oft einsetzen.
+   * @param object Objekt auf das sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   */
+  public Access(Accessible object, Object owner) {
+    this(object,owner,UNLIMITED_ACCESSTIMES);
+  }
+
+  /**
+   * Liefert den Besitzer des Rechts.
+   */
+  public Object getOwner() {
+    return owner;
+  }
+
+  /**
+   * Prueft, ob das Recht noch gueltig ist.
+   */
+  public boolean isDisposed() {
+    return disposed;
+  }
+
+  /**
+   * Wirft eine <code>AccessViolationException</code>, wenn das Zugriffsrecht
+   * nicht mehr gueltig ist. Andernfalls passiert nichts.
+   */
+  public void checkDisposed() {
+    if ( this.isDisposed() )
+      throw new AccessViolationException(this,this.getClass().getName()+" is disposed!!");
+  }
+
+  /**
+   * Macht das Recht ungueltig. Entspricht {@link #release()}.
+   */
+  public void dispose() {
+    release();
+  }
+
+  /**
+   * Gibt das Recht auf. Das betroffene Objekt wird informiert und
+   * sollte entsprechend reagieren.<br>
+   * Alle Referenzen auf das betroffene Objekt und auf den Besitzer des
+   * Rechts werden entfernt!! {@link #getOwner()} liefert im folgenden also
+   * <code>null</code>.
+   * @see schmitzm.data.property.Accessible#releaseAccess(Access)
+   */
+  public void release() {
+    this.disposed = true;
+    if (object != null)
+      object.releaseAccess(this);
+    this.owner  = null;
+    this.object = null;
+  }
+
+  /**
+   * Vergleicht das Zugriffsrecht mit einem anderen. Zwei Rechte sind gleich,
+   * wenn:
+   * <ol>
+   * <li>Die Klassen identisch sind.</li>
+   * <li>Die Rechtebesitzer identisch sind.</li>
+   * <li>Die Rechte sich auf dasselbe Objekt beziehen.</li>
+   * </ol>
+   * @param access Ein Zugriffsrecht
+   */
+  public boolean equals(Object access) {
+    return access != null &&
+           access.getClass().equals( this.getClass() ) &&
+           ((Access)access).owner  == this.owner &&
+           ((Access)access).object == this.object;
+  }
+
+  /**
+   * Liefert die Anzahl, die der Besitzer berechtigt ist, das Zugriffsrecht
+   * (ueber eine Methode) einzusetzen.
+   * @see #UNLIMITED_ACCESSTIMES
+   */
+  public int getMaxAccessTimes() {
+    return this.maxAccessTimes;
+  }
+
+  /**
+   * Liefert die Anzahl an Zugriffen, die der Besitzer bereits (ueber eine
+   * Methode) getaetigt hat.
+   */
+  public int getAccessTimes() {
+    return this.accessTimes;
+  }
+
+  /**
+   * Erhoeht die Anzahl an (Methoden-)Zugriffen auf das Recht und prueft, ob die
+   * maximal erlaubte Anzahl erreicht/ueberschritten ist.
+   * Ist dies der Fall wird das Zugriffsrecht ungueltig gemacht.
+   * @see #dispose()
+   */
+  protected void incAndCheckMaxAccessTimesReached() {
+    if ( !isDisposed() ) {
+      accessTimes++;
+      if (getMaxAccessTimes() != UNLIMITED_ACCESSTIMES &&
+          getAccessTimes() >= getMaxAccessTimes())
+        dispose();
+    }
+  }
+}

Added: trunk/src/schmitzm/data/property/AccessViolationException.java
===================================================================
--- trunk/src/schmitzm/data/property/AccessViolationException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/AccessViolationException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,50 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Exception wird immer dann geworfen, wenn ein Fehler mit Zugriffsrechten
+ * auftritt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class AccessViolationException extends RuntimeException {
+  private Access access = null;
+
+  /**
+   * Erzeugt einen neuen Fehler.
+   * @param a       betroffener Zugriff
+   * @param message Fehlermeldung
+   */
+  public AccessViolationException(Access a, String message) {
+    super(message);
+    // original : this.access = access;
+    this.access = a; // changed by SK
+
+  }
+
+  /**
+   * Erzeugt einen neuen Fehler.
+   * @param a       betroffener Zugriff
+   */
+  public AccessViolationException(Access a) {
+    this(a,"");
+  }
+
+  /**
+   * Liefert das Zugriffsrecht, bei dem der Fehler aufgetreten ist.
+   */
+  public Access getAccess() {
+    return access;
+  }
+
+}

Added: trunk/src/schmitzm/data/property/Accessible.java
===================================================================
--- trunk/src/schmitzm/data/property/Accessible.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/Accessible.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,63 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Dieses Interface ist von allen Objektklassen zu implementieren, fuer die
+ * besondere Zugriffsrechte erteilt werden sollen.
+ * @see schmitzm.data.property.Access
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface Accessible {
+  /**
+   * Wird aufgerufen, wenn ein Zugriffsrecht von einem Objekt wieder freigegen
+   * wird oder das Zugriffsrecht entzogen wurde.
+   * @param access erzeugtes Zugriffsrecht
+   */
+  public void releaseAccess(Access access);
+
+  /**
+   * Wird aufgerufen, wenn ein Zugriffsrecht fuer das Objekt erzeugt wird.
+   * @param access erzeugtes Zugriffsrecht
+   */
+  public void applyAccess(Access access);
+
+  /**
+   * Entzieht saemtlichen Zugriffsrechten, die aktuell fuer das Objekt
+   * verteilt sind, die Gueltigkeit.
+   */
+  public void disposeAllAccess();
+
+  /**
+   * Liefert die Anzahl an Zugriffsrechten einer bestimmten Art, die aktuell
+   * fuer das Objekt verteilt sind.
+   */
+  public int getAccessCount(Class c);
+
+  /**
+   * Prueft, ob aktuell Zugriffsrechte einer bestimmten Art
+   * auf das Objekt verteilt sind.
+   */
+  public boolean hasAccess(Class c);
+
+  /**
+   * Liefert die Anzahl an Zugriffsrechten, die aktuell fuer das Objekt verteilt
+   * sind.
+   */
+  public int getAccessCount();
+
+  /**
+   * Prueft, ob aktuell Zugriffsrechte auf das Objekt verteilt sind.
+   */
+  public boolean hasAccess();
+}

Added: trunk/src/schmitzm/data/property/DynamicProperties.java
===================================================================
--- trunk/src/schmitzm/data/property/DynamicProperties.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/DynamicProperties.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,69 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.event.ObjectTraceable;
+
+/**
+ * Diese Interface sollten alle Objekte implementieren, die sich <b>dynamisch</b>
+ * aus einzelnen Eigenschaften ({@link Property Properties}) zusammensetzen.
+ * Die Methoden des Interface liefern sowohl den Zugriff auf die aktuellen Eigenschaften
+ * des Objekts, ermoeglichen aber auch das dynamische Hinzufuegen oder Entfernen von
+ * Eigenschaften.<br>
+ * Zu beachten ist, dass es der jeweiligen Implementierung ueberlassen ist,
+ * ob beim Entfernen einer Eigenschaft auch die komplette Eigenschaft (mittels
+ * {@link Property#dispose()}) geschlossen wird. Je nach Implementierung muss
+ * dies manuell geschehen. Deshalb liefern die remove-Methoden eine Referenz
+ * auf die entfernte Eigenschaft zurueck.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface DynamicProperties extends Properties, ObjectTraceable {
+  /**
+   * Fuegt dem Objekt eine Eigenschaft hinzu. Das Objekt sollte darauf
+   * achten, dass es noch keine Eigenschaft mit diesem Namen besitzt,
+   * damit die Eindeutigkeit des Names gewaehrleistet ist.
+   * @param prop Eigenschaft
+   * @exception schmitzm.data.property.PropertyException falls das
+   *            Objekt bereits eine Eigenschaft mit dem Namen der neuen
+   *            Eigenschaft besitzt
+   */
+  public void addProperty(Property prop);
+
+  /**
+   * Entfernt eine Eigenschaft von dem Objekt.
+   * @param name Name der Eigenschaft
+   * @return Die entferne Eigenschaft oder <code>null</code> falls das
+   *         Objekt keine Eigenschaft mit dem Namen besitzt
+   */
+  public Property removeProperty(String name);
+
+  /**
+   * Entfernt eine Eigenschaft von dem Objekt. Falls das Objekt diese
+   * Eigenschaft nicht besitzt, passiert nichts.
+   * @param prop zu entfernende Eigenschaft
+   * @return Die entferne Eigenschaft oder <code>null</code> falls das Objekt
+   *         die Eigenschaft nicht besitzt
+   */
+  public Property removeProperty(Property prop);
+
+  /**
+   * Entfernt Eigenschaften von dem Objekt. Falls das Objekt eine dieser
+   * Eigenschaften nicht besitzt, passiert fuer die entsprechende Eigenschaft
+   * nichts.
+   * @param prop zu entfernende Eigenschaft
+   * @return Die entfernten Eigenschaften (kann eine leere Eigenschaftsmenge
+   *         sein, wenn das Objekt keine der uebergebenen Eigenschaften
+   *         besitzt.
+   */
+  public Properties removeProperties(Properties prop);
+}

Added: trunk/src/schmitzm/data/property/ListProperty.java
===================================================================
--- trunk/src/schmitzm/data/property/ListProperty.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ListProperty.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,413 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import java.util.Vector;
+import schmitzm.data.ObjectStructure;
+import schmitzm.data.event.Invoker;
+import schmitzm.data.event.ObjectChangeEvent;
+
+/**
+ * Diese Klasse stellt eine Listen-Eigenschaft dar, die Objekte eines bestimmten
+ * Typs aufnehmen kann. Die Groesse der Liste steht a priori noch nicht fest.
+ * Es koennen beliebig Objekte hingefügt oder entfernt werden.<br>
+ * Intern wird die Liste als <code>java.util.Vector</code> organisiert.<br>
+ * Der Zugriff auf die Liste kann durch Zugriffsrechte kontrolliert werden.
+ * Ein Zugriff (lesend oder schreibend) ist nur ueber ein entsprechendes
+ * Zugriffsrecht ({@link Access}) moeglich. Standardmaessig ist unbegrenzter
+ * Zugriff eingestellt. Durch Angabe von {@link ValuePropertyAccessParameters}
+ * kann jedoch benutzerdefiniert festgelegt werden, wie viele Objekte gleichzeitig
+ * Lese- und/oder Schreibzugriff erlangen duerfen.
+ * @see #getReadAccess(Object)
+ * @see #getWriteAccess(Object)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ListProperty extends ValueProperty {
+
+  /**
+   * Speichert die Listen-Elemente.
+   */
+  protected Vector elements = new Vector();
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft.
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ der Elemente, die die <code>ListProperty</code>
+   *        aufnehmen kann
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ListProperty(String name, ValuePropertyType elementType, ValuePropertyAccessParameters params) {
+    super( name, elementType, params );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ der Elemente, die die <code>ListProperty</code>
+   *        aufnehmen kann
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ListProperty(String name, ValuePropertyType elementType) {
+    this( name, elementType, ValuePropertyAccessParameters.UNLIMITED_ACCESS );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. int.class).
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ der Elemente, die die <code>ListProperty</code>
+   *        aufnehmen kann
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ListProperty(String name, Class elementType, ValuePropertyAccessParameters params) {
+    super( name, elementType, params );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. int.class). Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ der Elemente, die die <code>ListProperty</code>
+   *        aufnehmen kann
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ListProperty(String name, Class elementType) {
+    this( name, elementType, ValuePropertyAccessParameters.UNLIMITED_ACCESS );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft.
+   * @param name Bezeichnung der Eigenschaft
+   * @param sample Bestimmt die Element-Struktur, die in der <code>ListProperty</code>
+   *               gespeichert werden koennen
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ListProperty(String name, ObjectStructure sample, ValuePropertyAccessParameters params) {
+    this( name, new ValuePropertyType(sample), params );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param sample Bestimmt die Element-Struktur, die in der <code>ListProperty</code>
+   *               gespeichert werden koennen
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ListProperty(String name, ObjectStructure sample) {
+    this( name, sample, ValuePropertyAccessParameters.UNLIMITED_ACCESS );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft.
+   * @param name Bezeichnung der Eigenschaft
+   * @param sampleData Bestimmt die Element-Struktur, die in der <code>ListProperty</code>
+   *                   gespeichert werden koennen (das Objekt wird <b>nicht</b>
+   *                   in die Liste aufgenommen!!)
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ListProperty(String name, Object sampleData, ValuePropertyAccessParameters params) {
+    this( name, ValuePropertyType.createValuePropertyType(sampleData), params );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param sampleData Bestimmt die Element-Struktur, die in der <code>ListProperty</code>
+   *                   gespeichert werden koennen (das Objekt wird <b>nicht</b>
+   *                   in die Liste aufgenommen!!)
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ListProperty(String name, Object sampleData) {
+    this( name, sampleData, ValuePropertyAccessParameters.UNLIMITED_ACCESS );
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Als Elemente kann diese saemtliche Objekte
+   * aufnehmen, die von <code>java.lang.Object</code> abgeleitet sind
+   * (also <u>keine</u> Build-in-Types!!).
+   * @param name Bezeichnung der Eigenschaft
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ListProperty(String name, ValuePropertyAccessParameters params) {
+    this(name,Object.class,params);
+  }
+
+  /**
+   * Erzeugt eine Listen-Eigenschaft. Als Elemente kann diese saemtliche Objekte
+   * aufnehmen, die von <code>java.lang.Object</code> abgeleitet sind
+   * (also <u>keine</u> Build-in-Types!!). Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ListProperty(String name) {
+    this(name,Object.class);
+  }
+
+  /**
+   * Zerstoert die Property und die Property-Werte. Handelt es sich bei den
+   * Werten der Liste wiederum Propertys, werden auch deren
+   * <code>dispose()</code>-Methoden aufgerufen.
+   */
+  public void dispose() {
+    for (int i=0; elements!=null && !elements.isEmpty(); i++) {
+      Object o = elements.firstElement();
+      this.removeValue(0);
+      if ( o instanceof Property )
+        ((Property)o).dispose();
+    }
+    super.dispose();
+  }
+
+  /**
+   * Liefert eine neue, leerer <code>ListProperty</code> mit identischem
+   * Namen, Typ und gleichen Zugriffsparemetern.
+   */
+  public ListProperty cloneStructure() {
+    return new ListProperty( getName(), getPropertyType(), getAccessParameters() );
+  }
+
+  /**
+   * Liefert ein Leserecht auf die Liste.
+   * Wie viele Leserechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.<br>
+   * Neben den "normalen" Lesezugriffen (getter auf bestimmtes Objekt) muessen
+   * fuer Listen weitere Zugriffsmethoden bereitgestellt werden. Deshalb liefert
+   * diese Methode kein {@link PropertyReadAccess}, sondern ein
+   * {@link ListPropertyReadAccess}.
+   * @param owner Objekt, welches das Recht beantragt
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public ListPropertyReadAccess getReadAccess(Object owner) {
+    return new ListPropertyReadAccess(this,owner);
+  }
+
+  /**
+   * Liefert ein Schreibrecht auf die Property.
+   * Wie viele Schreibrechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.<br>
+   * Neben den "normalen" Schreibzugriffen (setter auf bestimmtes Objekt) muessen
+   * fuer Listen weitere Zugriffsmethoden bereitgestellt werden. Deshalb liefert
+   * diese Methode kein {@link PropertyWriteAccess}, sondern ein
+   * {@link ListPropertyWriteAccess}.
+   * @param owner Objekt, welches das Recht beantragt
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public ListPropertyWriteAccess getWriteAccess(Object owner) {
+    return new ListPropertyWriteAccess(this,owner);
+  }
+
+  /**
+   * Liefert ein Leserecht auf die Liste, welches nach einmaligem
+   * Zugriff automatisch ungueltig wird.<br>
+   * Wie viele Leserechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.<br>
+   * Neben den "normalen" Lesezugriffen (getter auf bestimmtes Objekt) muessen
+   * fuer Listen weitere Zugriffsmethoden bereitgestellt werden. Deshalb liefert
+   * diese Methode kein {@link PropertyReadAccess}, sondern ein
+   * {@link ListPropertyReadAccess}.
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public ListPropertyReadAccess getOneTimeReadAccess() {
+    return new ListPropertyReadAccess(this,this,1);
+  }
+
+  /**
+   * Liefert ein Schreibrecht auf die Property, welches nach einmaligem
+   * Zugriff automatisch ungueltig wird.<br>
+   * Wie viele Schreibrechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.<br>
+   * Neben den "normalen" Schreibzugriffen (setter auf bestimmtes Objekt) muessen
+   * fuer Listen weitere Zugriffsmethoden bereitgestellt werden. Deshalb liefert
+   * diese Methode kein {@link PropertyWriteAccess}, sondern ein
+   * {@link ListPropertyWriteAccess}.
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public ListPropertyWriteAccess getOneTimeWriteAccess() {
+    return new ListPropertyWriteAccess(this,this,1);
+  }
+
+  /**
+   * Liefert die Anzahl an Zugriffsrechten einer bestimmten Art, die aktuell
+   * fuer das Objekt verteilt sind.<br>
+   * Muss {@link ValueProperty#getAccessCount(Class)} ueberschreiben, da dieser
+   * Klasse Instanzen von {@link ListPropertyReadAccess} und {@link ListPropertyWriteAccess}
+   * verteilt.
+   */
+  public int getAccessCount(Class c) {
+    int accessCount = 0;
+    // Wird als Typ ListPropertyReadAccess (ListPropertyReadAccess) angegeben,
+    // wuerde die super-Methode 0 zurueckgeben, da diese kein Obertyp von
+    // PropertyReadAccess (PropertyReadAccess) ist!!
+    if ( c==null || c.isAssignableFrom(ListPropertyReadAccess.class) )
+      accessCount += super.getAccessCount(PropertyReadAccess.class);
+    if ( c==null || c.isAssignableFrom(ListPropertyWriteAccess.class) )
+      accessCount += super.getAccessCount(PropertyWriteAccess.class);
+    return accessCount;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ValueProperty
+  ///////////////////////////////////////////////////////////////////
+
+  /**
+   * Liefert ein Element der Liste. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * <b>Da eine Listen-Eigenschaft 1-dim. ist, muss als
+   * <code>coords</code>-Parameter genau 1 Wert angegeben werden!!</b>
+   * @param coords Listen-Index
+   * @exception java.lang.UnsupportedOperationException falls nicht genau ein
+   *            <code>coords</code>-Parameter angegeben wird
+   */
+  protected Object getValue(int... coords) {
+    if (coords.length != 1)
+      throw new UnsupportedOperationException("A ListProperty contains values in one dimension. Thus one and only one parameter are allowed for getValue(..).");
+    return elements.elementAt(coords[0]);
+  }
+
+  /**
+   * Belegt ein Element der Liste. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * <b>Da eine Listen-Eigenschaft 1-dim. ist, muss als
+   * <code>coords</code>-Parameter genau 1 Wert angegeben werden!!</b>
+   * @param value  neuer Wert fuer das Listen-Element
+   * @param coords Listen-Index
+   * @exception java.lang.UnsupportedOperationException falls nicht genau ein
+   *            <code>coords</code>-Parameter angegeben wird
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   */
+  protected void setValue(Object value, int... coords) {
+    if (coords.length != 1)
+      throw new UnsupportedOperationException("A ListProperty contains values in one dimension. Thus one and only one parameter are allowed for getValue(..).");
+    if ( value!=null && !this.isValid( value.getClass() ) )
+      throw new ClassCastException(value.getClass().getName().concat(" can not be stored in a ListProperty[").concat(this.getType().getName()).concat("]"));
+
+    // Die set-Methode darf auch fuer einen ueber die Liste
+    // hinaus reichenden Index aufgerufen. In diesem Fall wird
+    // einfach ein Objekt hinzugefuegt.
+    if ( coords[0] == getCount() ) {
+      insertValue(value,coords[0]);
+    } else {
+      // altes Objekt merken fuer Event
+      Object oldValue = getValue(coords[0]);
+      elements.setElementAt(value, coords[0]);
+
+      this.fireEvent(new ObjectChangeEvent(
+          new Invoker(this),
+          oldValue,
+          value
+      ));
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Neue Listen-Funktionen
+  ///////////////////////////////////////////////////////////////////
+
+  /**
+   * Liefert den Listen-Index des ersten Vorkommens eines Elements.
+   * @param value Listen-Element
+   * @return -1 falls das Element nicht in der Liste vorhanden ist.
+   */
+  protected int indexOf(Object value) {
+    return elements.indexOf(value);
+  }
+
+  /**
+   * Liefert die aktuelle Anzahl an Elementen, die in der Liste gespeichert
+   * sind.
+   */
+  protected int getCount() {
+    return elements.size();
+  }
+
+  /**
+   * Belegt ein Element der Liste. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value  neuer Wert fuer das Listen-Element
+   * @param index  Listen-Index fuer das neue Element (alle bestehenden Elemente
+   *               ab (einschliesslich) dieser Position werden nach hinten
+   *               verschoben)
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   */
+  protected void insertValue(Object value, int index) {
+    if (!isValid(value.getClass()))
+      throw new ClassCastException(value.getClass().getName().concat(
+          " can not be stored in a ListProperty[").concat(getType().getName()).concat("]"));
+
+    elements.insertElementAt(value, index);
+
+    fireEvent(new ObjectChangeEvent(
+        new Invoker(this),
+        null,
+        value
+    ));
+  }
+
+  /**
+   * Entfernt ein Element aus der Liste.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param index Listen-Index des zu loeschenden Elements
+   */
+  protected void removeValue(int index) {
+    fireEvent(new ObjectChangeEvent(
+        new Invoker(this),
+        getValue(index),
+        null
+    ));
+    elements.remove(index);
+  }
+
+  /**
+   * Entfernt alle Instanzen des angegebenen Objekts aus der Liste (sofern
+   * es vorhanden ist).<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value das zu entfernende Objekt
+   * @return <code>false</code> falls dass Objekt nicht in der Liste vorhanden war
+   */
+  protected boolean removeValue(Object value) {
+    int count = 0;
+    for (int i = 0; (i = this.indexOf(value)) >= 0; count++)
+      removeValue(i);
+    return count > 0;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ObjectStructure
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert <code>true</code>, da eine Listen-Eigenschaft immer
+   * aus mehreren Werten besteht.
+   */
+  public boolean containsMultipleValues() {
+    return true;
+  }
+
+}

Added: trunk/src/schmitzm/data/property/ListPropertyReadAccess.java
===================================================================
--- trunk/src/schmitzm/data/property/ListPropertyReadAccess.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ListPropertyReadAccess.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,96 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Klasse stellt ein Recht auf Lesezugriff fuer eine
+ * {@link ListProperty} dar. Da Listen dynamisch aufgebaut sind, erweitert sie
+ * hierzu den {@link PropertyReadAccess} um <code>getCount</code>-
+ * und <code>indexOf</code>-Methoden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ListPropertyReadAccess extends PropertyReadAccess {
+  /**
+   * Erzeugt ein neues Lesezugriffsrecht fuer Listen. Die Anzahl an Zugriffen
+   * fuer den Besitzer ist unbegrenzt.
+   * @param object Instanz von <code>ListProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @see schmitzm.data.property.ListProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ListProperty</code> ist
+   */
+  public ListPropertyReadAccess(Accessible object, Object owner) {
+    this(object, owner, UNLIMITED_ACCESSTIMES);
+  }
+
+  /**
+   * Erzeugt ein neues Lesezugriffsrecht fuer Listen.
+   * @param object Instanz von <code>ListProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @param maxAccessTimes Anzahl an (Methoden-)Zugriffen, die der Rechtebesitzer
+   *                       taetigen darf, bevor das Recht automatisch entzogen wird
+   * @see schmitzm.data.property.ListProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ListProperty</code> ist
+   */
+  public ListPropertyReadAccess(Accessible object, Object owner, int maxAccessTimes) {
+    super(object, owner, maxAccessTimes);
+    if (! (object instanceof ListProperty))
+      throw new AccessViolationException(this,this.getClass().getName() +" can only be instantiated for ListProperties");
+  }
+
+  /**
+   * Liefert die aktuelle Anzahl an Elementen, die in der Liste gespeichert
+   * sind.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   */
+  public int getCount() {
+    checkDisposed();
+    int ret = ((ListProperty)object).getCount();
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Checkt, ob die Liste leer ist.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   */
+  public boolean isEmpty() {
+    return getCount() == 0;
+  }
+
+  /**
+   * Liefert den Listen-Index des ersten Vorkommens eines Elements.
+   * @param value Listen-Element
+   * @return -1 falls das Element nicht in der Liste vorhanden ist.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   */
+  public int indexOf(Object value) {
+    checkDisposed();
+    int ret = ((ListProperty)object).indexOf(value);
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Checkt, ob ein Objekt in der Liste vorhanden ist.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   */
+  public boolean contains(Object value) {
+    return indexOf(value) >= 0;
+  }
+}

Added: trunk/src/schmitzm/data/property/ListPropertyWriteAccess.java
===================================================================
--- trunk/src/schmitzm/data/property/ListPropertyWriteAccess.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ListPropertyWriteAccess.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,201 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.event.Invoker;
+import schmitzm.data.event.ObjectChangeEvent;
+
+/**
+ * Diese Klasse stellt ein Recht auf Schreibzugriff fuer eine
+ * {@link ListProperty} dar. Da Listen dynamisch aufgebaut sind, erweitert sie
+ * hierzu den {@link PropertyWriteAccess} um <code>insert</code>-
+ * und <code>remove</code>-Methoden.<br>
+ * <b>Bemerkung:</b><br>
+ * Um auch den kompletten Lesezugriff zu ermoeglichen, werden zusaetzlich auch
+ * noch einmal alle (neuen) Methoden von {@link ListPropertyReadAccess}
+ * implementiert. Dies ist erforderlich, da diese Klasse von
+ * {@link PropertyWriteAccess} abgeleitet werden muss und doppelte Ableitung
+ * leider nicht moeglich ist.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ListPropertyWriteAccess extends PropertyWriteAccess {
+
+  /**
+   * Erzeugt ein neues Schreibzugriffsrecht fuer Listen. Die Anzahl an Zugriffen
+   * fuer den Besitzer ist unbegrenzt.
+   * @param object Instanz von <code>ListProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @see schmitzm.data.property.ListProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ListProperty</code> ist
+   */
+  public ListPropertyWriteAccess(Accessible object, Object owner) {
+    this(object, owner,UNLIMITED_ACCESSTIMES);
+  }
+
+  /**
+   * Erzeugt ein neues Schreibzugriffsrecht fuer Listen.
+   * @param object Instanz von <code>ListProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @param maxAccessTimes Anzahl an (Methoden-)Zugriffen, die der Rechtebesitzer
+   *                       taetigen darf, bevor das Recht automatisch entzogen wird
+   * @see schmitzm.data.property.ListProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ListProperty</code> ist
+   */
+  public ListPropertyWriteAccess(Accessible object, Object owner, int maxAccessTimes) {
+    super(object, owner, maxAccessTimes);
+    if (! (object instanceof ListProperty))
+      throw new AccessViolationException(this,
+          this.getClass().getName() + " can only be instantiated for ListProperties");
+  }
+
+  /**
+   * Fuegt der Liste ein Element (am Ende) hinzu. Kann (seit JDK1.5.0) auch
+   * genutzt werden, wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value  neuer Wert fuer die Liste
+   * @exception java.lang.UnsupportedOperationException falls nicht genau ein
+   *            <code>coords</code>-Parameter angegeben wird
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public void addValue(Object value) {
+// Kann so nicht gemacht werden, da bei OneTimeWriteAccess das Recht
+// bei Aufruf der this.getCount()-Methode bereits "verbraucht" wird!!
+// Das anschliessende this.insertValue(..) fuehrt dann zu einer
+// AccessViolationException, da kein Recht mehr besteht!! :-)
+// Loesung: >> direkt auf getCount-Methode des Objekts zugreifen!
+//  insertValue(value, getCount());
+    insertValue(value,((ListProperty)object).getCount());
+  }
+
+  /**
+   * Belegt ein Element der Liste. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value  neuer Wert fuer das Listen-Element
+   * @param index  Listen-Index fuer das neue Element (alle bestehenden Elemente
+   *               ab (einschliesslich) dieser Position werden nach hinten
+   *               verschoben)
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public void insertValue(Object value, int index) {
+    checkDisposed();
+    ((ListProperty)this.object).insertValue(value,index);
+    incAndCheckMaxAccessTimesReached();
+  }
+
+  /**
+   * Entfernt ein Element aus der Liste.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param index Listen-Index des zu loeschenden Elements
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public void removeValue(int index) {
+    checkDisposed();
+    ((ListProperty)this.object).removeValue(index);
+    incAndCheckMaxAccessTimesReached();
+  }
+
+  /**
+   * Entfernt alle Instanzen des angegebenen Objekts aus der Liste (sofern
+   * es vorhanden ist).<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value das zu entfernende Objekt
+   * @return <code>false</code> falls dass Objekt nicht in der Liste vorhanden war
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public boolean removeValue(Object value) {
+    checkDisposed();
+    boolean ret = ((ListProperty)this.object).removeValue(value);
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Entfernt alle Objekte aus der Liste.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer jedes geloeschte
+   * Element der Liste.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public void removeAll() {
+    checkDisposed();
+    while ( ((ListProperty)this.object).getCount() > 0 )
+      ((ListProperty)this.object).removeValue(0);
+    incAndCheckMaxAccessTimesReached();
+  }
+
+  ////////////////////////////////////////////////////////////////////////
+  // Folgende Methoden sind identisch zur ListPropertyReadAcces.
+  // Muessen leider doppelt implememtiert werden, da ListPropertyWriteAccess
+  // bereits von PropertyWriteAccess abgeleitet werden muss und doppelte
+  // Ableitung leider nicht moeglich ist.
+  ////////////////////////////////////////////////////////////////////////
+  /**
+   * Liefert die aktuelle Anzahl an Elementen, die in der Liste gespeichert
+   * sind.<br>
+   * Identisch zu {@link ListPropertyReadAccess#getCount()}.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public int getCount() {
+    checkDisposed();
+    int ret = ((ListProperty)object).getCount();
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Checkt, ob die Liste leer ist.<br>
+   * Identisch zu {@link ListPropertyReadAccess#isEmpty()}.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public boolean isEmpty() {
+    return getCount() == 0;
+  }
+
+  /**
+   * Liefert den Listen-Index des ersten Vorkommens eines Elements.<br>
+   * Identisch zu {@link ListPropertyReadAccess#indexOf(Object)}.
+   * @param value Listen-Element
+   * @return -1 falls das Element nicht in der Liste vorhanden ist.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public int indexOf(Object value) {
+    checkDisposed();
+    int ret = ((ListProperty)object).indexOf(value);
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Checkt, ob ein Objekt in der Liste vorhanden ist.<br>
+   * Identisch zu {@link ListPropertyReadAccess#contains(Object)}.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public boolean contains(Object value) {
+    return indexOf(value) >= 0;
+  }
+}

Added: trunk/src/schmitzm/data/property/MatrixProperty.java
===================================================================
--- trunk/src/schmitzm/data/property/MatrixProperty.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/MatrixProperty.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,327 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.lang.MultiDimArray;
+import schmitzm.data.event.Invoker;
+import schmitzm.data.event.ObjectChangeEvent;
+import schmitzm.data.ObjectStructure;
+
+/**
+ * Diese Klasse stellt eine Matrix-Eigenschaft dar, die Objekte eines bestimmten
+ * Typs aufnehmen kann. Die Dimension und Groesse der Matrix wird bei der
+ * Instanziierung festgelegt und kann nicht geaendert werden.<br>
+ * Der Zugriff auf die Property kann durch Zugriffsrechte kontrolliert werden.
+ * Ein Zugriff (lesend oder schreibend) ist nur ueber ein entsprechendes
+ * Zugriffsrecht ({@link Access}) moeglich. Standardmaessig ist unbegrenzter
+ * Zugriff eingestellt. Durch Angabe von {@link ValuePropertyAccessParameters}
+ * kann jedoch benutzerdefiniert festgelegt werden, wie viele Objekte gleichzeitig
+ * Lese- und/oder Schreibzugriff erlangen duerfen.
+ * @see schmitzm.data.property.ValueProperty#getReadAccess(Object)
+ * @see schmitzm.data.property.ValueProperty#getWriteAccess(Object)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MatrixProperty extends ValueProperty {
+  // Groesse der Matrix merken (aus Effizienzgruenden!)
+  private int[] size = null;
+
+  /**
+   * Speichert die Matrix als <code>java.lang.reflect.Array</code>.
+   * @see java.lang.reflect.Array
+   */
+  protected Object matrix = null;
+
+  /**
+   * Speichert die Groessenangabe der Matrix als <code>String</code> der Form
+   * <code>"(d1,d2,d3,...)</code>.
+   */
+  protected String sizeDesc = "";
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param elementType Matrix kann nur Elemente dieses Daten-Typs aufnehmen
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public MatrixProperty(String name, int[] size, ValuePropertyType elementType, ValuePropertyAccessParameters params) {
+    super(name, elementType, params);
+    this.size      = size.clone();
+    this.matrix    = MultiDimArray.newInstance(elementType.getType(),size);
+    // Groessenbeschreibung zusammensetzen
+    sizeDesc = "(";
+    for (int d=0; d<size.length; d++) {
+      if ( d > 0 ) sizeDesc+=",";
+      sizeDesc+=size[d];
+    }
+    sizeDesc+=")";
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben.
+   * Der Zugriff auf die Eigenschaft ist uneingeschraengt (beliebig viele
+   * Lese- und beliebig viele Schreibrechte).
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param elementType Matrix kann nur Elemente dieses Daten-Typs aufnehmen
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public MatrixProperty(String name, int[] size, ValuePropertyType elementType) {
+    this(name, size, elementType, ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param elementType Matrix kann nur Elemente dieses Daten-Typs aufnehmen
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public MatrixProperty(String name, int[] size, Class elementType, ValuePropertyAccessParameters params) {
+    this(name,size,new ValuePropertyType(elementType),params);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben.
+   * Der Zugriff auf die Eigenschaft ist uneingeschraengt (beliebig viele
+   * Lese- und beliebig viele Schreibrechte).
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param elementType Matrix kann nur Elemente dieses Daten-Typs aufnehmen
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public MatrixProperty(String name, int[] size, Class elementType) {
+    this(name, size, elementType, ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param sample Matrix kann nur Elemente dieser Struktur aufnehmen
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public MatrixProperty(String name, int[] size, ObjectStructure sample, ValuePropertyAccessParameters params) {
+    this(name,size,new ValuePropertyType(sample),params);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben.
+   * Der Zugriff auf die Eigenschaft ist uneingeschraengt (beliebig viele
+   * Lese- und beliebig viele Schreibrechte).
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param sample Matrix kann nur Elemente dieser Struktur aufnehmen
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public MatrixProperty(String name, int[] size, ObjectStructure sample) {
+    this(name, size, sample, ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param sampleData Bestimmt die Element-Struktur, die in der <code>MatrixProperty</code>
+   *                   gespeichert werden kann (das Objekt wird <b>nicht</b>
+   *                   in die Matrix aufgenommen!!)
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public MatrixProperty(String name, int[] size, Object sampleData, ValuePropertyAccessParameters params) {
+    this(name,size,ValuePropertyType.createValuePropertyType(sampleData),params);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Typ kann auch ein Build-in-Type
+   * angegeben werden (z.B. int.class). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben.
+   * Der Zugriff auf die Eigenschaft ist uneingeschraengt (beliebig viele
+   * Lese- und beliebig viele Schreibrechte).
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param sampleData Bestimmt die Element-Struktur, die in der <code>MatrixProperty</code>
+   *                   gespeichert werden kann (das Objekt wird <b>nicht</b>
+   *                   in die Matrix aufgenommen!!)
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public MatrixProperty(String name, int[] size, Object sampleData) {
+    this(name, size, sampleData, ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Elemente kann diese saemtliche Objekte
+   * aufnehmen, die von <code>java.lang.Object</code> abgeleitet sind
+   * (also <u>keine</u> Build-in-Types!!). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+
+  public MatrixProperty(String name, int[] size, ValuePropertyAccessParameters params) {
+    this(name, size, Object.class,params);
+  }
+
+  /**
+   * Erzeugt eine Matrix-Eigenschaft. Als Elemente kann diese saemtliche Objekte
+   * aufnehmen, die von <code>java.lang.Object</code> abgeleitet sind
+   * (also <u>keine</u> Build-in-Types!!). Die Dimension der Matrix ist implizit
+   * durch die Größe des <code>size</code>-Arrays gegeben.
+   * Der Zugriff auf die Eigenschaft ist uneingeschraengt (beliebig viele
+   * Lese- und beliebig viele Schreibrechte).
+   * @param name Name der Eigenschaft
+   * @param size Groesse der Matrix in jeder Dimension
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+
+  public MatrixProperty(String name, int[] size) {
+    this(name, size, Object.class, ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Zerstoert die Matrix, indem die auf <code>null</code> gesetzt wird.
+   */
+  public void dispose() {
+    matrix = null;
+    super.dispose();
+  }
+
+  /**
+   * Liefert eine neue, leerer <code>MatrixProperty</code> mit identischem
+   * Namen, Typ, geleicher Groesse und gleichen Zugriffsparemetern.
+   */
+  public MatrixProperty cloneStructure() {
+    return new MatrixProperty( getName(), getSize(), getPropertyType(), getAccessParameters() );
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ValueProperty
+  ///////////////////////////////////////////////////////////////////
+
+  /**
+   * Liefert ein Element der Matrix. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.
+   * @param coords Position (Koordinaten) der Matrix
+   * @exception java.lang.UnsupportedOperationException falls die Anzahl der
+   *            Koordinaten nicht genau der Matrix-Dimension entspricht
+   */
+  protected Object getValue(int... coords) {
+    if (coords.length != getDimension())
+      throw new UnsupportedOperationException("The MatrixProperty is ".concat(""+getDimension()).concat("-dimensional. Thus exactly ").concat(""+getDimension()).concat(" parameters are needed for getValue(..).") );
+    return MultiDimArray.get(matrix,coords);
+  }
+
+  /**
+   * Setzt ein Element der Matrix. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Liste.
+   * @param value  Wert fuer das Matrix-Element
+   * @param coords Position (Koordinaten) der Matrix
+   * @exception java.lang.UnsupportedOperationException falls die Anzahl der
+   *            Koordinaten nicht genau der Matrix-Dimension entspricht
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   */
+  protected void setValue(Object value, int... coords) {
+    if (coords.length != getDimension())
+      throw new UnsupportedOperationException("The MatrixProperty is ".concat(""+getDimension()).concat("-dimensional. Thus exactly ").concat(""+getDimension()).concat(" parameters are needed for setValue(..).") );
+    if ( value!=null && !this.isValid( value.getClass() ) )
+      throw new ClassCastException(value.getClass().getName().concat(" can not be stored in a MatrixProperty[").concat(this.getType().getName()).concat("]"));
+
+    // altes Objekt merken fuer Event
+    Object oldValue = getValue(coords);
+    MultiDimArray.set(matrix,coords,value);
+
+    this.fireEvent( new ObjectChangeEvent(
+      new Invoker(this),
+      oldValue,
+      value
+    ) );
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Neue Matrix-Funktionen
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert die Dimension der Matrix.
+   */
+  public int getDimension() {
+    return this.size.length;
+  }
+
+  /**
+   * Liefert die Groesse der Matrix in einer Dimension.
+   * @param dim Index der Dimension (beginnend bei 0)
+   */
+  public int getSize(int dim) {
+    return size[dim];
+  }
+
+  /**
+   * Liefert die Groesse der Matrix in allen Dimensionen.
+   */
+  public int[] getSize() {
+    return size.clone();
+  }
+
+  /**
+   * Liefert die Groesse der Matrix in allen Dimensionen als Beschreibung.
+   */
+  public String getSizeText() {
+    return sizeDesc;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ObjectStructure
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert <code>true</code>, da eine Matrix-Eigenschaft immer
+   * aus mehreren Werten besteht.
+   */
+  public boolean containsMultipleValues() {
+    return true;
+  }
+
+//  /**
+//   * Liefert <code>true</code>, da neben dem Klassennamen auch die groesse
+//   * der Matrix ausgewiesen werden soll.
+//   */
+//  public boolean isStructureNamed() {
+//    return true;
+//  }
+//
+//  /**
+//   * Liefert <code>true</code>, da neben dem Klassennamen auch die groesse
+//   * der Matrix ausgewiesen werden soll.
+//   */
+//  public String getStructureName() {
+//    return this.getClass().getSimpleName().concat( getSizeText() );
+//  }
+}

Added: trunk/src/schmitzm/data/property/Properties.java
===================================================================
--- trunk/src/schmitzm/data/property/Properties.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/Properties.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,82 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.ObjectStructure;
+
+/**
+ * Diese Interface sollten alle Objekte implementieren, die sich (fest) aus
+ * einzelnen Eigenschaften ({@link Property Properties}) zusammensetzen.
+ * Die Methoden des Interface liefern den Zugriff auf die einzelnen Eigenschaften.
+ * Das Hinzufuegen oder Entfernen von Eigenschaften ist durch dieses Interface
+ * nicht moeglich.
+ * @see schmitzm.data.property.DynamicProperties
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface Properties extends ObjectStructure {
+  /**
+   * Liefert die Anzahl an Eigenschaften des Objekts.
+   */
+  public int getPropertyCount();
+
+  /**
+   * Liefert die Eigenschaften des Objekts als Array. Wenn das Objekt (noch)
+   * keine Eigenschaften besitzt, sollte ein leerer Array (und nicht <code>null</code>!)
+   * zurueckgegeben werden!
+   */
+  public Property[] getProperties();
+
+  /**
+   * Liefert alle Eigenschaften des Objekts, die einem bestimmten
+   * Typ angehoeren. Als Typ kann sowohl ein bestimmter <code>Property</code>-Untertyp
+   * angegeben werden (z.B. {@link ListProperty}) oder ein Objekt-Typ (Typ
+   * den eine Property aufnehmen kann; z.B. <code>int.class</code>).<br>
+   * Da die Methode wiederum eine <code>Properties</code>-Instanz liefert,
+   * lassen sich recht einfach verschiedene Filter hintereinander anwenden:<br>
+   * <br>
+   * <center><code>
+   * Properties.getProperties(ListProperty.class).getProperties(int.class).getPropertyNames()
+   * </code></center>
+   * <br>
+   * liefert z.B. die Namen aller Listen-Eigenschaften eines Objekts, die
+   * <code>int</code>-Werte aufnehmen koennen.<br>
+   * <b>Bemerke:</b><br>
+   * Wenn das Objekt keine Eigenschaften des angegebenen Typs besitzt, sollte
+   * eine leere Property-Menge (z.B. {@link PropertySet}) zurueckgegeben werden
+   * und <b>nicht</b> <code>null</code>!
+   */
+  public Properties getProperties(Class type);
+
+  /**
+   * Liefert die Namen aller Eigenschaften des Objekts.
+   */
+  public String[] getPropertyNames();
+
+  /**
+   * Liefert die Typen aller Eigenschaften des Objekts.
+   */
+  public PropertyType[] getPropertyTypes();
+
+  /**
+   * Liefert eine Eigenschaft des Objekts.
+   * @param name Name der Eigenschaft
+   * @return <code>null</code> falls das Objekt keine Eigenschaft mit dem
+   *         Namen besitzt
+   */
+  public Property getProperty(String name);
+
+  /**
+   * Prueft, ob das Objekt eine bestimmte Eigenschaft besitzt.
+   */
+  public boolean containsProperty(String name);
+}

Added: trunk/src/schmitzm/data/property/Property.java
===================================================================
--- trunk/src/schmitzm/data/property/Property.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/Property.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,211 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.ObjectStructure;
+import schmitzm.data.event.AbstractObjectTraceable;
+import schmitzm.data.event.ObjectCloseEvent;
+import schmitzm.data.event.Invoker;
+
+/**
+ * Diese Klasse stellt den Obertyp fuer eine Eigenschaft dar. Eine Eigenschaft
+ * besteht aus 3 Teilen/Feldern:
+ * <ul>
+ * <li>ein Name, der die Eigenschaft (in einer Menge von Eigenschaften identifiziert)</li>
+ * <li>eine Klasse, die den Typ der Eigenschaft beschreibt</code></li>
+ * <li>ein Wert, als aktuelle Auspr?gung der Eigenschaft</li>
+ * </ul>
+ * Properties werden an verschiedenen Stellen eingesetzt. z.B. bei der Beschreibung
+ * allgemeiner Xulu-Datenobjekte oder allgemeiner Modell-Ereignisse (Events).<br>
+ * An eine Property koennen beliebig <code>ObjectListener</code> gekoppelt werden.
+ * Sobald die Eigenschaft (?ber <code>setValue(..)</code>) ihren Wert aendert,
+ * werden alle angeschlossenen <code>ObjectListener</code> mit einem
+ * <code>ObjectChangeEvent</code> informiert.<br>
+ * <br>
+ * Es gibt verschiedenste Arten, wie eine Eigenschaft aufgebaut ist und wie sie
+ * ihre Auspraegung(en) verwaltet (z.B. ein skalarer Wert oder eine ganze
+ * Liste/Matrix). Entsprechend variieren auch die Zugriffsmethoden.
+ * Aus diesem Grund spezifiziert diese Klasse noch keine Methoden, um auf die
+ * Auspraegung der Eigenschaft (lesend oder schreibend) zuzugreifen.
+ * Dies ist durch die speziellen Unterklassen zu implementieren.<br><br>
+ * Der Zugriff auf die Property kann durch Zugriffsrechte kontrolliert werden.
+ * Ein Zugriff (z.B. lesend oder schreibend) ist nur ueber ein entsprechendes
+ * Zugriffsrecht ({@link Access}) moeglich. Da an dieser Stelle (in dieser Oberklasse)
+ * die Art der Eigenschaft noch nicht spezifiziert ist, sind die entsprechenden
+ * Methoden fuer das {@link Accessible}-Interface von den Unterklassen
+ * zu implementieren.
+  * <br><br>
+  * Aus Vereinfachungsgruenden implementiert diese Klasse auch das Interface
+  * {@link Properties}. Auf den ersten Blick macht dies zwar keinen Sinn, da
+  * eine Property zunaechst ja immer genau eine Eigenschaft repraesentiert.
+  * An vielen Stellen ist es jedoch praktisch, eine einzelne Eigenschaft unmittelbar
+  * als (einelementige) Menge von Propertys behandeln zu koennen, ohne zunaechst
+  * ein {@link PropertySet} erzeugen und die Eigenschaft einfuegen zu muessen.
+ * @see schmitzm.data.event.ObjectListener
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class Property extends AbstractObjectTraceable implements Properties, Accessible {
+
+  /**
+   * Speichert den Namen der Eigenschaft.
+   */
+  protected String name = "";
+
+  /**
+   * Speichert den Daten-Typ der Eigenschaft.
+   */
+  protected PropertyType type = null;
+
+  /**
+   * Erzeugt eine Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. <code>int.class</code>).
+   * @param name Name der Eigenschaft
+   * @param type Daten-Typ der Eigenschaft
+   */
+  public Property(String name, PropertyType type) {
+    this.name = name;
+    this.type = type;
+  }
+
+  /**
+   * Liefert den Namen der Eigenschaft.
+   */
+  public String getName() {
+    return this.name;
+  }
+
+  /**
+   * Liefert die Klasse des Daten-Typs, der in der Eigenschaft gespeichert werden kann.
+   */
+  public Class getType() {
+    return getPropertyType().getType();
+  }
+
+  /**
+   * Liefert den Daten-Typ, der in der Eigenschaft gespeichert werden kann.
+   */
+  public PropertyType getPropertyType() {
+    return type;
+  }
+
+  /**
+   * Checkt, ob die Property einen Wert vom Typ <code>type</code> aufnehmen
+   * kann.
+   * @see schmitzm.data.property.PropertyType#isValid(Class)
+   */
+  public boolean isValid(Class type) {
+    return getPropertyType().isValid(type);
+  }
+
+  /**
+   * Erzeugt einen neue Property, die von der Struktur her identisch ist
+   * mit der Property. Der Inhalt der Property muss <b>nicht</b> kopiert werden.
+   */
+  public abstract Property cloneStructure();
+
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von Accessible
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * "Schliesst" die Eigenschaft. Diese Eigenschaft entzieht zunaecht saemtliche
+   * noch aktiven Zugriffsrechte und informiert dann  alle angeschlossenen
+   * <code>ObjectListener</code>. Anschliessend werden alle <code>ObjectListener</code>
+   * von dem Objekt entfernt.<br>
+   * Unterklassen sollten diese Methode ueberschreiben wenn weitere Aktionen
+   * beim "Schliessen" einer Eigenschaft notwendig sind.
+   */
+  public void dispose() {
+    disposeAllAccess();
+    this.fireEvent( new ObjectCloseEvent( new Invoker(this) ));
+    this.removeAllObjectListeners();
+  }
+
+  /**
+   * Prueft, ob aktuell Zugriffsrechte auf das Objekt verteilt sind.
+   */
+  public boolean hasAccess() {
+    return getAccessCount() > 0;
+  }
+
+  /**
+   * Prueft, ob aktuell Zugriffsrechte einer bestimmten Art
+   * auf das Objekt verteilt sind.
+   */
+  public boolean hasAccess(Class c) {
+    return getAccessCount(c) > 0;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von Properties
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert immer 1.
+   */
+  public int getPropertyCount() {
+    return 1;
+  }
+
+  /**
+   * Liefert einen 1-elementigen Array, in dem nur die Eigenschaft selbst
+   * enthalten ist.
+   */
+  public Property[] getProperties() {
+    return new Property[] { this };
+  }
+
+  /**
+   * Liefert <code>this</code>, falls, der angegebene Typ eine Oberklasse
+   * dieser Property ist, oder eine Oberklasse des Typs dieser Property.
+   * Andernfalls wird ein leeres {@link PropertySet} zurueckgegeben.
+   */
+  public Properties getProperties(Class type) {
+    if ( type.isAssignableFrom( this.getType() ) ||
+         type.isAssignableFrom( this.getClass() ) )
+      return this;
+    return new PropertySet();
+  }
+
+  /**
+   * Liefert einen 1-elementigen Array, in dem nur der Name der Eigenschaft selbst
+   * enthalten ist.
+   */
+  public String[] getPropertyNames() {
+    return new String[] { getName() };
+  }
+
+  /**
+   * Liefert einen 1-elementigen Array, in dem nur der Typ der Eigenschaft
+   * selbst enthalten ist.
+   */
+  public PropertyType[] getPropertyTypes() {
+    return new PropertyType[] { getPropertyType() };
+  }
+
+  /**
+   * Liefert die Eigenschaft selbst (<code>this</code>), falls deren Name
+   * angegeben wird, ansonsten <code>null</code>.
+   */
+  public Property getProperty(String name) {
+    return containsProperty(name) ? this : null;
+  }
+
+  /**
+   * Liefert <code>true</code>, falls der Name der Eigenschaft selbst
+   * angegeben wird.
+   */
+  public boolean containsProperty(String name) {
+    return name.equals(getName());
+  }
+
+}

Added: trunk/src/schmitzm/data/property/PropertyException.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertyException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertyException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,47 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Exception wird immer dann geworfen, wenn ein Fehler mit Objekt-Eigenschaften
+ * ({@link Property Properties}) auftritt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PropertyException extends RuntimeException {
+  private Property prop = null;
+
+  /**
+   * Erzeugt einen neuen Fehler.
+   * @param p       betroffene Eigenschaft
+   * @param message Fehlermeldung
+   */
+  public PropertyException(Property p, String message) {
+    super(message);
+    this.prop = p;
+  }
+
+  /**
+   * Erzeugt einen neuen Fehler.
+   * @param p betroffene Eigenschaft Zugriff
+   */
+  public PropertyException(Property p) {
+    this(p,"");
+  }
+
+  /**
+   * Liefert die Eigenschaft, fuer die das Problem auftrat.
+   */
+  public Property getProperty() {
+    return prop;
+  }
+}

Added: trunk/src/schmitzm/data/property/PropertyReadAccess.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertyReadAccess.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertyReadAccess.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,198 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Klasse stellt ein Recht auf Lesezugriff fuer eine
+ * {@link ValueProperty} dar.
+ * Hierzu implementiert sie die entsprechenden <code>getter</code>-Methoden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PropertyReadAccess extends Access {
+  private ValueProperty property       = null;
+
+  /**
+   * Erzeugt ein neues Lesezugriffsrecht. Die Anzahl an Zugriffen fuer den
+   * Besitzer ist unbegrenzt.
+   * @param object Instanz von <code>ValueProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @see schmitzm.data.property.ValueProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ValueProperty</code> ist
+   */
+  public PropertyReadAccess(Accessible object, Object owner) {
+    this(object,owner,UNLIMITED_ACCESSTIMES);
+  }
+
+  /**
+   * Erzeugt ein neues Lesezugriffsrecht.
+   * @param object Instanz von <code>ValueProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @param maxAccessTimes Anzahl an (Methoden-)Zugriffen, die der Rechtebesitzer
+   *                       taetigen darf, bevor das Recht automatisch entzogen wird
+   * @see schmitzm.data.property.ValueProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ValueProperty</code> ist
+   */
+  public PropertyReadAccess(Accessible object, Object owner, int maxAccessTimes) {
+    super(object,owner,maxAccessTimes);
+    if (!(object instanceof ValueProperty))
+      throw new AccessViolationException(this,this.getClass().getName()+" can only be instantiated for ValueProperties");
+    this.property = (ValueProperty)object;
+  }
+
+  /**
+   * Liefert den/einen Wert der Eigenschaft.<br>
+   * <code>coords</code> spezifizieren (optional) die "Koordinaten", an denen
+   * der Wert in der Eigenschaft zu finden ist.
+   * Fuer skalare Eigenschaften darf/braucht diese Angabe nicht gemacht zu
+   * werden. Fuer Listen und 1-dim. Matrizen (Arrays) darf nur ein Wert
+   * angegeben werden. Fuer mehr-dimensionale Matrizen muessen entsprechend
+   * der Dimension mehr Koordinaten angegeben werden (als einzelne Parameter oder
+   * als ein Array). z.B. fuer 2-dim. Matrix:<br>
+   * <code>getValue(10,13)</code> oder <code>getValue( new int[] {10,13} )</code><br>
+   * <br>
+   * Kann (seit JDK1.5.0) auch genutzt werden, wenn es sich bei der Property um einen
+   * Build-in-Type handelt.
+   * @param coords optionale Koordinaten
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   */
+  public Object getValue(int... coords) {
+    checkDisposed();
+    Object ret = this.property.getValue(coords);
+    incAndCheckMaxAccessTimesReached();
+    return ret;
+  }
+
+  /**
+   * Liefert den/einen Wert der Eigenschaft als <code>char</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>char</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public char getValueAsChar(int... coords) {
+    return ((Character)getValue(coords)).charValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>short</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>short</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public short getValueAsShort(int... coords) {
+    return ((Number)getValue(coords)).shortValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>byte</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>byte</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public byte getValueAsByte(int... coords) {
+    return ((Number)getValue(coords)).byteValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>int</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>int</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public int getValueAsInt(int... coords) {
+    return ((Number)getValue(coords)).intValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>long</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>long</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public long getValueAsLong(int... coords) {
+    return ((Number)getValue(coords)).longValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>float</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>float</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public float getValueAsFloat(int... coords) {
+    return ((Number)getValue(coords)).floatValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>double</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>double</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public double getValueAsDouble(int... coords) {
+    return ((Number)getValue(coords)).doubleValue();
+  }
+
+  /**
+   * Liefert den/einen aktuellen Wert der Eigenschaft als <code>boolean</code>.
+   * Kann nur genutzt werden, wenn der Objekt-Typ ein Build-In-Type ist.
+   * @param coords (optionale) Koordinaten (siehe <code>getValue(..)</code>
+   *               fuer Erlaeuterungen)
+   * @exception java.lang.ClassCastException falls es sich nicht um eine
+   *            <code>boolean</code>-Property handelt.
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Leserecht auf der Property nicht mehr gueltig ist
+   * @see #getValue(int...)
+   */
+  public boolean getValueAsBoolean(int... coords) {
+    return ((Boolean)getValue(coords)).booleanValue();
+  }
+
+}

Added: trunk/src/schmitzm/data/property/PropertySet.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertySet.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertySet.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,287 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import schmitzm.data.ObjectStructure;
+import schmitzm.data.ObjectStructureUtil;
+import schmitzm.data.event.Invoker;
+import schmitzm.data.event.ObjectTraceable;
+import schmitzm.data.event.AbstractObjectTraceable;
+import schmitzm.data.event.ObjectChangeEvent;
+
+/**
+ * Diese Klasse stellt eine Menge an Eigenschaften dar. Die einzelnen Properties
+ * werden parallel in einer {@link java.util.Hashtable} und einem
+ * {@link java.util.Vector} verwaltet. Die Hashtable dient dazu, die Properties
+ * effizient ueber ihren Namen ansprechen zu koennen. Der Vector repraesentiert
+ * eine Ordnung und wird deshalb verwendet, wenn alle Eigenschaften
+ * angesprochen werden (z.B. {@link #getProperties()} oder {@link #getPropertyNames()}).<br>
+ * Zu beachten ist, dass beim Entfernen einer Eigenschaft aus der Menge
+ * <b>nicht</b> die komplette Eigenschaft (mittels {@link Property#dispose()})
+ * geschlossen wird! Dies bietet den Vorteil dass
+ * eine Eigenschaft einfach von einer Menge in eine andere verschoben werden kann.
+ * Soll eine Eigenschaft (und vor allem ihre Ressourcen) komplett freigegeben
+ * werden, muss dies manuell geschehen. Hierfuer liefern die remove-Methoden eine
+ * Referenz auf die entfernte Eigenschaft zurueck.<br>
+ * <code>PropertySet</code> implementiert das Interface {@link ObjectTraceable},
+ * so dass Aenderungen an der Menge durch Listener verfolgt werden koennen.
+ * Es werden jedoch <b>keine</b> Aenderungen an den in der Menge enthaltenen
+ * Eigenschaften propagiert, sondern nur Aenderungen an der Eigenschaftsmenge
+ * selbst, also Hinzufuegen oder Entfernen einer Property. In beiden Faellen
+ * wird ein {@link ObjectChangeEvent} geworfen, wobei entweder
+ * {@link ObjectChangeEvent#getNewValue()} (beim Entfernen) oder {@link ObjectChangeEvent#getOldValue()}
+ * (beim Hinzufuegen) <code>null</code> liefert.
+ * @see schmitzm.data.property.Property
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PropertySet extends AbstractObjectTraceable implements DynamicProperties, Cloneable{
+  private Hashtable contentHash = null;
+  private Vector    contentVec  = null;
+
+  /**
+   * Erzeugt eine leere Menge von Eigenschaften aus einer Vorlage. Es wird jedoch
+   * nur die Struktur der Vorlage kopiert, <b>nicht</b> die darin enthaltenen
+   * Eigenschaften!!
+   * @param propSet Vorlage fuer die Property-Menge
+   */
+  public PropertySet(PropertySet propSet) {
+    this.contentHash =  (Hashtable)propSet.contentHash.clone();
+    this.contentVec  =  (Vector)propSet.contentVec.clone();
+  }
+
+  /**
+   * Erzeugt eine leere Menge von Eigenschaften.
+   */
+  public PropertySet() {
+    this.contentHash = new Hashtable();
+    this.contentVec  = new Vector();
+  }
+
+  /**
+   * Erzeugt eine Kopie der Menge. Dabei wird jedoch nicht der Inhalt - also
+   * die Properties selbst - kopiert, sondern nur das Hashtable-Objekt!!
+   */
+  public Object clone() {
+    return new PropertySet(this);
+  }
+
+  /**
+   * Liefert die Anzahl an Eigenschaften.
+   */
+  public int getPropertyCount() {
+    return contentVec.size();
+  }
+
+  /**
+   * Liefert die Eigenschaften der Menge als Array.
+   */
+  public Property[] getProperties() {
+//    return (Property[])contentHash.values().toArray(new Property[] {});
+    return (Property[])contentVec.toArray(new Property[] {});
+  }
+
+  /**
+   * Liefert alle Eigenschaften, die einem bestimmten
+   * Typ angehoeren. Als Typ kann sowohl ein bestimmter <code>Property</code>-Untertyp
+   * angegeben werden (z.B. {@link ListProperty}), als auch ein Objekt-Typ (Typ
+   * den eine Property aufnehmen kann; z.B. <code>int.class</code>).<br>
+   * Da die Methode wiederum eine <code>Properties</code>-Instanz liefert,
+   * lassen sich recht einfach verschiedene Filter hintereinander anwenden:<br>
+   * <br>
+   * <center><code>
+   * Properties.getProperties(ListProperty.class).getProperties(int.class).getPropertyNames()
+   * </code></center>
+   * <br>
+   * liefert z.B. die Namen aller Listen-Eigenschaften, die <code>int</code>-Werte
+   * aufnehmen koennen.
+   */
+  public Properties getProperties(Class type) {
+    PropertySet set = new PropertySet();
+    for(Enumeration e=contentVec.elements();e.hasMoreElements();) {
+      Property p = (Property)e.nextElement();
+      // Ist der angegebene Typ eine Property (oder ein Untertyp),
+      // wird direkt auf die Art der Property geprueft (1), ansonsten auf den
+      // Typ, den die Property beinhalten kann (2)
+      if ( Property.class.isAssignableFrom(type) ) {
+        // (1) type Superklasse der Property-Art?
+        if ( type.isAssignableFrom( p.getClass() ) )
+          set.addProperty(p);
+      } else {
+        // (2) type Superklasse des Property-Typs?
+        if ( type.isAssignableFrom( p.getType() ) )
+          set.addProperty(p);
+      }
+    }
+
+    return set;
+  }
+
+  /**
+   * Liefert die Namen aller Eigenschaften.
+   */
+  public String[] getPropertyNames() {
+//    return (String[])content.keySet().toArray(new String[0]);
+    String[] name = new String[ getPropertyCount() ];
+    Enumeration e = contentVec.elements();
+    for(int i=0; e.hasMoreElements(); i++)
+      name[i] = ((Property)e.nextElement()).getName();
+    return name;
+  }
+
+  /**
+   * Liefert die Typen aller Eigenschaften.
+   */
+  public PropertyType[] getPropertyTypes() {
+    PropertyType[]  type = new PropertyType[ getPropertyCount() ];
+    Enumeration e = contentVec.elements();
+    for(int i=0; e.hasMoreElements(); i++)
+      type[i] = ((Property)e.nextElement()).getPropertyType();
+    return type;
+  }
+
+  /**
+   * Liefert eine bestimmte Eigenschaft der Menge.
+   * @param name Name der Eigenschaft
+   * @return <code>null</code> falls die Menge keine Eigenschaft dieses
+   *         Namens enthaelt
+   */
+  public Property getProperty(String name) {
+    return (Property)contentHash.get(name);
+  }
+
+  /**
+   * Prueft, ob die Menge eine bestimmte Eigenschaft enthaelt.
+   * @param name Name der Eigenschaft
+   */
+  public boolean containsProperty(String name) {
+    return getProperty(name)!=null;
+  }
+
+  /**
+   * Fuegt der Menge eine Eigenschaft hinzu. Dabei wird auf Eindeutigkeit
+   * des Namens geachtet!
+   * @param prop Eigenschaft
+   * @exception schmitzm.data.property.PropertyException falls bereits
+   *            eine Eigenschaft dieses Namens in der Menge vorhanden ist
+   */
+  public void addProperty(Property prop) {
+    if ( containsProperty(prop.getName()) )
+      throw new PropertyException(prop,"PropertySet already contains a Property named '".concat(prop.getName()).concat("'"));
+    contentHash.put(prop.getName(),prop);
+    contentVec.add(prop);
+    fireEvent(new ObjectChangeEvent(new Invoker(this),null,prop));
+  }
+
+  /**
+   * Entfernt eine Eigenschaft aus der Menge.
+   * @param name Name der Eigenschaft
+   * @return Die entferne Eigenschaft oder <code>null</code>, falls das
+   *         die Menge keine Eigenschaft mit dem Namen enthaelt
+   */
+  public Property removeProperty(String name) {
+    Property prop = (Property)contentHash.remove(name);
+    contentVec.remove(prop);
+    fireEvent(new ObjectChangeEvent(new Invoker(this),prop,null));
+    return prop;
+  }
+
+  /**
+   * Entfernt eine Eigenschaft aus der Menge.
+   * Falls die Menge eine solche Eigenschaft nicht besitzt, passiert nichts.
+   * @param prop zu entfernende Eigenschaft
+   * @return Die entferne Eigenschaft oder <code>null</code> falls das Objekt
+   *         die Eigenschaft nicht besitzt
+   */
+  public Property removeProperty(Property prop) {
+    return removeProperty(prop.getName());
+  }
+
+  /**
+   * Entfernt Eigenschaften aus der Menge. Falls die Menge eine dieser
+   * Eigenschaften nicht enthaelt, passiert fuer die entsprechende Eigenschaft
+   * nichts.
+   * @param prop zu entfernende Eigenschaft
+   * @return Die entfernten Eigenschaften (kann eine leere Eigenschaftsmenge
+   *         sein, wenn die Menge <u>keine</u> der uebergebenen Eigenschaften
+   *         besitzt.
+   */
+  public Properties removeProperties(Properties prop) {
+    Property    p   = null;
+    PropertySet set = new PropertySet();
+    for (int i=0; i<prop.getProperties().length; i++)
+      if ( (p = removeProperty(prop.getProperties()[i]))!=null )
+        set.addProperty(p);
+    return set;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ObjectStructure
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert <code>false</code>.
+   */
+  public boolean isStructureNamed() {
+    return false;
+  }
+
+  /**
+   * Liefert <code>null</code>.
+   */
+  public String getStructureName() {
+    return null;
+  }
+
+  /**
+   * Liefert die Anzah1 an Properties in der Menge.
+   * @see #getPropertyCount()
+   */
+  public int getAttrCount() {
+    return getPropertyCount();
+  }
+
+  /**
+   * Liefert eine Liste, die genau so viele Elemente enthaelt, wie die
+   * Eigenschaftsmenge Propertys hat. Alle Listen-Elemente sind vom Typ
+   * {@link ObjectStructure}.
+   */
+  public Enumeration<ObjectStructure> getAttrTypes() {
+    return contentVec.elements();
+  }
+
+  /**
+   * Vergleicht die Struktur der Property mit einer anderen auf Gleichheit.
+   * @param object eine anderes {@link ObjectStructure}-Objekt
+   * @return <code>false</code> falls das uebergebene Objekt keine
+   *         {@link ObjectStructure} ist
+   * @see ValuePropertyType#equalsInStructure(Object)
+   */
+  public boolean equalsInStructure(Object object) {
+    if ( !(object instanceof ObjectStructure) )
+      return false;
+    return ObjectStructureUtil.compareObjectStructures(this,(ObjectStructure)object) == ObjectStructureUtil.EQUAL;
+  }
+
+  /**
+   * Liefert <code>false</code>, da eine Eigenschaftsmenge von jeder Eigenschaft
+   * immer nur eine Instanz enthaelt.
+   */
+  public boolean containsMultipleValues() {
+    return false;
+  }
+
+}

Added: trunk/src/schmitzm/data/property/PropertyType.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertyType.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertyType.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,78 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.temp.BaseTypeUtil;
+
+/**
+ * Diese Klasse stellt allgemein den (strukturellen) Typ einer Eigenschaft dar.
+ * Sie selbst implementiert lediglich die Speicherung eines Klassennamens.
+ * Entscheidender sind die abgeleiteten Untertypen:<br>
+ * Fuer alle Wert-Eigenschaften ist im {@linkplain ValuePropertyType Typ} z.B.
+ * (falls vorhanden) die Unterstruktur gespeichert.
+ * @see ValuePropertyType
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class PropertyType {
+  /**
+   * Speichert den eigentlichen Typ der Property.
+   */
+  protected Class type = null;
+
+  /**
+   * Erzeugt einen neuen Eigenschaftstyp
+   * @param type Objekt-Typ den die Eigenschaft aufnehmen kann
+   */
+  public PropertyType(Class type) {
+    this.type = type;
+  }
+
+  /**
+   * Liefert den Typ, den die Eigenschaft aufnehmen kann.
+   */
+  public Class getType() {
+    return this.type;
+  }
+
+  /**
+   * Checkt, ob die Property einen Wert vom Typ <code>type</code> aufnehmen
+   * kann. Dies ist der Fall, wenn <code>type</code> dieselbe oder eine
+   * Subklasse des Property-Typs ist.<br>
+   * Ausserdem gelten fuer Build-In-Types auch die entsprechenden Objekte
+   * (z.B. <code>Integer</code>-Instanz fuer in <code>int</code>-Property)
+   * als gueltig.
+   * @see schmitzm.data.property.Property#isValid(Class)
+   * @see schmitzm.temp.BaseTypeUtil#isBuildInType(Class)
+   * @see schmitzm.temp.BaseTypeUtil#isEquivalent(Class,Class)
+   */
+  public boolean isValid(Class type) {
+    return getType().isAssignableFrom(type) ||
+           BaseTypeUtil.isBuildInType(getType()) && BaseTypeUtil.isEquivalent(getType(),type);
+//    return getType().equals(char.class)    && Character.class.isAssignableFrom(type) ||
+//           getType().equals(short.class)   && Short.class.isAssignableFrom(type) ||
+//           getType().equals(byte.class)    && Byte.class.isAssignableFrom(type) ||
+//           getType().equals(int.class)     && Integer.class.isAssignableFrom(type) ||
+//           getType().equals(long.class)    && Long.class.isAssignableFrom(type) ||
+//           getType().equals(float.class)   && Float.class.isAssignableFrom(type) ||
+//           getType().equals(double.class)  && Double.class.isAssignableFrom(type) ||
+//           getType().equals(boolean.class) && Boolean.class.isAssignableFrom(type) ||
+//           getType().isAssignableFrom(type);
+  }
+
+  /**
+   * Liefert den Namen der Typ-Klasse als Beschreibung des Eigenschaftstyp.
+   */
+  public String toString() {
+    return getType().getName();
+  }
+}

Added: trunk/src/schmitzm/data/property/PropertyUtil.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertyUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertyUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,322 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import java.util.Vector;
+import schmitzm.lang.LangUtil;
+import schmitzm.temp.BaseTypeUtil;
+
+/**
+ * Diese Klasse beinhaltet statische Hilfsmethoden fuer das Arbeiten mit
+ * {@link Property} und {@link Properties}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PropertyUtil {
+
+  /**
+   * Liefert eine Eigenschaft eines {@link Properties}-Objekt.
+   * Enthaelt das {@link Properties}-Objekt keine Eigenschaft mit dem
+   * angegebenen Namen, wird dieser sukzessive verkuerzt (Punkt-Notation)
+   * bis im Objekt eine entsprechende Eigenschaft gefunden wird.
+   * In diesem Fall muss es sich beim Wert dieser Eigenschaft
+   * wiederum um ein {@link Properties}-Objekt handeln, um den "abgeschnittenen"
+   * Teil des Eigenschaft-Names rekursiv zu verarbeiten.
+   * @param propObject {@link Properties}-Objekt
+   * @param propSuffix Name der Eigenschaft (bzw. mehrere Eigenschaften
+   *                   durch "." getrennt)
+   * @return {@code null} wenn die Eigenschaft nicht gefunden werden kann
+   */
+  public static Property getProperty(Properties propObject, String propSuffix) {
+    return getProperty(propObject, propSuffix, '.');
+  }
+
+  /**
+   * Liefert den Wert eine Eigenschaft. Enthaelt das {@link Properties}-Objekt
+   * keine Eigenschaft mit dem angegebenen Namen, wird dieser sukzessive
+   * verkuerzt (Punkt-Notation) bis im Objekt eine entsprechende Eigenschaft
+   * gefunden wird. In diesem Fall muss es sich beim Wert dieser Eigenschaft
+   * wiederum um ein {@link Properties}-Objekt handeln, um den "abgeschnittenen"
+   * Teil des Eigenschaft-Names rekursiv zu verarbeiten.
+   * @param propObject {@link Properties}-Objekt
+   * @param propSuffix Name der Eigenschaft (bzw. mehrere Eigenschaften
+   *                   getrennt durch das {@code sep}-Zeichen)
+   * @param sep        Trenn-Zeichen, mit dem in {@link propSuffix} mehere Eigenschaften
+   *                   von einander getrennt sind
+   * @return {@code null} wenn die Eigenschaft nicht gefunden werden kann
+   */
+  public static Property getProperty(Properties propObject, String propSuffix, char sep) {
+    if ( propSuffix == null )
+      propSuffix = "";
+
+    String propName = propSuffix;
+    int[]  arrayCoord = new int[0];
+    propSuffix = "";
+    while ( !propName.equals("") ) {
+      Property prop = propObject.getProperty(propName);
+      if ( prop == null ) {
+        arrayCoord = getArrayCoordinates(propName);
+        if ( arrayCoord.length > 0 ) {
+          // cut the array coordinates
+          propName = cutArrayCoordinates(propName);
+          prop     = propObject.getProperty(propName);
+        }
+      }
+      // Property found
+      if ( prop != null ) {
+        // no more property names exist >> the object is the requested property (Juhuuuu!)
+        if ( propSuffix.equals("") )
+          return prop;
+
+        // more property names exist >> property must be a ScalarProperty containing
+        //                              a Properties to perform the property suffix
+        if ( prop instanceof ValueProperty ) {
+          Object propValue = ((ValueProperty)prop).getOneTimeReadAccess().getValue(arrayCoord);
+          if ( propValue instanceof Properties )
+            return getProperty((Properties)propValue, propSuffix, sep);
+        }
+      }
+
+      // cut at last "." and try the prefix as property name
+      int tLastDotIdx = propName.lastIndexOf(sep);
+      if ( !propSuffix.equals("") )
+        propSuffix += sep;
+      propSuffix += propName.substring(tLastDotIdx+1);
+      propName   =  tLastDotIdx > 0 ? propName.substring(0,tLastDotIdx) : "";
+    }
+    // Property not found in object
+    return null;
+  }
+
+  /**
+   * Ermittelt die am Ende eines Strings in runden Klammern spezifierten
+   * Koordinaten.
+   * @param objectSpec ein String
+   * @return int[0], wenn der String keine Klammerung am Ende enthaelt.
+   */
+  public static int[] getArrayCoordinates(String objectSpec) {
+    objectSpec = objectSpec.trim();
+    int[] arrayCoord = new int[0];
+    if ( objectSpec.endsWith(")") && objectSpec.contains("(")) {
+      int openIdx = objectSpec.lastIndexOf("(");
+      String[] arrayCoordStr = objectSpec.substring(openIdx+1,objectSpec.length()-1).split(",");
+      arrayCoord = new int[arrayCoordStr.length];
+      for (int i=0; i<arrayCoord.length; i++)
+        arrayCoord[i] = Integer.parseInt(arrayCoordStr[i].trim());
+    }
+    return arrayCoord;
+  }
+
+  /**
+   * Entfernt eine etwaige Koordinaten-Definition (in runden Klammern) am
+   * Ende eines Strings.
+   * @param objectSpec ein String
+   * @return den original String, wenn dieser keine Koordinaten-Definition
+   *         enthaelt
+   */
+  public static String cutArrayCoordinates(String objectSpec) {
+    if ( objectSpec.endsWith(")") && objectSpec.contains("("))
+      return objectSpec.substring(0, objectSpec.lastIndexOf("("));
+    return objectSpec;
+  }
+
+
+  /**
+   * Wraps a {@link Property} to an appropriate java object.
+   * <ul>
+   *   <li>{@link ScalarProperty}: wrapped to its (single) value</li>
+   *   <li>{@link ListProperty}: wrapped to an appropriate {@link Vector} which
+   *                             contains the {@link ListProperty} values</li>
+   *   <li>{@link MatrixProperty}: wrapped to an appropriate native {@code Object[]..[]}
+   *                               which contains the {@link MatrixProperty} values</li>
+   * </ul>
+   * @param prop   a property
+   * @param coord  <b>if specified, the appropiate element of the {@link ListProperty}
+   *               or {@link MatrixProperty} is returned, instead of the complete
+   *               list or matrix!</b>
+   * @return the Property itself, if no wrap can be performed
+   */
+  public static Object getPropertyValue(Property prop, int... coord) {
+    if ( coord == null )
+      coord = new int[0];
+
+    //*********** ScalarProperty **************
+    if ( prop instanceof ScalarProperty ) {
+      return ((ScalarProperty)prop).getOneTimeReadAccess().getValue();
+    }
+
+    //*********** ListProperty **************
+    if ( prop instanceof ListProperty ) {
+      // if coordinates are given, the list element is returned
+      if (coord.length > 0)
+        return ((ListProperty)prop).getOneTimeReadAccess().getValue(coord);
+      // return all list elements as Vector
+      ListPropertyReadAccess prop_RA = ((ListProperty)prop).getReadAccess(null);
+      Vector vector = new Vector( prop_RA.getCount() );
+      for (int i=0; i<prop_RA.getCount(); i++)
+        vector.add( prop_RA.getValue(i) );
+      prop_RA.release();
+      return vector;
+    }
+
+    //*********** MatrixProperty **************
+    if ( prop instanceof MatrixProperty ) {
+      // if coordinates are given, the matrix element is returned
+      if (coord.length > 0)
+        return ((MatrixProperty)prop).getOneTimeReadAccess().getValue(coord);
+
+      // challenge: MatrixProperty is generic and its dimension and
+      //            data type is not fixed
+      //            --> the creation of the target native array
+      //                must be realised generic too
+      MatrixProperty     matrixProp = (MatrixProperty)prop;
+      PropertyReadAccess prop_RA    = (matrixProp).getReadAccess(null);
+      int[]              dimSizes   = matrixProp.getSize(); // Size of the dimensions
+      Object             array      = LangUtil.createArray( dimSizes );
+      // Calculate the total number of elements in the matrix
+      int elemCount = 1;
+      for ( int dimSize : dimSizes )
+        elemCount *= dimSize;
+
+      // put the elements in the array
+      coord = new int[ dimSizes.length ];
+      for ( int i=0; i<elemCount; i++ ) {
+        // set the element at coordinate tCoord
+        LangUtil.setArrayValue(
+            array,
+            prop_RA.getValue( coord ),
+            coord
+        );
+
+        // increment the coordinate for the element
+        for (int d = 1; d < dimSizes.length; d++)
+          if (++coord[d] < dimSizes[d])
+            break; // Coordinates in dimensionen > d not influenced by the increment
+          else
+            coord[d] = 0; // Initialize coordinate in dimension d and continue
+                          // to increment in dimension d+1 erhoehen
+      }
+      prop_RA.release();
+      return array;
+    }
+
+    return prop;
+  }
+
+  /**
+   * Sets the value of a {@link Property} with an appropriate java object.
+   * <ul>
+   *   <li>{@link ScalarProperty}: sets the property's (single) value to {@code aValue}</li>
+   *   <li>{@link ListProperty}: clears the list and fills it with the
+   *                             elements of {@code aValue} (must be an {@code Object[]}
+   *                             or an {@link Iterable})</li>
+   *   <li>{@link MatrixProperty}: fills the matrix with the elements of
+   *                               {@code aValue} (must be an {@code Object[]})</li>
+   * </ul>
+   * @param prop   a property
+   * @param value  a value the property is set to
+   * @param coord  <b>if specified, only the appropiate element of the {@link ListProperty}
+   *               or {@link MatrixProperty} is set with {@code aValue},
+   *               instead of the complete list or matrix!</b>
+   */
+  public static void setPropertyValue(ValueProperty prop, Object value, int... coord) {
+    if ( coord == null )
+      coord = new int[0];
+
+    //*********** ScalarProperty **************
+    if (prop instanceof ScalarProperty)
+      ((ScalarProperty)prop).getOneTimeWriteAccess().setValue(
+          convertToPropertyType(value, prop.getType())
+      );
+
+    //*********** ListProperty **************
+    if (prop instanceof ListProperty) {
+      // if coordinates are given, the matrix element is set
+      if (coord.length > 0) {
+        ((ListProperty)prop).getOneTimeWriteAccess().setValue(
+            convertToPropertyType(value, prop.getType()),
+            coord
+        );
+        return;
+      }
+      ListPropertyWriteAccess prop_WA = ((ListProperty)prop).getWriteAccess(null);
+      // Clear the ListProperty
+      prop_WA.removeAll();
+      // Put all elements of aValue into the ListProperty
+      if ( value instanceof String ) {
+        // String vom Format "{ ... , ... , ... }", dann String
+        // splitten
+        String sepString = ((String) value).trim();
+        if ( sepString.startsWith("{") && sepString.endsWith("}") )
+          value = sepString.substring(1, sepString.length()-1).split(",");
+      }
+      if ( value instanceof Object[] )
+        for (Object arrayValue : (Object[])value)
+          prop_WA.addValue( convertToPropertyType(arrayValue, prop.getType()) );
+      else if ( value instanceof Iterable )
+        for (Object element : (Iterable)value)
+          prop_WA.addValue( convertToPropertyType(element, prop.getType()) );
+      prop_WA.release();
+    }
+    //*********** MatrixProperty **************
+    if (prop instanceof MatrixProperty) {
+      // if coordinates are given, the matrix element is set
+      if (coord.length > 0) {
+        ((MatrixProperty)prop).getOneTimeWriteAccess().setValue(
+            convertToPropertyType(value, prop.getType()),
+            coord
+        );
+        return;
+      }
+
+      MatrixProperty      matrixProp = (MatrixProperty)prop;
+      PropertyWriteAccess prop_WA    = (matrixProp).getWriteAccess(null);
+      int[]               dimSizes   = LangUtil.getArrayLength(value); // Size of the dimensions
+      if ( dimSizes.length != matrixProp.getDimension() )
+        throw new UnsupportedOperationException("Dimension of MatrixProperty differs from specified array: "+matrixProp.getDimension()+" <> "+dimSizes.length);
+
+      // Calculate the total number of elements in the matrix
+      int elemCount = 1;
+      for ( int dimSize : dimSizes )
+        elemCount *= dimSize;
+
+      // put the array elements in the MatrixProperty
+      coord = new int[ dimSizes.length ];
+      for ( int i=0; i<elemCount; i++ ) {
+        // set the MatrixProperty at coordinate tCoord
+        prop_WA.setValue(
+          convertToPropertyType(LangUtil.getArrayValue( value, coord ), prop.getType()),
+          coord
+        );
+
+        // increment the coordinate for the element
+        for (int d = 1; d < dimSizes.length; d++)
+          if (++coord[d] < dimSizes[d])
+            break; // Coordinates in dimensionen > d not influenced by the increment
+          else
+            coord[d] = 0; // Initialize coordinate in dimension d and continue
+                          // to increment in dimension d+1 erhoehen
+      }
+      prop_WA.release();
+    }
+  }
+
+  private static Object convertToPropertyType(Object value, Class neededType) {
+    // try to set the object directly to the property
+    if ( !neededType.isInstance(value) ) {
+      if ( value instanceof String )
+        value = BaseTypeUtil.convertFromString((String)value, neededType);
+    }
+    return value;
+  }
+
+}

Added: trunk/src/schmitzm/data/property/PropertyWriteAccess.java
===================================================================
--- trunk/src/schmitzm/data/property/PropertyWriteAccess.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/PropertyWriteAccess.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,81 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Klasse stellt ein Recht auf Schreibzugriff fuer eine
+ * {@link ValueProperty} dar.
+ * Hierzu implementiert sie die entsprechenden <code>setter</code>-Methoden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PropertyWriteAccess extends PropertyReadAccess {
+  private ValueProperty property = null;
+
+  /**
+   * Erzeugt ein neues Schreibzugriffsrecht. Die Anzahl an Zugriffen fuer den
+   * Besitzer ist unbegrenzt.
+   * @param object Instanz von <code>ValueProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @see schmitzm.data.property.ValueProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ValueProperty</code> ist
+   */
+  public PropertyWriteAccess(Accessible object, Object owner) {
+    this(object,owner,UNLIMITED_ACCESSTIMES);
+  }
+
+  /**
+   * Erzeugt ein neues Schreibzugriffsrecht.
+   * @param object Instanz von <code>ValueProperty</code> auf die sich das Recht bezieht
+   * @param owner  Besitzer des Rechts
+   * @param maxAccessTimes Anzahl an (Methoden-)Zugriffen, die der Rechtebesitzer
+   *                       taetigen darf, bevor das Recht automatisch entzogen wird
+   * @see schmitzm.data.property.ValueProperty
+   * @exception schmitzm.data.property.AccessViolationException falls das
+   *            angegebene Objekt <b>keine</b> <code>ValueProperty</code> ist
+   */
+  public PropertyWriteAccess(Accessible object, Object owner, int maxAccessTimes) {
+    super(object,owner,maxAccessTimes);
+    if (!(object instanceof ValueProperty))
+      throw new AccessViolationException(this,this.getClass().getName()+" can only be instantiated for ValueProperties");
+    property = (ValueProperty)object;
+  }
+
+  /**
+   * Setzt den/einen Wert der Eigenschaft.<br>
+   * <code>coords</code> spezifizieren (optional) die "Koordinaten", an denen
+   * der Wert in der Eigenschaft zu finden ist.
+   * Fuer skalare Eigenschaften darf/braucht diese Angabe nicht gemacht zu
+   * werden. Fuer Listen und 1-dim. Matrizen (Arrays) darf nur ein Wert
+   * angegeben werden. Fuer mehr-dimensionale Matrizen muessen entsprechend
+   * der Dimension mehr Koordinaten angegeben werden (als einzelne Parameter oder
+   * als ein Array). z.B. fuer 2-dim. Matrix:<br>
+   * <code>getValue(10,13)</code> oder <code>getValue( new int[] {10,13} )</code><br>
+   * <br>
+   * Kann (seit JDK1.5.0) auch genutzt werden, wenn es sich bei der Property um einen
+   * Build-in-Type handelt.
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Property.
+   * @param value  neuer Wert fuer die Eigenschaft
+   * @param coords optionale Koordinaten
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das Schreibrecht auf der Property nicht mehr gueltig ist
+   */
+  public void setValue(Object value, int... coords) {
+    checkDisposed();
+    this.property.setValue(value,coords);
+    incAndCheckMaxAccessTimesReached();
+  }
+
+}

Added: trunk/src/schmitzm/data/property/ScalarProperty.java
===================================================================
--- trunk/src/schmitzm/data/property/ScalarProperty.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ScalarProperty.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,183 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.event.ObjectChangeEvent;
+import schmitzm.data.event.Invoker;
+
+/**
+ * Diese Klasse stellt eine Eigenschaft dar, welche genau einen Wert
+ * (in Form eines Objekts) darstellt.<br>
+ * Der Zugriff auf die Property kann durch Zugriffsrechte kontrolliert werden.
+ * Ein Zugriff (lesend oder schreibend) ist nur ueber ein entsprechendes
+ * Zugriffsrecht ({@link Access}) moeglich. Standardmaessig ist unbegrenzter
+ * Zugriff eingestellt. Durch Angabe von {@link ValuePropertyAccessParameters}
+ * kann jedoch benutzerdefiniert festgelegt werden, wie viele Objekte gleichzeitig
+ * Lese- und/oder Schreibzugriff erlangen duerfen.
+ * @see schmitzm.data.property.ValueProperty#getReadAccess(Object)
+ * @see schmitzm.data.property.ValueProperty#getWriteAccess(Object)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ScalarProperty extends ValueProperty {
+  /**
+   * Speichert die Auspraegung der Eigenschaft.
+   */
+  protected Object value = null;
+
+  /**
+   * Erzeugt eine Eigenschaft.
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ, den die Eigenschaft aufnehmen kann
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ScalarProperty(String name, ValuePropertyType elementType, ValuePropertyAccessParameters params) {
+    super( name, elementType, params );
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ, den die Eigenschaft aufnehmen kann
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ScalarProperty(String name, ValuePropertyType elementType) {
+    this( name, elementType, ValuePropertyAccessParameters.UNLIMITED_ACCESS );
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. int.class).
+   * @param name Bezeichnung der Eigenschaft
+   * @param elementType Daten-Typ, den die Eigenschaft aufnehmen kann
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ScalarProperty(String name, Class elementType, ValuePropertyAccessParameters params) {
+    super( name, elementType, params );
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. <code>int.class</code>). Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param type Daten-Typ der Eigenschaft
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ScalarProperty(String name, Class type) {
+    this(name,type,ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Der Datentyp ist implizit durch das Wert-Objekt
+   * gegeben. Kann auch fuer Build-in-Werte (<code>int</code>, <code>char</code>, ...)
+   * verwendet werden.
+   * @param name  Bezeichnung der Eigenschaft
+   * @param value Initialer Wert der Eigenschaft
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ScalarProperty(String name, Object value, ValuePropertyAccessParameters params) {
+    this(name,ValuePropertyType.createValuePropertyType(value),params);
+    setValue(value);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Der Datentyp ist implizit durch das Wert-Objekt
+   * gegeben. Kann auch fuer Build-in-Werte (<code>int</code>, <code>char</code>, ...)
+   * verwendet werden. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name  Bezeichnung der Eigenschaft
+   * @param value Initialer Wert der Eigenschaft
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ScalarProperty(String name, Object value) {
+    this(name,value,ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Liefert eine neue, leerer <code>ScalarProperty</code> mit identischem
+   * Namen, Typ und gleichen Zugriffsparemetern.
+   */
+  public ScalarProperty cloneStructure() {
+    return new ScalarProperty( getName(), getPropertyType(), getAccessParameters() );
+  }
+
+  /**
+   * Zerstoert die Property und den Property-Wert. Handelt es sich bei dem
+   * Wert der Property wiederum um eine Property, wird auch deren
+   * <code>dispose()</code>-Methode aufgerufen.
+   */
+  public void dispose() {
+    if ( value instanceof Property )
+      ((Property)value).dispose();
+    value = null;
+    super.dispose();
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ValueProperty
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert den aktuellen Wert der Eigenschaft. Kann (seit JDK1.5.0) auch
+   * genutzt werden, wenn es sich bei der Property um einen Build-in-Type
+   * handelt.<br>
+   * <b>Da eine skalare Eigenschaft genau einen Wert enthaelt, duerfen keine
+   * <code>coords</code>-Parameter angegeben werden!!</b>
+   * @exception java.lang.UnsupportedOperationException falls ein oder mehrere
+   *            <code>coords</code>-Parameter angegeben werden
+   */
+  protected Object getValue(int... coords) {
+    if (coords.length > 0)
+      throw new UnsupportedOperationException("A ScalarProperty only contains one value. Thus no parameters are allowed for getValue(..).");
+    return value;
+  }
+
+  /**
+   * Setzt den Wert der Eigenschaft. Kann (seit JDK1.5.0) auch genutzt werden,
+   * wenn es sich bei der Property um einen Build-in-Type handelt.<br>
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Property.
+   * <b>Da eine skalare Eigenschaft genau einen Wert enthaelt, duerfen keine
+   * <code>coords</code>-Parameter angegeben werden!!</b>
+   * @param value neuer Wert fuer die Eigenschaft
+   * @exception java.lang.UnsupportedOperationException falls ein oder mehrere
+   *            <code>coords</code>-Parameter angegeben werden
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   */
+  protected void setValue(Object value, int... coords) {
+    if (coords.length > 0)
+      throw new UnsupportedOperationException("A ScalarProperty only contains one value. Thus no parameters are allowed for getValue(..).");
+    if ( value!=null && !this.isValid( value.getClass() ) )
+      throw new ClassCastException(value.getClass().getName().concat(" can not be stored in a ScalarProperty[").concat(this.getType().getName()).concat("]"));
+
+    this.fireEvent( new ObjectChangeEvent(
+      new Invoker(this),
+      getValue(),
+      this.value = value
+    ) );
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ObjectStructure
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert <code>false</code>, das eine skalare Eigenschaft immer
+   * nur aus einem Wert besteht.
+   */
+  public boolean containsMultipleValues() {
+    return false;
+  }
+}

Added: trunk/src/schmitzm/data/property/ValueProperty.java
===================================================================
--- trunk/src/schmitzm/data/property/ValueProperty.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ValueProperty.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,375 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import schmitzm.data.ObjectStructure;
+
+/**
+ * Diese Klasse stellt eine Oberklasse fuer alle Eigenschaften dar, die
+ * einen oder mehrere Wert speichern koennen.
+ * <b>Die Implementierung basiert auf Sun JDK 1.5.0 und nutzt optionale
+ * Methoden-Parameter.</b>
+ * Hierdurch kann (wahlweise) fuer skalare Properties auf Parameter verzichtet
+ * werden (z.B. <code>getValue()</code> oder <code>setValue(<i>someObject</i>)</code>).
+ * Fuer 2-dim. Matrizen koennen derselben Methode 2 zusaetzliche Parameter
+ * (oder ein 2-dim. Array) uebergeben werden, um die Position innerhalb der
+ * Matrix zu spezifizieren (z.B. <code>getValue(10,13)</code> oder
+ * <code>setValue(<i>someObject</i>,10,13)</code>).<br><br>
+ * Die jeweilige Sub-Klasse muss nur noch die Art der Speicherung (z.B. skalar,
+ * Liste oder Matrix), sowie je eine setter- und getter-Methode implementieren.<br>
+ * Der Zugriff auf die Property kann durch Zugriffsrechte kontrolliert werden.
+ * Ein Zugriff (lesend oder schreibend) ist nur ueber ein entsprechendes
+ * Zugriffsrecht ({@link Access}) moeglich. Standardmaessig ist unbegrenzter
+ * Zugriff eingestellt. Durch Angabe von {@link ValuePropertyAccessParameters}
+ * kann jedoch benutzerdefiniert festgelegt werden, wie viele Objekte gleichzeitig
+ * Lese- und/oder Schreibzugriff erlangen duerfen.
+ * @see #getReadAccess(Object)
+ * @see #getWriteAccess(Object)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class ValueProperty extends Property implements ObjectStructure {
+  private Vector readAccess = new Vector<PropertyReadAccess>();
+  private Vector writeAccess = new Vector<PropertyWriteAccess>();
+  private ValuePropertyAccessParameters accessParams = null;
+
+  /**
+   * Erzeugt eine Eigenschaft.
+   * @param name   Bezeichnung der Eigenschaft
+   * @param type   Typ der Eigenschaft
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ValueProperty(String name, PropertyType type, ValuePropertyAccessParameters params) {
+    super(name,type);
+    this.accessParams = params;
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name   Bezeichnung der Eigenschaft
+   * @param type   Typ der Eigenschaft
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ValueProperty(String name, PropertyType type) {
+    this(name,type,ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft.
+   * @param name   Bezeichnung der Eigenschaft
+   * @param sample Bestimmt die Element-Struktur, die in der Eigenschaft
+   *               gespeichert werden kann
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ValueProperty(String name, ObjectStructure sample, ValuePropertyAccessParameters params) {
+    this(name,new ValuePropertyType(sample),params);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name   Bezeichnung der Eigenschaft
+   * @param sample Bestimmt die Element-Struktur, die in der Eigenschaft
+   *               gespeichert werden kann
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ValueProperty(String name, ObjectStructure sample) {
+    this(name,new ValuePropertyType(sample),ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. <code>int.class</code>).
+   * @param name   Bezeichnung der Eigenschaft
+   * @param type   Daten-Typ der Eigenschaft
+   * @param params Parameter fuer die Verwaltung des Zugriffsrechts
+   * @see schmitzm.data.property.ValuePropertyAccessParameters
+   */
+  public ValueProperty(String name, Class type, ValuePropertyAccessParameters params) {
+    this(name,ValuePropertyType.createValuePropertyType(type),params);
+  }
+
+  /**
+   * Erzeugt eine Eigenschaft. Als Typ kann auch ein Build-in-Type angegeben
+   * werden (z.B. <code>int.class</code>). Der Zugriff auf die Eigenschaft ist
+   * uneingeschraengt (beliebig viele Lese- und beliebig viele Schreibrechte).
+   * @param name Bezeichnung der Eigenschaft
+   * @param type Daten-Typ der Eigenschaft
+   * @see schmitzm.data.property.ValuePropertyAccessParameters#UNLIMITED_ACCESS
+   */
+  public ValueProperty(String name, Class type) {
+    this(name,type,ValuePropertyAccessParameters.UNLIMITED_ACCESS);
+  }
+
+  /**
+   * Liefert den Daten-Typ, der in der Eigenschaft gespeichert werden kann.
+   */
+  public ValuePropertyType getPropertyType() {
+    return (ValuePropertyType)super.getPropertyType();
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung fuer Accessible
+  ///////////////////////////////////////////////////////////////////
+
+  /**
+   * Liefert die Parameter (max. Lese/Schreibzugriff) der Property.
+   * @return ValuePropertyAccessParameters
+   */
+  public ValuePropertyAccessParameters getAccessParameters() {
+    return accessParams;
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Zugriffsrecht (Lesend oder Schreibend)
+   * angefordert wird. Diese Methode prueft, ob das Recht erteilt werden kann.
+   * Wie viele Lese- und Schreibrechte erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.
+   * @param access Zugriffsrecht welches erteilt werden soll
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das angegebene Recht nicht zur Property passt
+   * @see #getAccessParameters()
+   */
+  public void applyAccess(Access access) {
+    if ( access.object != this )
+      throw new AccessViolationException(access,"Access does not belong to an instance of "+this.getClass());
+
+    if ( access instanceof PropertyReadAccess )
+      // Jedes Leserecht wird nur einmal vermerkt
+      if ( !readAccess.contains(access) ) {
+        // Leserechte schon alle vergeben?
+        if ( !getAccessParameters().isReadUnlimited() && readAccess.size() == getAccessParameters().getSimultanReadAccess() )
+          throw new AccessViolationException(access,this.getClass().getName().concat(" can only share ReadAccess to ").concat(String.valueOf(getAccessParameters().getSimultanWriteAccess())).concat(" object"));
+        readAccess.add(access);
+      }
+
+    if ( access instanceof PropertyWriteAccess )
+      // Jedes Schreibrecht wird nur einmal vermerkt
+      if ( !writeAccess.contains(access) ) {
+        // Schreibrechte schon alle vergeben?
+        if ( !getAccessParameters().isWriteUnlimited() && writeAccess.size() == getAccessParameters().getSimultanWriteAccess() )
+          throw new AccessViolationException(access,this.getClass().getName().concat(" can only share WriteAccess to ").concat(String.valueOf(getAccessParameters().getSimultanWriteAccess())).concat(" object"));
+        writeAccess.add(access);
+      }
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Zugriffsrecht (Lesend oder Schreibend)
+   * wieder zurueckgegeben wird.
+   * @param access Zugriffsrecht welches zurueckgegeben werden soll
+   * @exception schmitzm.data.property.AccessViolationException falls
+   *            das angegebene Recht nicht zur Property passt
+   */
+  public void releaseAccess(Access access) {
+    if ( access.object != this )
+      throw new AccessViolationException(access,"Access does not belong to an instance of "+this.getClass());
+
+    if ( access instanceof PropertyReadAccess )
+      readAccess.remove(access);
+    if ( access instanceof PropertyWriteAccess )
+      writeAccess.remove(access);
+  }
+
+  /**
+   * Entzieht saemtlichen Zugriffsrechten, die aktuell fuer das Objekt
+   * verteilt sind, die Gueltigkeit.
+   */
+  public void disposeAllAccess() {
+    // Bemerke: das Entfernen aus dem Vecor geschieht durch
+    //          releaseAccess(.) welche von Access.dispose()
+    //          aufgerufen wird!!
+    for (;!readAccess.isEmpty();)
+      ((Access)readAccess.elementAt(0)).dispose();
+    for (;!writeAccess.isEmpty();)
+      ((Access)writeAccess.elementAt(0)).dispose();
+  }
+
+  /**
+   * Liefert die Anzahl an Zugriffsrechten einer bestimmten Art, die aktuell
+   * fuer das Objekt verteilt sind.
+   */
+  public int getAccessCount(Class c) {
+    int accessCount = 0;
+    if ( c==null || c.isAssignableFrom(PropertyReadAccess.class) )
+      accessCount += readAccess.size();
+    if ( c==null || c.isAssignableFrom(PropertyWriteAccess.class) )
+      accessCount += writeAccess.size();
+    return accessCount;
+  }
+
+  /**
+   * Liefert die Gesamz-Anzahl an Zugriffsrechten, die aktuell fuer das Objekt
+   * verteilt sind.
+   */
+  public int getAccessCount() {
+    return getAccessCount(null);
+  }
+
+
+  /**
+   * Liefert ein Leserecht auf die Property.
+   * Wie viele Leserechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.
+   * @param owner Objekt, welches das Recht beantragt
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public PropertyReadAccess getReadAccess(Object owner) {
+    return new PropertyReadAccess(this,owner);
+  }
+
+  /**
+   * Liefert ein Schreibrecht auf die Property.
+   * Wie viele Schreibrechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.
+   * @param owner Objekt, welches das Recht beantragt
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public PropertyWriteAccess getWriteAccess(Object owner) {
+    return new PropertyWriteAccess(this,owner);
+  }
+
+  /**
+   * Liefert ein Leserecht auf die Property, welches nach einmaligem
+   * Zugriff automatisch ungueltig wird.<br>
+   * Wie viele Leserechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public PropertyReadAccess getOneTimeReadAccess() {
+    return new PropertyReadAccess(this,this,1);
+  }
+
+  /**
+   * Liefert ein Schreibrecht auf die Property, welches nach einmaligem
+   * Zugriff automatisch ungueltig wird.<br>
+   * Wie viele Schreibrechte gleichzeitig erteilt werden koennen ist ueber die
+   * Access-Parametern festgelegt.
+   * @see #applyAccess(Access)
+   * @see #getAccessParameters()
+   */
+  public PropertyWriteAccess getOneTimeWriteAccess() {
+    return new PropertyWriteAccess(this,this,1);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Deklarationen fuer ValueProperty
+  ///////////////////////////////////////////////////////////////////
+
+  /**
+   * Liefert den/einen Wert der Eigenschaft.<br>
+   * <code>coords</code> spezifizieren (optional) die "Koordinaten", an denen
+   * der Wert in der Eigenschaft zu finden ist.
+   * Fuer skalare Eigenschaften darf/braucht diese Angabe nicht gemacht zu
+   * werden. Fuer Listen und 1-dim. Matrizen (Arrays) darf nur ein Wert
+   * angegeben werden. Fuer mehr-dimensionale Matrizen muessen entsprechend
+   * der Dimension mehr Koordinaten angegeben werden (als einzelne Parameter oder
+   * als ein Array). z.B. fuer 2-dim. Matrix:<br>
+   * <code>getValue(10,13)</code> oder <code>getValue( new int[] {10,13} )</code><br>
+   * <br>
+   * Kann (seit JDK1.5.0) auch genutzt werden, wenn es sich bei der Property um einen
+   * Build-in-Type handelt.
+   * @param coords optionale Koordinaten
+   */
+  protected abstract Object getValue(int... coords);
+
+  /**
+   * Setzt den/einen Wert der Eigenschaft.<br>
+   * <code>coords</code> spezifizieren (optional) die "Koordinaten", an denen
+   * der Wert in der Eigenschaft zu finden ist.
+   * Fuer skalare Eigenschaften darf/braucht diese Angabe nicht gemacht zu
+   * werden. Fuer Listen und 1-dim. Matrizen (Arrays) darf nur ein Wert
+   * angegeben werden. Fuer mehr-dimensionale Matrizen muessen entsprechend
+   * der Dimension mehr Koordinaten angegeben werden (als einzelne Parameter oder
+   * als ein Array). z.B. fuer 2-dim. Matrix:<br>
+   * <code>getValue(10,13)</code> oder <code>getValue( new int[] {10,13} )</code><br>
+   * <br>
+   * Kann (seit JDK1.5.0) auch genutzt werden, wenn es sich bei der Property um einen
+   * Build-in-Type handelt.
+   * Iniziiert ein <code>ObjectChangeEvent</code> fuer die Property.
+   * @param value  neuer Wert fuer die Eigenschaft
+   * @param coords optionale Koordinaten
+   * @exception java.lang.ClassCastException falls das angegebene Objekt nicht
+   *            zum Property-Typ passt
+   */
+  protected abstract void setValue(Object value, int... coords);
+
+
+  ///////////////////////////////////////////////////////////////////
+  // Implementierung von ObjectStructure
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert <code>true</code>, da jede Property einen Namen besitzt.
+   */
+  public boolean isStructureNamed() {
+    return true;
+  }
+
+  /**
+   * Liefert den Namen der Eigenschaft.
+   * @see #getName()
+   */
+  public String getStructureName() {
+    return getName();
+  }
+
+  /**
+   * Liefert 1, da eine Property immer genau eine Eigenschaft darstellt.
+   */
+  public int getAttrCount() {
+    return 1;
+  }
+
+  /**
+   * Liefert eine Liste, in der genau 1 Element enthalten ist. Dabei handelt es
+   * sich um eine {@link ObjectStructure}, falls die Property einen strukturierten
+   * Inhalt hat, oder andernfalls eine {@link Class}, die den "allgemeinen"
+   * Eigenschaftstyp darstellt.
+   * @see ValuePropertyType#getTypeStructure()
+   * @see ValuePropertyType#getType()
+   */
+  public Enumeration getAttrTypes() {
+    return new Enumeration() {
+      private int nextCalls = 0;
+
+      public boolean hasMoreElements() {
+        return nextCalls == 0;
+      }
+
+      public Object nextElement() {
+        if (!hasMoreElements())
+          throw new NoSuchElementException(getClass().getSimpleName().concat(" only contains ").concat( String.valueOf(getAttrCount()) ).concat(" Attribute!"));
+        nextCalls++;
+        return getPropertyType().isStructured() ? getPropertyType().getTypeStructure() : getType();
+      }
+    };
+  }
+
+  /**
+   * Vergleicht die Struktur der Property mit einer anderen auf Gleichheit.
+   * @param object kann eine andere {@link ValueProperty} sein oder ein
+   *               {@link ValuePropertyType}
+   * @see ValuePropertyType#equalsInStructure(Object)
+   */
+  public boolean equalsInStructure(Object object) {
+    return getPropertyType().equalsInStructure( object );
+  }
+}

Added: trunk/src/schmitzm/data/property/ValuePropertyAccessParameters.java
===================================================================
--- trunk/src/schmitzm/data/property/ValuePropertyAccessParameters.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ValuePropertyAccessParameters.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,74 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+/**
+ * Diese Klasse stellt Rechte-Parameter fuer Wert-Eigenschaften (<code>ValueProperty</code>)
+ * dar. Fuer eine Wert-Eigenschaft koennen Lese- und Schreibrechte vergeben
+ * werden. Ueber die Parameter dieser Klasse kann festgelegt werden, wie
+ * viele Objekte gleichzeitig das jeweilige Recht besitzenm duerfen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ValuePropertyAccessParameters {
+  /** Konstante, die fuer unbegrenzten (Lese- oder Schreib-)Zugriff steht. */
+  public static final int UNLIMITED = -1;
+  /** Vordefinierte Parameter fuer unbegrenzten Lese- und unbegrenzten Schreibzugriff. */
+  public static final ValuePropertyAccessParameters UNLIMITED_ACCESS = new ValuePropertyAccessParameters(UNLIMITED,UNLIMITED);
+  /** Vordefinierte Parameter fuer unbegrenzten Lese- und isolierten Schreibzugriff
+   * (max. ein Schreibzugriffsrecht zu jedem Zeitpunkt). */
+  public static final ValuePropertyAccessParameters DEFAULT_ACCESS   = new ValuePropertyAccessParameters(UNLIMITED,1);
+
+  private int simRead = 0;
+  private int simWrite = 0;
+
+  /**
+   * Erzeugt neue Parameter fuer Wert-Eigenschaften.
+   * @param simultanRead Anzahl an Objekten, die glz. lesen duerfen
+   * @param simultanWrite Anzahl an Objekten, die glz. schreiben duerfen
+   */
+  public ValuePropertyAccessParameters(int simultanRead, int simultanWrite) {
+    this.simRead = simultanRead;
+    this.simWrite = simultanWrite;
+  }
+
+  /**
+   * Liefert die Anzahl an Objekten die gleichzeitig lesen duerfen.
+   * @return <code>UNLIMITED</code> falls beliebig viele Objekte glz. lesen duerfen
+   */
+  public int getSimultanReadAccess() {
+    return this.simRead;
+  }
+
+  /**
+   * Prueft, ob beliebig viele Objekte gleichzeitig lesen duerfen.
+   */
+  public boolean isReadUnlimited() {
+    return getSimultanReadAccess()==UNLIMITED;
+  }
+
+  /**
+   * Liefert die Anzahl an Objekten die gleichzeitig schreiben duerfen.
+   * @return <code>UNLIMITED</code> falls beliebig viele Objekte glz. schreiben duerfen
+   */
+  public int getSimultanWriteAccess() {
+    return this.simWrite;
+  }
+
+  /**
+   * Prueft, ob beliebig viele Objekte gleichzeitig schreiben duerfen.
+   */
+  public boolean isWriteUnlimited() {
+    return getSimultanWriteAccess()==UNLIMITED;
+  }
+
+}

Added: trunk/src/schmitzm/data/property/ValuePropertyType.java
===================================================================
--- trunk/src/schmitzm/data/property/ValuePropertyType.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/ValuePropertyType.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,240 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.data.property;
+
+import schmitzm.data.ObjectStructure;
+import schmitzm.data.ObjectStructureUtil;
+import schmitzm.temp.BaseTypeUtil;
+
+/**
+ * Diese Klasse repraesentiert den Datentyp einer {@linkplain ValueProperty Wert-Eigenschaft}.
+ * Der Datentyp kann aus einer einfachen Klasse bestehen, oder aus oder aus
+ * einer komplexen  {@linkplain ObjectStructure Objekt-Struktur}.
+ * <b>Bemerke:</b><br>
+ * Fuer Basis-Datentypen (<code>int</code>, <code>double</code>,
+ * <code>String</code>, ...) sollten keine neuen
+ * <code>ValuePropertyType</code>-Instanzen erzeugt werden, sondern die
+ * bereits implementierten Konstanten verwendet werden!
+ * @see #TYPE_BYTE
+ * @see #TYPE_SHORT
+ * @see #TYPE_INT
+ * @see #TYPE_LONG
+ * @see #TYPE_FLOAT
+ * @see #TYPE_DOUBLE
+ * @see #TYPE_BOOLEAN
+ * @see #TYPE_CHAR
+ * @see #TYPE_STRING
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ValuePropertyType extends PropertyType {
+  /**
+   * Speichert die Struktur der Objekte, die fuer den Property-Wert zulaessig
+   * ist.
+   */
+  protected ObjectStructure structure = null;
+
+  /** Repraesentiert eine vorgefertigte {@link Byte}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_BYTE    = new ValuePropertyType(Byte.class);
+  /** Repraesentiert eine vorgefertigte {@link Short}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_SHORT   = new ValuePropertyType(Short.class);
+  /** Repraesentiert eine vorgefertigte {@link Integer}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_INT     = new ValuePropertyType(Integer.class);
+  /** Repraesentiert eine vorgefertigte {@link Long}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_LONG    = new ValuePropertyType(Long.class);
+  /** Repraesentiert eine vorgefertigte {@link Float}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_FLOAT   = new ValuePropertyType(Float.class);
+  /** Repraesentiert eine vorgefertigte {@link Double}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_DOUBLE  = new ValuePropertyType(Double.class);
+  /** Repraesentiert eine vorgefertigte {@link Boolean}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_BOOLEAN = new ValuePropertyType(Boolean.class);
+  /** Repraesentiert eine vorgefertigte {@link Character}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_CHAR    = new ValuePropertyType(Character.class);
+  /** Repraesentiert eine vorgefertigte {@link String}-Eigenschaft. **/
+  public static final ValuePropertyType TYPE_STRING  = new ValuePropertyType(String.class);
+
+  /**
+   * Liefert einen passenden Property-Typ zu einem Datentyp oder einem
+   * Beispiel-Objekt. Handelt es sich um
+   * einen Basis-Datentyp (<code>Integer</code>, <code>Double</code>,
+   * <code>String</code>, ...) wird eine der konstanten
+   * <code>ValuePropertyType</code>-Instanzen zurueckgegeben. Andernfalls wird
+   * versucht den Beispielwert ueber den Standard-Konstruktor der angegebenen
+   * Klasse zu erzeugen. Schlaegt dies fehl, wird der Beispiel-Wert auf
+   * <code>null</code> gesetzt.
+   * @param sample Typ den die Eigenschaft aufnehmen soll (<code>Class</code>,
+   *               <code>ObjectStructure</code> oder Beispiel-<code>Object</code>)
+   * @see #TYPE_BYTE
+   * @see #TYPE_SHORT
+   * @see #TYPE_INT
+   * @see #TYPE_LONG
+   * @see #TYPE_FLOAT
+   * @see #TYPE_DOUBLE
+   * @see #TYPE_BOOLEAN
+   * @see #TYPE_CHAR
+   * @see #TYPE_STRING
+   */
+  public static ValuePropertyType createValuePropertyType(Object sample) {
+    if ( sample == null )
+      throw new UnsupportedOperationException("Can not create a PropertyType from null");
+
+    // Wenn es sich um eine Structure handelt, diese verwenden
+    if ( sample instanceof ObjectStructure )
+      return new ValuePropertyType( (ObjectStructure)sample );
+
+    // Fuer Basis-Datentypen die Konstanten verwenden
+    // Bemerke: Leider werden fuer Class-Objekte nicht die BaseTypeUtil-Methoden
+    //          fuer Class aufgerufen, sondern fuer Object!! (warum?)
+    //          Deshalb muss hier leider fallunterschieden und explizit
+    //          gecastet werden
+    if ( sample instanceof Class ) {
+      if (BaseTypeUtil.isByte((Class)sample))
+        return TYPE_BYTE;
+      if (BaseTypeUtil.isShort((Class)sample))
+        return TYPE_SHORT;
+      if (BaseTypeUtil.isInteger((Class)sample))
+        return TYPE_INT;
+      if (BaseTypeUtil.isLong((Class)sample))
+        return TYPE_LONG;
+      if (BaseTypeUtil.isFloat((Class)sample))
+        return TYPE_FLOAT;
+      if (BaseTypeUtil.isDouble((Class)sample))
+        return TYPE_DOUBLE;
+      if (BaseTypeUtil.isBoolean((Class)sample))
+        return TYPE_BOOLEAN;
+      if (BaseTypeUtil.isCharacter((Class)sample))
+        return TYPE_CHAR;
+      if (BaseTypeUtil.isString((Class)sample))
+        return TYPE_STRING;
+      // sonst Property-Typ neu erzeugen
+      return new ValuePropertyType((Class)sample);
+    } else {
+      if (BaseTypeUtil.isByte(sample))
+        return TYPE_BYTE;
+      if (BaseTypeUtil.isShort(sample))
+        return TYPE_SHORT;
+      if (BaseTypeUtil.isInteger(sample))
+        return TYPE_INT;
+      if (BaseTypeUtil.isLong(sample))
+        return TYPE_LONG;
+      if (BaseTypeUtil.isFloat(sample))
+        return TYPE_FLOAT;
+      if (BaseTypeUtil.isDouble(sample))
+        return TYPE_DOUBLE;
+      if (BaseTypeUtil.isBoolean(sample))
+        return TYPE_BOOLEAN;
+      if (BaseTypeUtil.isCharacter(sample))
+        return TYPE_CHAR;
+      if (BaseTypeUtil.isString(sample))
+        return TYPE_STRING;
+      // sonst Property-Typ neu erzeugen
+      return new ValuePropertyType(sample.getClass());
+    }
+  }
+
+  /**
+   * Erzeugt einen neuen Eigenschaftstyp.
+   * <b>Bemerke:</b><br>
+   * Fuer Basis-Datentypen (<code>Integer</code>, <code>Double</code>,
+   * <code>String</code>, ...) sollte nicht dieser Konstruktor verwendet werden,
+   * sondern eine der konstanten <code>ValuePropertyType</code>-Instanzen.
+   * @param sample Beispielwert
+   * @exception java.lang.UnsupportedOperationException falls der Beispiel-Wert
+   *            keine Instanz des Eigenschaftstyp ist.
+   * @see #TYPE_BYTE
+   * @see #TYPE_SHORT
+   * @see #TYPE_INT
+   * @see #TYPE_LONG
+   * @see #TYPE_FLOAT
+   * @see #TYPE_DOUBLE
+   * @see #TYPE_BOOLEAN
+   * @see #TYPE_CHAR
+   * @see #TYPE_STRING
+   */
+  public ValuePropertyType(ObjectStructure sample) {
+    super(sample.getClass());
+    this.structure = sample;
+  }
+
+  /**
+   * Erzeugt einen neuen Eigenschaftstyp. Der Beispielwert wird ueber den
+   * Standard-Konstruktor der Typ-Klasse zu instanziieren versucht. Schlaegt
+   * dies fehl, wird der Beispiel-Wert auf <code>null</code> gesetzt.<br>
+   * <b>Bemerke:</b><br>
+   * Fuer Basis-Datentypen (<code>Integer</code>, <code>Double</code>,
+   * <code>String</code>, ...) sollte nicht dieser Konstruktor verwendet werden,
+   * sondern eine der konstanten <code>ValuePropertyType</code>-Instanzen.
+   * @param type Typ den die Eigenschaft aufnehmen kann
+   * @see #TYPE_BYTE
+   * @see #TYPE_SHORT
+   * @see #TYPE_INT
+   * @see #TYPE_LONG
+   * @see #TYPE_FLOAT
+   * @see #TYPE_DOUBLE
+   * @see #TYPE_BOOLEAN
+   * @see #TYPE_CHAR
+   * @see #TYPE_STRING
+   */
+  public ValuePropertyType(Class type) {
+    super(type);
+    this.structure = null;
+  }
+
+  /**
+   * Prueft, ob der Eigenschaftstyp aus einer festen Struktur besteht.
+   * @return <code>false</code> falls der Eigenschaftstyp lediglich durch eine Klasse
+   *         festgelegt ist
+   */
+  public boolean isStructured() {
+    return getTypeStructure() != null;
+  }
+
+  /**
+   * Liefert die (feste) Struktur des Eigenschaftstyp.
+   * @return <code>null</code> falls der Eigenschaftstyp lediglich durch eine Klasse
+   *         festgelegt ist
+   */
+  public ObjectStructure getTypeStructure() {
+    return structure;
+  }
+
+  /**
+   * Prueft, ob der Datentyp mit einem anderen <b>strukturell</b> identisch ist.
+   * Der Inhalt der Eigenschaft wird nicht beachtet
+   * @param typeObj kann eine <code>ValuePropertyType</code>-Instanz sein, oder
+   *                auch eine {@link ValueProperty}-Instanz
+   */
+  public boolean equalsInStructure(Object typeObj) {
+    // ValuePropertyType-Instanz aus uebergebenen Objekt ermitteln
+    ValuePropertyType type = null;
+    if ( typeObj instanceof ValuePropertyType )
+      type = (ValuePropertyType)typeObj;
+    if ( typeObj instanceof ValueProperty )
+      type = ((ValueProperty)typeObj).getPropertyType();
+    // ein vergleichbarere Typ
+    if ( type == null )
+      return false;
+
+    // wenn beide strukturiert sind, die Struktur vergleichen
+    if ( isStructured() ) {
+      if ( !type.isStructured() )
+          return false;
+      return ObjectStructureUtil.compareObjectStructures(getTypeStructure(),type.getTypeStructure()) == ObjectStructureUtil.EQUAL;
+    } else {
+      // wenn beide nicht strukturiert sind, die Klassen vergleichen
+      if ( type.isStructured() )
+          return false;
+      return getType().equals( type.getType() );
+    }
+  }
+
+ }

Added: trunk/src/schmitzm/data/property/package.html
===================================================================
--- trunk/src/schmitzm/data/property/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/property/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,9 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen (insbesondere Interfaces) für die Property-Struktur von Objekten.
+	Hierzu zählt auch die Zugriffskontrolle auf einzelne Eigenschaften, die auf den Klassen
+	{@link schmitzm.data.property.Access} und {@link schmitzm.data.property.Accessible}
+	aufgebaut ist. Eine {@linkplain schmitzm.data.property.ValueProperty Wert-Eigenschaft} kann
+	z.B. nur über ein entsprechendes Zugriffsrecht (lesend/schreibend) angesprochen werden.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/data/resource/locales/DataResourceBundle.properties
===================================================================
--- trunk/src/schmitzm/data/resource/locales/DataResourceBundle.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/resource/locales/DataResourceBundle.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,11 @@
+# -----------------------------------------------------------
+# ------ Default Translations (english) for components ------
+# ------ in Package schmitz.data                       ------
+# -----------------------------------------------------------
+
+RasterDim.GridWidth=Grid width
+RasterDim.GridHeight=Grid height
+RasterDim.CellWidth=Cell width
+RasterDim.CellHeight=Cell height
+RasterDim.SampleType=Grid sample type
+RasterDim.ErrorMess=${0} of '${1}' is incompatible to '${2}'...

Added: trunk/src/schmitzm/data/resource/locales/DataResourceBundle_de.properties
===================================================================
--- trunk/src/schmitzm/data/resource/locales/DataResourceBundle_de.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/data/resource/locales/DataResourceBundle_de.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,11 @@
+# ------------------------------------------------
+# ------ German Translations for components ------
+# ------ in Package schmitz.data            ------
+# ------------------------------------------------
+
+RasterDim.GridWidth=Raster-Breite
+RasterDim.GridHeight=Raster-Hoehe
+RasterDim.CellWidth=Zellen-Breite
+RasterDim.CellHeight=Zellen-Hoehe
+RasterDim.SampleType=Raster-Datentyp
+RasterDim.ErrorMess=${0} von '${1}' ist nicht kompatibel zu '${2}'...

Added: trunk/src/schmitzm/geotools/FilterUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/FilterUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/FilterUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,38 @@
+package schmitzm.geotools;
+
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.filter.Filter;
+import org.geotools.filter.FilterFactoryFinder;
+import org.geotools.filter.FilterFactoryImpl;
+import org.geotools.filter.visitor.DuplicatorFilterVisitor;
+import org.opengis.filter.FilterFactory2;
+
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+
+/**
+ * Diese Klasse enthaelt statische Helper-Methoden fuer die Arbeit mit
+ * {@link Filter}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * 
+ * @version 1.01
+ */
+public class FilterUtil {
+  /** Instanz von {@link FilterFactoryImpl}. */
+  public static final FilterFactoryImpl FILTER_FAC   = (FilterFactoryImpl)FilterFactoryFinder.createFilterFactory();
+  /** Globale Instanz von {@link FilterFactory2} **/
+  public static final FilterFactory2 FILTER_FAC2 = CommonFactoryFinder.getFilterFactory2(null);
+  /** Instanz von {@link GeometryFactory}. */
+  public static final GeometryFactory   GEOMETRY_FAC = new GeometryFactory();
+
+  /**
+   * Erzeugt eine Kopie eines Filters
+   * @param filter ein Filter
+   */
+  public static Filter cloneFilter(Filter filter) {
+    DuplicatorFilterVisitor dfv = new DuplicatorFilterVisitor(FILTER_FAC,false);
+    dfv.visit( filter );
+    Filter newFilter = (Filter)dfv.getCopy();
+    return newFilter;
+  }
+}

Added: trunk/src/schmitzm/geotools/GTUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/GTUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/GTUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,171 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools;
+
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.geotools.geometry.jts.JTS;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.referencing.CRS;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.geometry.Envelope; // gt2-2.4.2
+//import org.opengis.spatialschema.geometry.Envelope; // gt2-2.3.4
+
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse enthaelt allgemeine Funktionen fuer die Arbeit mit Geotools.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GTUtil {
+  private static Logger LOGGER = Logger.getLogger( GTUtil.class.getName() );
+
+  /** Konstante fuer das CRS "WGS84" (erzeugt als "EPSG:4326")*/
+  public static CoordinateReferenceSystem WGS84 = null;
+
+  // Initialisierung der CRS-Konstanten
+  static {
+    try {
+      WGS84 = CRS.decode("EPSG:4326",true); // lat/lon (WGS84)
+    } catch (Exception err) {
+    	LOGGER.warn("Exception while creating default WGS84 from code 'EPSG:4326'");
+    }
+  }
+
+  /**
+   * Erzeugt ein {@link CoordinateReferenceSystem} aus einer String-Definition.
+   * Akzeptiert wird ein EPSG-Code "EPSG:..." oder eine WKT-Definition des CRS
+   * @param crsDef Definition fuer das CRS.
+   * @return {@code null}, falls der String nicht zu einem CRS dekodiert werden
+   *         kann
+   */
+  public static CoordinateReferenceSystem createCRS(String crsDef) {
+    if ( crsDef == null || crsDef.trim().equals("") )
+      return null;
+
+    CoordinateReferenceSystem crs = null;
+    try {
+        // Akzeptiert wird: EPSG-Code
+        if (crsDef.startsWith("EPSG:"))
+          crs = createCRS_EPSG( crsDef );
+        // Wenn EPSG-Dekodierung nicht erfolgreich: WKT versuchen
+        if ( crs == null )
+          crs = CRS.parseWKT( crsDef );
+    } catch (Exception err) {
+      LOGGER.error("Error while creating CRS",err);
+    }
+    return crs;
+  }
+
+  /**
+   * Erzeugt ein {@link CoordinateReferenceSystem} aus einem (EPSG-)Code.
+   * Entspricht {@link CRS#decode(String,boolean) CRS#decode(String,true)}.
+   * Exceptions werden jedoch abgefangen und stattdessen {@code null} zurueckgegeben.
+   * @param code Code fuer das CRS.
+   */
+  public static CoordinateReferenceSystem createCRS_EPSG(String code) {
+    try {
+      return CRS.decode(code,true);
+    } catch (Exception err) {
+      LOGGER.error("Error while creating CRS",err);
+      return null;
+    }
+  }
+
+  /**
+   * Erzeugt ein UTM-CoordinateReferenceSystem.
+   * @param zone UTM-Zone
+   */
+  public static CoordinateReferenceSystem createCRS_UTM(int zone) {
+    DecimalFormat format_99 = new DecimalFormat("00");
+    return createCRS( "EPSG:326"+format_99.format(zone) );
+  }
+
+  /**
+   * Erzeugt einen {@link Envelope2D} aus einem {@link Rectangle2D} .
+   * @param env Georeferenz und Ausdehnung
+   * @param crs CoordinateReferenceSystem
+   */
+  public static Envelope2D createEnvelope2D(Rectangle2D env, CoordinateReferenceSystem crs) {
+    return new Envelope2D(crs, env.getX(), env.getY(), env.getWidth(),
+                          env.getHeight());
+  }
+
+  /**
+   * Liefert alle zur Verfuegung stehenden {@linkplain CoordinateReferenceSystem CRS} fuer eine
+   * Authority.
+   * @param authority      Authority fuer die die CRS geliefert werden (z.B. {@code "EPSG"})
+   * @param longitudeFirst {@code true} erzwingt die Achsenordnung (longitude, latitude). Siehe
+   *                       {@link CRS#decode(String, boolean)} (Bemerkung: {@code false} bedeutet
+   *                       System-Default, <b>nicht</b> (latitude, longitude)!)
+   * @param suppressWarnings wenn {@code true} werden Warnmeldungen unterdrueckt
+   * @return eine nach dem CRS-Namen geordnete Map
+   */
+  public static final SortedMap<String,CoordinateReferenceSystem> getAvailableCRSByName(String authority, boolean longitudeFirst, boolean suppressWarnings) {
+    SortedMap<String,CoordinateReferenceSystem> mapByCode = getAvailableCRSByCode(authority, longitudeFirst, suppressWarnings);
+    TreeMap<String,CoordinateReferenceSystem>   mapByName = new TreeMap<String, CoordinateReferenceSystem>();
+    for (String code : mapByCode.keySet()) {
+      CoordinateReferenceSystem crs = mapByCode.get(code);
+      mapByName.put( crs.getName().toString()+" ["+code+"]", crs );
+    }
+    return mapByName;
+  }
+
+  /**
+   * Liefert alle zur Verfuegung stehenden {@linkplain CoordinateReferenceSystem CRS} fuer eine
+   * Authority.
+   * @param authority      Authority fuer die die CRS geliefert werden (z.B. {@code "EPSG"})
+   * @param longitudeFirst {@code true} erzwingt die Achsenordnung (longitude, latitude). Siehe
+   *                       {@link CRS#decode(String, boolean)} (Bemerkung: {@code false} bedeutet
+   *                       System-Default, <b>nicht</b> (latitude, longitude)!)
+   * @param suppressWarnings wenn {@code true} werden Warnmeldungen unterdrueckt
+   * @return eine nach dem CRS-Code geordnete Map
+   */
+  public static final SortedMap<String,CoordinateReferenceSystem> getAvailableCRSByCode(String authority, boolean longitudeFirst, boolean suppressWarnings) {
+    TreeMap<String,CoordinateReferenceSystem> map = new TreeMap<String, CoordinateReferenceSystem>();
+    for (String code : (Set<String>)CRS.getSupportedCodes(authority)) {
+      if ( !code.startsWith(authority) )
+        code = authority + ":" + code;
+      try {
+        CoordinateReferenceSystem crs = CRS.decode(code, longitudeFirst);
+        map.put(code, crs);
+      } catch (Exception err) {
+        if ( !suppressWarnings )
+          LOGGER.warn("CRS could not be decoded: "+code);
+      }
+    }
+    return map;
+  }
+
+  /**
+   * Berechnet den Schnitt zweier {@link Envelope Envelopes}.
+   * @param env1 erster Envelope
+   * @param env2 zweiter Envelope
+   * @param crs  {@link CoordinateReferenceSystem} fuer den Schnitt-Envelope
+   */
+  public static Envelope intersectEnvelope(Envelope env1, Envelope env2, CoordinateReferenceSystem crs) {
+    // Envelopes in JTS umwandeln
+    com.vividsolutions.jts.geom.Envelope env1JTS = new com.vividsolutions.jts.geom.Envelope(
+      env1.getMinimum(0),env1.getMaximum(0),env1.getMinimum(1),env1.getMaximum(1));
+    com.vividsolutions.jts.geom.Envelope env2JTS = new com.vividsolutions.jts.geom.Envelope(
+      env2.getMinimum(0),env2.getMaximum(0),env2.getMinimum(1),env2.getMaximum(1));
+    com.vividsolutions.jts.geom.Envelope intersetionEnvJTS = env1JTS.intersection(env2JTS);
+    // Subset nur bzgl. des Bereichs in dem auch das Raster liegt
+    return JTS.getEnvelope2D(intersetionEnvJTS,crs);
+  }
+}

Added: trunk/src/schmitzm/geotools/JTSUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/JTSUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/JTSUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,73 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools;
+
+import org.geotools.geometry.jts.JTS;
+import org.geotools.referencing.CRS;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import org.apache.log4j.Logger;
+import org.opengis.geometry.DirectPosition; // gt2-2.4.2
+//import org.opengis.spatialschema.geometry.DirectPosition; // gt2-2.3.1
+
+/**
+ * Diese Klasse enthaelt allgemeine Funktionen fuer die Arbeit mit den
+ * in Geotools verwendeten JTS-Komponenten vereinfacht.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.1
+ */
+public class JTSUtil {
+  private static Logger LOGGER = Logger.getLogger( JTSUtil.class.getName() );
+
+  /**
+   * Created an (CRS-less) JTS-Envelope from an OpenGIS-Envelope.
+   * @param envelope an OpenGIS-Envelope
+   * @return an JTS-Envelope
+   */
+  public static Envelope createEnvelope(org.opengis.geometry.Envelope envelope) { // gt2-2.4.2
+//  public static Envelope createEnvelope(org.opengis.spatialschema.geometry.Envelope envelope) { // gt2-2.3.4
+    return new Envelope(
+      envelope.getMinimum(0), // X1
+      envelope.getMaximum(0), // X2
+      envelope.getMinimum(1), // Y1
+      envelope.getMaximum(1)  // Y2
+    );
+  }
+
+  /**
+   * Transformiert einen JTS-Envelope von einem CRS in ein anderes.
+   * @param sourceEnv JTS-Envelope
+   * @param sourceCRS CRS von {@code sourceEnv}
+   * @param destCRS   CRS in das umgerechnet werden soll
+   * @see CRS#findMathTransform(CoordinateReferenceSystem,CoordinateReferenceSystem)
+   * @see JTS#transform(Envelope,MathTransform)
+   */
+  public static Envelope transformEnvelope(Envelope sourceEnv, CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem destCRS) {
+    Envelope destEnv = null;
+      MathTransform transform;
+	try {
+		transform = CRS.findMathTransform(sourceCRS,destCRS);
+		destEnv = JTS.transform(sourceEnv,transform);
+	} catch (FactoryException e) {
+		LOGGER.warn("CRS tranformation for JTS envelope not successfully",e);
+	} catch (TransformException e) {
+		LOGGER.warn("CRS tranformation for JTS envelope not successfully",e);
+	}
+    return destEnv;
+  }
+}

Added: trunk/src/schmitzm/geotools/feature/AbstractAutoValueGenerator.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/AbstractAutoValueGenerator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/AbstractAutoValueGenerator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,57 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import org.geotools.feature.AttributeType;
+
+/**
+ * This interface represents a generator to create an attribute default
+ * value individually.
+ * @see FeatureUtil#registerAutoValueGenerator(AttributeType, AutoValueGenerator)
+ * @see FeatureUtil#getAutoValueGenerator(AttributeType)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public abstract class AbstractAutoValueGenerator<E> implements AutoValueGenerator<E> {
+  /** Holds the first value generated by {@link #generateNextValue()}. */
+  protected E firstValue;
+
+  /** Holds the last value generated by {@link #generateNextValue()}. */
+  protected E lastValue;
+
+  /**
+   * Creates a new generator.
+   */
+  public AbstractAutoValueGenerator() {
+    this(null);
+  }
+
+  /**
+   * Creates a new generator.
+   * @param firstValue first value generated by {@link #generateNextValue()}
+   */
+  public AbstractAutoValueGenerator(E firstValue) {
+    resetAutoValue(firstValue);
+  }
+
+  /**
+   * Resets the generator, so the next {@link #generateNextValue()} call
+   * generates {@code firstValue} as auto value.
+   * @param firstValue next value to generate (if {@code null} the
+   *                   first value is reset to a previous set first
+   *                   value)
+   */
+  public void resetAutoValue(E firstValue) {
+    if ( firstValue != null )
+      this.firstValue = firstValue;
+    this.lastValue  = null;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/feature/AttributeFilter.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/AttributeFilter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/AttributeFilter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,205 @@
+package schmitzm.geotools.feature;
+
+import org.geotools.filter.FilterFactoryImpl;
+import org.geotools.filter.AbstractFilterImpl;
+import org.geotools.feature.Feature;
+// fuer Doku
+import org.geotools.filter.Filter;
+
+/**
+ * Diese Klasse stellt einen {@link Filter} dar, der einen Attributwert mit einem
+ * konstanten Vergleichswert vergleicht.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class AttributeFilter extends AbstractFilterImpl implements Cloneable {
+  /** Attribut-Filter, der auf "Attributwert = Konstante" prueft. */
+  public static final AttributeFilter EQUALS = new AttributeFilter(CompareType.EQUALS);
+  /** Attribut-Filter, der auf "Attributwert > Konstante" prueft. */
+  public static final AttributeFilter GT = new AttributeFilter(CompareType.GT);
+  /** Attribut-Filter, der auf "Attributwert >= Konstante" prueft. */
+  public static final AttributeFilter GE = new AttributeFilter(CompareType.GE);
+  /** Attribut-Filter, der auf "Attributwert < Konstante" prueft. */
+  public static final AttributeFilter LT = new AttributeFilter(CompareType.LT);
+  /** Attribut-Filter, der auf "Attributwert <= Konstante" prueft. */
+  public static final AttributeFilter LE = new AttributeFilter(CompareType.LE);
+
+  /** Typ der Vergleichsfunktion fuer den Filter. */
+  public static enum CompareType {
+    /** Vergleich auf Gleichheit (Attributwert = Konstante). */
+    EQUALS,
+    /** Groesser-Vergleich (Attributwert > Konstante). */
+    GT,
+    /** Groesser/Gleich-Vergleich (Attributwert >= Konstante). */
+    GE,
+    /** Kleiner-Vergleich (Attributwert < Konstante). */
+    LT,
+    /** Kleiner/Gleich-Vergleich (Attributwert <= Konstante). */
+    LE,
+  }
+
+  /** Art des Vergleichs in {@link #evaluate(Feature)}. */
+  protected CompareType compType = null;
+  /** Name des Feature-Attributs, das mit der {@linkplain #compValue Konstante}
+   *  verglichen wird.*/
+  protected String attrName = "";
+  /** Konstante, mit der das {@linkplain #attrName Feature-Attibut}
+   *  vergleichen wird. */
+  protected Object compValue = null;
+
+  /**
+   * Erzeugt einen neuen Filter. Das Attribut und die Konstante muessen
+   * nachtraeglich durch {@link #setAttributeName(String)} und
+   * {@link #setCompareValue(Object)} gesetzt werden.
+   * @param compType bestimmt die Vergleichs-Funktion
+   * @see #CompareType
+   */
+  public AttributeFilter(CompareType compType) {
+    this(compType, null, null);
+  }
+
+  /**
+   * Erzeugt einen neuen Filter.
+   * @param compType bestimmt die Vergleichs-Funktion
+   * @param attrName Name des Attributs, welches verglichen wird
+   * @param compValue Konstante mit der das Attribut verglichen wird
+   */
+  public AttributeFilter(CompareType compType, String attrName, Object compValue) {
+    super( new FilterFactoryImpl() );
+    this.compType = compType;
+    setAttributeName(attrName);
+    setCompareValue(compValue);
+  }
+
+  /**
+   * Liefert einen Filter, der die inverse Funktion des Filters darstellt.
+   */
+  public AttributeFilter inverse() {
+    switch ( compType ) {
+      case EQUALS: return EQUALS;
+      case GT:     return LE;
+      case GE:     return LT;
+      case LT:     return GE;
+      case LE:     return GT;
+    }
+    throw new UnsupportedOperationException("Compare type can not be inverted: "+compType);
+  }
+
+  /**
+   * Setzt die Konstante, mit der der Attribut-Wert verglichen wird.
+   * @param compValue Vergleichswert
+   */
+  public void setCompareValue(Object compValue) {
+    this.compValue = compValue;
+  }
+
+  /**
+   * Liefert die Konstante, mit der der Attribut-Wert verglichen wird.
+   */
+  public Object getCompareValue() {
+    return this.compValue;
+  }
+
+  /**
+   * Setzt das Attribut, das mit der Konstanten verglichen wird.
+   * @param attrName Attribut-Name
+   */
+  public void setAttributeName(String attrName) {
+    this.attrName = attrName;
+  }
+
+  /**
+   * Liefert das Attribut, das mit der Konstanten verglichen wird.
+   */
+  public Object getAttributeName() {
+    return this.attrName;
+  }
+
+  /**
+   * Liefert den Vergleichstype mit dem das Attribut und die Konstanten
+   * verglichen werden.
+   */
+  public CompareType getCompareType() {
+    return this.compType;
+  }
+
+  /**
+   * Fuehrt den Vergleich durch. Liefert {@code false}, wenn der Attribut-Name
+   * oder die Konstante nicht gesetzt ist oder der Attribut-Wert {@code null} ist.
+   * @param feature Feature dessen Attribut mit der Konstanten verglichen wird
+   */
+  public boolean evaluate(Feature feature) {
+    if ( attrName == null || compValue == null )
+      return false;
+    Object attrValue = feature.getAttribute(attrName);
+    if ( attrValue == null )
+      return false;
+
+    Object attributeValue = attrValue;
+    Object compareValue   = compValue;
+    // Wenn Attribut- und Vergleichswert numerisch sind, dann werden die
+    // Zahlen vergleichen
+    if ( attrValue instanceof Number && compValue instanceof Number ) {
+      attributeValue = ((Number)attrValue).doubleValue();
+      compareValue   = ((Number)compValue).doubleValue();
+    } else if ( attrValue instanceof Number && compValue instanceof String ) {
+      // Versuchen, "compValue" in Zahl umzuwandeln
+      try {
+        compareValue   = Double.parseDouble( (String)compValue );
+        attributeValue = ((Number)attrValue).doubleValue();
+      } catch (NumberFormatException err) {
+      }
+    } else if ( attrValue instanceof String && compValue instanceof Number ) {
+      // Versuchen, "attrValue" in Zahl umzuwandeln
+      try {
+        attributeValue = Double.parseDouble( (String)attrValue );
+        compareValue   = ((Number)compValue).doubleValue();
+      } catch (NumberFormatException err) {
+      }
+    }
+
+    // Vergleich durchfuehren
+    switch( compType ) {
+      case EQUALS: return attributeValue.equals( compareValue );
+      case GT:
+      case GE:
+      case LT:
+      case LE: if ( !(attrValue instanceof Number) || !(compValue instanceof Number) )
+                 return false;
+               double aVal = (Double)attributeValue;
+               double cVal = (Double)compareValue;
+               switch (compType) {
+                 case GT: return aVal >  cVal;
+                 case GE: return aVal >= cVal;
+                 case LT: return aVal <  cVal;
+                 case LE: return aVal <= cVal;
+               }
+    }
+    // sollte nie erreicht werden
+    throw new UnsupportedOperationException("Unknown compare Type: "+compType);
+  }
+
+  /**
+   * Fuehrt den Vergleich durch. Liefert {@code false}, wenn der Attribut-Name
+   * oder die Konstante nicht gesetzt ist oder der Attribut-Wert {@code null} ist.
+   * @param feature Feature dessen Attribut mit der Konstanten verglichen wird
+   * @exception IllegalArgumentException wenn das uebergebene Objekt kein
+   *            {@link Feature} ist
+   */
+  public boolean evaluate(Object feature) {
+    if ( feature != null && !(feature instanceof Feature) )
+      throw new IllegalArgumentException("evaluate(Object) can only be appied ob Feature objects: "+feature.getClass().getSimpleName());
+    return evaluate((Feature)feature);
+  }
+
+  /**
+   * Erzeugt einen {@link AttributeFilter} mit dem gleichen Vergleichstyp,
+   * dem gleichen Attributnamen und der gleichen Vergleichskonstante wie
+   * die Instanz.
+   */
+  public AttributeFilter clone() {
+    return new AttributeFilter( compType, attrName, compValue );
+  }
+
+}
+

Added: trunk/src/schmitzm/geotools/feature/AttributeTypeFilter.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/AttributeTypeFilter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/AttributeTypeFilter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,47 @@
+package schmitzm.geotools.feature;
+
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.type.GeometricAttributeType;
+
+// fuer Doku
+
+/**
+ * Dieses Interface definiert einen Filter fuer die Attribute eines
+ * Features oder einer FeatureCollection. Hiermit lassen sich bestimmte Attribute
+ * ausblenden, so dass z.B. die Geometrie eines Features nicht in einer
+ * Tabelle dargestellt wird.
+ * @see FeatureTypeTableModel
+ * @see FeatureFilterPanel
+ * @see FeatureCollectionPane
+ * @see FeatureCollectionPane.FeatureCollectionTableModel
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface AttributeTypeFilter {
+
+  /**
+   * Standard-Filter, der alle Attribute akzeptiert.
+   */
+  public static final AttributeTypeFilter ALL = new AttributeTypeFilter() {
+    public boolean accept(AttributeType type, int idx) {
+      return true;
+    }
+  };
+
+  /**
+   * Standard-Filter, der {@linkplain GeometricAttributeType Geometrie-Attribute}
+   * ausblendet.
+   */
+  public static final AttributeTypeFilter NO_GEOMETRY = new AttributeTypeFilter() {
+    public boolean accept(AttributeType type, int idx) {
+      return !( type instanceof GeometricAttributeType );
+    }
+  };
+
+  /**
+   * Bestimmt, ob eine Attribut dargestellt wird, oder nicht.
+   * @param type ein Attribut-Typ
+   * @param idx  der Index des Attributs
+   */
+  public boolean accept(AttributeType type, int idx);
+}

Added: trunk/src/schmitzm/geotools/feature/AutoValueGenerator.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/AutoValueGenerator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/AutoValueGenerator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,37 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import org.geotools.feature.AttributeType;
+
+/**
+ * This interface represents a generator to create an attribute default
+ * value individually.
+ * @see FeatureUtil#registerAutoValueGenerator(AttributeType, AutoValueGenerator)
+ * @see FeatureUtil#getAutoValueGenerator(AttributeType)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public interface AutoValueGenerator<E> {
+  /**
+   * Resets the generator, so the next {@link #generateNextValue()} call
+   * generates {@code firstValue} as auto value.
+   * @param firstValue next value to generate (if {@code null} the
+   *                   first value is reset to a previous set first
+   *                   value)
+   */
+  public void resetAutoValue(E firstValue);
+
+  /**
+   * Returns the next value.
+   */
+  public E getNextValue();
+
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureCollectionReader.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureCollectionReader.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureCollectionReader.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,93 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.feature;
+
+import java.io.IOException;
+
+import org.geotools.data.FeatureReader;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.feature.FeatureCollection;
+
+import org.geotools.feature.collection.FeatureIteratorImpl;
+
+/**
+ * Diese Klasse implementiert einen {@link FeatureReader} ueber den
+ * {@link FeatureIterator} einer {@link FeatureCollection}.<br>
+ * <b>Beachte:</b><br>
+ * Die <code>FeatureCollection</code> muss mindestens ein Element enthalten! Ansonsten
+ * kann der {@link FeatureType} nicht ermittelt werden!
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureCollectionReader implements FeatureReader {
+  private FeatureIterator   iterator    = null;
+  private boolean           closed      = false;
+  private FeatureType       featureType = null;
+  private FeatureCollection fc          = null;
+
+  /**
+   * Erzeugt einen neuen FeatureReader.
+   * @param fc FeatureCollection aus der gelesen wird.
+   * @throws java.lang.UnsupportedOperationException falls die FeatureCollection
+   *         kein Element enthaelt.
+   */
+  public FeatureCollectionReader(FeatureCollection fc) {
+    if ( fc.isEmpty() )
+      throw new UnsupportedOperationException("FeatureCollection must contain at least one Feature!");
+    this.fc          = fc;
+    this.featureType = new FeatureIteratorImpl(fc).next().getFeatureType();
+    reset();
+  }
+
+  /**
+   * Liefert die Art der Features.
+   */
+  public FeatureType getFeatureType() {
+    return featureType;
+  }
+
+  /**
+   * Prueft, ob ein weiteres Feature gelesen werden kann.
+   */
+  public boolean hasNext() {
+    return !closed && iterator.hasNext();
+  }
+
+  /**
+   * Liefert das naechste Feature (aus der FeatureCollection).
+   * @exception IOException falls der Reader bereits geschlossen ist.
+   */
+  public Feature next() throws IOException {
+    if (closed)
+      throw new IOException("FeatureReader already closed!");
+    return iterator.next();
+  }
+
+  /**
+   * Schliesst den Reader. Danach koennen keine weiteren Features mehr
+   * gelesen werden. Ueber {@link #reset()} kann der Reader wieder geoeffnet
+   * werden.
+   */
+  public void close() {
+    closed = true;
+  }
+
+  /**
+   * Setzt den Reader auf den Anfang der {@link FeatureCollection} zurueck.
+   */
+  public void reset() {
+    this.closed = false;
+    this.iterator = new FeatureIteratorImpl(this.fc);
+  }
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureOperationTree.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureOperationTree.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureOperationTree.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,176 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.feature;
+
+import org.geotools.feature.Feature;
+
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.TreeNode;
+
+/**
+ * Diese Klasse stellt einen Operator-Baum dar, in dem neben den von der
+ * Oberklasse definitierten Operationen, Referenzen auf {@link Feature}-Attribute
+ * enthalten sein koennen. Der Operator-Baum wird auf einem einzelnen {@link Feature}
+ * ausgewertet. <b>Zur Zeit koennen nur numerische Attribute referenziert werden
+ * und keine Strings!</b><br>
+ * <br>
+ * <b>Referenz auf Attribut-Name:</b> {@link AttributeNameReferenceNode}<br>
+ * Die Referenz auf einen Attribut-Namen wird durch einen {@code String}-Wert
+ * dargestellt, der von einem {@code $} eingeleitet wird (z.B. {@code $citizens}).<br>
+ * <br>
+ * <b>Referenz auf Attribut-Index:</b> {@link AttributeIndexReferenceNode}<br>
+ * Die Referenz auf einen Attribut-Index (beginnend bei 0) wird durch einen
+ * {@code int}-Wert dargestellt, der von einem {@code #} eingeleitet wird (z.B. {@code #7}).<br>
+ * <br>
+ * Bei der Auswertung des Operatorbaums wird die Referenz durch
+ * den entsprechenden Attribut-Wert des Features ersetzt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureOperationTree extends OperationTree {
+  private Feature feature = null;
+
+  /**
+   * Erzeugt einen neuen Raster-Operatorbaum
+   * @param root Wurzelknoten
+   */
+  public FeatureOperationTree(TreeNode root) {
+    super(root);
+  }
+
+  /**
+   * Nicht unterstuetzt!
+   * @deprecated
+   * @exception UnsupportedOperationException bei jedem Aufruf
+   */
+  public Object evaluate() {
+    throw new UnsupportedOperationException("FeatureOperationTree only supports evaluate(Feature)");
+  }
+
+  /**
+   * Wertet den Operatorbaum auf einem {@link Feature} aus.
+   * @param feature ein Feature
+   */
+  public Object evaluate(Feature feature) {
+    this.feature = feature;
+    return super.evaluate();
+  }
+
+  /**
+   * Wertet einen Knoten des Operator-Baums aus.
+   * @param opTreeNode BinaryTreeNode
+   * @return double
+   */
+  protected Object evaluate(TreeNode opTreeNode) {
+    // Attribut-Index
+    if ( opTreeNode instanceof AttributeIndexReferenceNode ) {
+      int attrIdx = ((AttributeIndexReferenceNode)opTreeNode).getObject();
+      Object object = feature.getAttribute( attrIdx );
+      if ( object == null )
+        throw new UnsupportedOperationException("FeatureOperationTree can not evaluate null-Attributes: "+attrIdx);
+//      if ( !(object instanceof Number) )
+//        throw new UnsupportedOperationException("FeatureOperationTree only can evaluate numeric Attributes: Attribute "+attrIdx+" is of type "+object.getClass().getSimpleName());
+//      return ((Number)object).doubleValue();
+      return object;
+    }
+
+    // Attribut-Name
+    if ( opTreeNode instanceof AttributeNameReferenceNode ) {
+      String attrName = ((AttributeNameReferenceNode)opTreeNode).getObject();
+      Object object = feature.getAttribute( attrName );
+      if ( feature.getFeatureType().getAttributeType( attrName ) == null )
+        throw new UnsupportedOperationException("Unknown feature attribute: "+attrName);
+      if ( object == null )
+        throw new UnsupportedOperationException("FeatureOperationTree can not evaluate null-Attributes: "+attrName);
+//      if ( !(object instanceof Number) )
+//        throw new UnsupportedOperationException("FeatureOperationTree can only evaluate numeric Attributes: '"+attrName+"' is of type "+object.getClass().getSimpleName());
+//      return ((Number)object).doubleValue();
+      return object;
+    }
+
+    return super.evaluate(opTreeNode);
+  }
+
+  /**
+   * Diese Knoten repraesentiert eine Namens-Referenz auf ein {@link Feature}-Attribut
+   * im Operatorbaum. Da es sich dabei um eine Konstante handelt, hat der Knoten
+   * keine Kind-Knoten. Die Referenz wird durch den Attribut-Namen ({@code String})
+   * dargestellt.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class AttributeNameReferenceNode extends BinaryTreeNode<String> {
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param attrName Attribut-Name
+     * @param parent   Vater-Knoten
+     */
+    public AttributeNameReferenceNode(String attrName, BinaryTreeNode parent) {
+      super(attrName, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param attrName Attribut-Name
+     */
+    public AttributeNameReferenceNode(String attrName) {
+      this(attrName, null);
+    }
+
+    /**
+     * Macht nichts, da {@code AttributeReferenceNode} immer einen Blatt-Knoten
+     * darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<String> child) {
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert eine Index-Referenz auf ein {@link Feature}-Attribut
+   * im Operatorbaum. Da es sich dabei um eine Konstante handelt, hat der Knoten
+   * keine Kind-Knoten. Die Referenz wird durch den Attribut-Namen ({@code String})
+   * dargestellt.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class AttributeIndexReferenceNode extends BinaryTreeNode<Integer> {
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param attrIdx  Attribut-Index (beginnend bei 0)
+     * @param parent   Vater-Knoten
+     */
+    public AttributeIndexReferenceNode(int attrIdx, BinaryTreeNode parent) {
+      super(attrIdx, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Referenz-Knoten
+     * @param attrIdx  Attribut-Index (beginnend bei 0)
+     */
+    public AttributeIndexReferenceNode(int attrIdx) {
+      this(attrIdx, null);
+    }
+
+    /**
+     * Macht nichts, da {@code AttributeIndexReferenceNode} immer einen Blatt-Knoten
+     * darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<Integer> child) {
+    }
+  }
+
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureOperationTreeFilter.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureOperationTreeFilter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureOperationTreeFilter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,86 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import org.geotools.filter.AbstractFilterImpl;
+import org.geotools.filter.FilterFactoryImpl;
+import org.geotools.feature.Feature;
+
+/**
+ * Dieser Filter nutzt einen {@link FeatureOperationTree}, um die
+ * {@link #evaluate(Feature)}-Methode auszuwerten. Ein Feature erfuellt die
+ * Filter-Bedingung, wenn das Ergebnis des auf dem Feature ausgewerteten
+ * Operator-Baums ungleich 0 ist.
+ * @see FeatureOperationTree
+ * @see FeatureOperationTreeParser
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureOperationTreeFilter extends AbstractFilterImpl {
+  /** Formel, die auf dem Feature ausgewertet wird. */
+  protected String rule = null;
+  /** Operator-Baum zur Formel {@link #rule}. */
+  protected FeatureOperationTree opTree = null;
+
+  /**
+   * Erstellt einen neuen Filter.
+   * @param rule Formel, die auf dem Feature ausgewertet wird.
+   * @see FeatureOperationTreeParser
+   */
+  public FeatureOperationTreeFilter(String rule) {
+    this( rule, new FeatureOperationTreeParser() );
+  }
+
+  /**
+   * Erstellt einen neuen Filter.
+   * @param rule Formel, die auf dem Feature ausgewertet wird.
+   * @param parser Parser, der aus der Formel einen Operator-Baum erstellt
+   */
+  public FeatureOperationTreeFilter(String rule, FeatureOperationTreeParser parser) {
+    super( new FilterFactoryImpl() );
+    this.rule   = rule;
+    this.opTree = parser.parse(rule);
+  }
+
+  /**
+   * Prueft, ob ein {@link Feature} dem Filter entspricht. Dies ist der Fall,
+   * wenn das Formel-Ergebnis fuer das Feature ungleich 0 ist.
+   * @param feature zu ueberpruefendes Feature
+   * @return {@code true}, wenn die Auswertung
+   */
+  public boolean evaluate(Feature feature) {
+    Object result = opTree.evaluate(feature);
+    return result instanceof Boolean && (Boolean)result ||
+           result instanceof Number  && ((Number)result).doubleValue() != 0;
+  }
+
+  /**
+   * Prueft, ob ein {@link Feature} dem Filter entspricht. Dies ist der Fall,
+   * wenn das Formel-Ergebnis fuer das Feature ungleich 0 ist.
+   * @param feature zu ueberpruefendes Feature
+   * @return {@code true}, wenn die Auswertung
+   * @exception IllegalArgumentException wenn das uebergebene Objekt kein
+   *            {@link Feature} ist
+   */
+  public boolean evaluate(Object feature) {
+    if ( feature != null && !(feature instanceof Feature) )
+      throw new IllegalArgumentException("evaluate(Object) can only be applied to Feature objects: "+feature.getClass().getSimpleName());
+    return evaluate((Feature)feature);
+  }
+
+  /**
+   * Liefert die Formel, die den Filter definitiert.
+   */
+  public String getRule() {
+    return rule;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureOperationTreeParser.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureOperationTreeParser.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureOperationTreeParser.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,72 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.feature;
+
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.tree.OperationTree;
+import schmitzm.lang.tree.OperationTreeParser;
+import schmitzm.data.RasterOperationTree.*;
+
+/**
+ * Diese Klasse stellt einen Parser fuer einen {@linkplain FeatureOperationTree Feature-Operatorbaum}
+ * dar. Dieser erstellt einen {@linkplain FeatureOperationTree Feature-Operatorbaum} aus einem
+ * Formel-String, der neben den in {@link OperationTreeParser} beschriebenen
+ * Komponenten Attribut-Referenzen enthalten darf.<br>
+ * <br>
+ * <b>Referenz auf Attribut-Name:</b> {@link FeatureOperationTree.AttributeNameReferenceNode}<br>
+ * Die Referenz auf einen Attribut-Namen wird durch einen {@code String}-Wert
+ * dargestellt, der von einem {@code $} eingeleitet wird (z.B. {@code $citizens}).<br>
+ * <br>
+ * <b>Referenz auf Attribut-Index:</b> {@link FeatureOperationTree.AttributeIndexReferenceNode}<br>
+ * Die Referenz auf einen Attribut-Index (beginnend bei 0) wird durch einen
+ * {@code int}-Wert dargestellt, der von einem {@code #} eingeleitet wird (z.B. {@code #7}).<br>
+ * <br>
+ * Bei der Auswertung des Operatorbaums wird die Referenz durch
+ * den entsprechenden Attribut-Wert des Features ersetzt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureOperationTreeParser extends OperationTreeParser {
+  /**
+   * Erstellt einen Feature-Operator-Baum aus einem Formel-String.
+   * @param rule Formel
+   */
+  public FeatureOperationTree parse(String rule) {
+    // Leere Formel wird als "immer wahr" behandelt
+    if ( rule == null || rule.trim().equals("") )
+      rule = "1";
+    return new FeatureOperationTree( super.parse(rule).getRoot() );
+  }
+
+  /**
+   * Parst das naechste Literal aus dem Tokenizer {@link #tok}.
+   * Erweitert die Funktionalitaet der Oberklasse, so dass neben Konstanten
+   * auch Referenznummern und -namen auf ein Feature-Attribut (eingeleitet
+   * durch {@code #} und {@code $}) angegeben werden koennen.
+   */
+  protected BinaryTreeNode parseLiteral() {
+    String token = nextNonWSToken();
+
+    // Attribut-Name
+    if (token.startsWith("$"))
+      return new FeatureOperationTree.AttributeNameReferenceNode(token.substring(1));
+
+    // Attribut-Index
+    if (token.startsWith("#"))
+      return new FeatureOperationTree.AttributeIndexReferenceNode(getIntFromString(token.substring(1)));
+
+    // sonst: auf Literale der Oberklasse zurueckgreifen
+    pushbackWithWSToken();
+    return super.parseLiteral();
+  }
+}
+

Added: trunk/src/schmitzm/geotools/feature/FeatureTableModel.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,178 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import javax.swing.table.TableModel;
+
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureType;
+
+import schmitzm.geotools.gui.GeotoolsGUIUtil;
+import schmitzm.swing.table.AbstractTableModel;
+import schmitzm.temp.BaseTypeUtil;
+
+/**
+ * Diese Klasse stellt ein {@link TableModel} auf einem einzelnen {@link Feature}
+ * dar. Dieses definiert drei Spalten:
+ * <ol>
+ *   <li>Attribut-Name</li>
+ *   <li>Attribut-Typ</li>
+ *   <li>Wert des Attributs als Text-Eingabe</li>
+ * </ol>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureTableModel extends AbstractTableModel {
+  /** {@link Feature} das in der Tabelle dargestellt wird. */
+  protected Feature feature = null;
+  /** {@link FeatureType} der in der Tabelle dargestellt wird. */
+  protected FeatureType featureType = null;
+
+  /**
+   * Erstellt ein leeres Tabellen-Modell.
+   */
+  public FeatureTableModel() {
+    this((Feature)null);
+  }
+
+  /**
+   * Erstellt ein neues Tabellen-Modell.
+   * @param feature dargestelltes Feature
+   */
+  public FeatureTableModel(Feature feature) {
+    this( feature != null ? feature.getFeatureType() : null);
+    setFeature( feature );
+  }
+
+  /**
+   * Erstellt ein neues Tabellen-Modell.
+   * @param type ein FeatureType
+   */
+  public FeatureTableModel(FeatureType type) {
+    super();
+    setFeature( type );
+  }
+
+  /**
+   * Setzt das {@link Feature}, das in der Tabelle dargestellt wird.
+   * @param feature {@link Feature}
+   */
+  public void setFeature(Feature feature) {
+    this.feature     = feature;
+    this.featureType = feature != null ? feature.getFeatureType() : null;
+    this.fireTableDataChanged();
+  }
+
+  /**
+   * Setzt das {@link Feature}, das in der Tabelle dargestellt wird.
+   * @param fType {@link FeatureType} fuer den eine Standard-Feature
+   *              angezeigt wird
+   */
+  public void setFeature(FeatureType fType) {
+    try {
+      if ( fType != null )
+        setFeature(
+          fType.create( FeatureUtil.getDefaultAttributeValues(fType) )
+        );
+      else
+        setFeature( (Feature)null );
+
+    } catch (Exception err) {
+      throw new RuntimeException(err);
+    }
+  }
+
+  /**
+   * Liefert den {@link FeatureType}, der in der Tabelle dargestellt wird.
+   */
+  public FeatureType getFeatureType() {
+    return this.featureType;
+  }
+
+  /**
+   * Liefert das {@link Feature}, das in der Tabelle dargestellt wird.
+   */
+  public Feature getFeature() {
+    return this.feature;
+  }
+
+  /**
+   * Liefert die Spaltennamen der Tabelle.
+   */
+  public String[] createColumnNames() {
+    return new String[] {
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.feature.FeatureTableModel.AttrName"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.feature.FeatureTableModel.AttrType"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.feature.FeatureTableModel.AttrValue")
+    };
+  }
+
+
+  /**
+   * Liefert die Anzahl an Zeilen. Diese entspricht der Anzahl an Attributen
+   * des {@link Feature}.
+   */
+  public int getRowCount() {
+    return featureType != null ? featureType.getAttributeCount() : 0;
+  }
+
+  /**
+   * Liefert einen Wert der Tabelle.
+   * @param rowIndex  Zeilen-Index (beginnend bei 0)
+   * @param columnIndex Spalten-Index (beginnend bei 0)
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    AttributeType aType = featureType.getAttributeType(rowIndex);
+    switch ( columnIndex ) {
+      case 0: return aType.getLocalName();
+      case 1: return aType.getBinding().getSimpleName();
+      case 2: return feature.getAttribute(rowIndex);
+    }
+    return null;
+  }
+
+  /**
+   * Setzt einen Wert der Tabelle. Nur fuer Spalte "Wert" (2) relevant.
+   * @param rowIndex  Zeilen-Index (beginnend bei 0)
+   * @param columnIndex Spalten-Index (beginnend bei 0)
+   */
+  @Override
+  public void setValueAt(Object value, int rowIndex, int columnIndex) {
+    AttributeType aType = featureType.getAttributeType(rowIndex);
+    if ( "".equals(value) )
+      value = null;
+    if ( value != null &&  Number.class.isAssignableFrom(aType.getBinding()) )
+      value = BaseTypeUtil.convertFromString(value.toString(), aType.getBinding());
+    if ( value == null && !aType.isNillable() )
+      value = FeatureUtil.getDefaultAttributeValue(aType.getBinding());
+
+    try {
+      switch ( columnIndex ) {
+        case 2: feature.setAttribute(rowIndex, value);
+      }
+    } catch (Exception err) {
+      throw new RuntimeException(err);
+    }
+  }
+
+  /**
+   * Liefert {@code true} nur fuer die Spalte "Wert" (2) und nur
+   * dann, wenn es sich um eine numerisches oder String-Attribut
+   * handelt.
+   */
+  @Override
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    Class aClass = featureType.getAttributeType(rowIndex).getBinding();
+    return columnIndex == 2 && BaseTypeUtil.isBaseType( aClass );
+  }
+
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureTypeBuilderTableModel.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureTypeBuilderTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureTypeBuilderTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,394 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import javax.smartcardio.ATR;
+import javax.swing.CellEditor;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.table.AbstractTableModel;
+
+import org.geotools.feature.AttributeTypeFactory;
+import org.geotools.feature.DefaultAttributeType;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.FeatureTypeBuilder;
+import org.geotools.feature.GeometryAttributeType;
+import org.geotools.feature.SchemaException;
+import org.geotools.feature.type.GeometricAttributeType;
+import org.geotools.gui.swing.referencing.CoordinateTableModel.CellRenderer;
+
+import schmitzm.geotools.gui.GeotoolsGUIUtil;
+import schmitzm.swing.BooleanInputOption;
+import schmitzm.swing.ManualInputOption;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.table.AbstractMutableTableModel;
+import schmitzm.swing.table.ComponentRenderer;
+import schmitzm.swing.table.MutableTableModel;
+import schmitzm.temp.BaseTypeUtil;
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+// fuer Doku
+import javax.swing.table.TableModel;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt ein {@link TableModel} auf einem "unfertigen" {@link FeatureType}
+ * dar. Dieses definiert vier Spalten:
+ * <ol>
+ *   <li>Attribut-Name als Text-Eingabe</li>
+ *   <li>Attribut-Typ als Auswahl-Feld</li>
+ *   <li>"Nillable"-Eigenschaft als Check-Box</li>
+ *   <li>"AutoValue"-Eigenschaft als Check-Box</li>
+ *   <li>Standard-Wert als Text-Eingabe</li>
+ * </ol>
+ * Da das {@link TableModel} auf einem {@link FeatureTypeBuilder} basiert,
+ * bietet es die Moeglichkeit, Attribute hinzuzufuegen oder zu loeschen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureTypeBuilderTableModel extends AbstractMutableTableModel {
+  /** Zur Verfuegung stehende Attribut-Typen. */
+  public static final Class[] ATTR_TYPES = new Class[] {
+    String.class,
+    Integer.class,
+    Long.class,
+    Byte.class,
+    Boolean.class,
+    Double.class,
+    Float.class,
+    BigInteger.class,
+    BigDecimal.class,
+    Point.class,
+    LineString.class,
+    Polygon.class
+  };
+
+  public static final String[] ATTR_TYPES_DESC = new String[ATTR_TYPES.length];
+  static {
+    for (int i=0; i<ATTR_TYPES_DESC.length; i++)
+      ATTR_TYPES_DESC[i] = ATTR_TYPES[i].getSimpleName();
+  }
+
+
+  /** Die in der Tabelle dargestellten Attribute. */
+  protected Vector<AttributeDefinition> attrDefinitions = new Vector<AttributeDefinition>();
+
+  /** {@link FeatureTypeBuilder} der in der Tabelle dargestellt wird. */
+  protected FeatureTypeBuilder featureTypeBuilder = null;
+
+  /**
+   * Erstellt ein leeres Tabellen-Modell.
+   */
+  public FeatureTypeBuilderTableModel() {
+    this((FeatureTypeBuilder)null);
+  }
+
+  /**
+   * Erstellt ein neues Tabellen-Modell.
+   * @param type ein FeatureType, der editiert wird
+   */
+  public FeatureTypeBuilderTableModel(FeatureType type) {
+    super();
+    setFeatureType(type);
+  }
+
+  /**
+   * Erstellt ein neues Tabellen-Modell.
+   * @param type ein FeatureType, der editiert wird
+   */
+  public FeatureTypeBuilderTableModel(FeatureTypeBuilder builder) {
+    super();
+    setFeatureTypeBuilder(builder);
+  }
+
+  /**
+   * Liefert die Spaltennamen der Tabelle.
+   */
+  public String[] createColumnNames() {
+    return new String[] {
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrName"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrType"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.Nillable"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AutoValue"),
+        GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.DefValue")
+    };
+  }
+
+  /**
+   * Setzt {@link CellRenderer} und {@link CellEditor} fuer die Tabelle, sowie
+   * eine adaequate Zeilen-Hoehe.
+   */
+  @Override
+  public void initTable(JTable table) {
+    table.setRowHeight( new JComboBox(new String[] {"Dummy"}).getPreferredSize().height );
+    table.getColumnModel().getColumn(0).setCellRenderer( new ComponentRenderer.JTextField() );
+    table.getColumnModel().getColumn(1).setCellRenderer( new ComponentRenderer.JComboBox() );
+    table.getColumnModel().getColumn(2).setCellRenderer( new ComponentRenderer.JCheckBox() );
+    table.getColumnModel().getColumn(3).setCellRenderer( new ComponentRenderer.JCheckBox() );
+    table.getColumnModel().getColumn(4).setCellRenderer( new ComponentRenderer.JTextField() );
+    table.getColumnModel().getColumn(0).setCellEditor( new DefaultCellEditor( new JTextField() ) );
+    table.getColumnModel().getColumn(1).setCellEditor( new DefaultCellEditor( new JComboBox( ATTR_TYPES_DESC ) ) );
+    table.getColumnModel().getColumn(2).setCellEditor( new DefaultCellEditor( new JCheckBox() {
+      public int getHorizontalAlignment() {
+        return this.CENTER;
+      }
+      public int getVerticalAlignment() {
+        return this.CENTER;
+      }
+    }));
+    table.getColumnModel().getColumn(3).setCellEditor( new DefaultCellEditor( new JCheckBox() {
+      public int getHorizontalAlignment() {
+        return this.CENTER;
+      }
+      public int getVerticalAlignment() {
+        return this.CENTER;
+      }
+    }));
+    table.getColumnModel().getColumn(4).setCellEditor( new DefaultCellEditor( new JTextField() ) );
+  }
+
+  /**
+   * Liefert die Anzahl an Attributen in der Tabelle.
+   */
+  public int getRowCount() {
+    return attrDefinitions.size();
+  }
+
+  /**
+   * Setzt den {@link FeatureTypeBuilder}, der in der Tabelle dargestellt wird.
+   * @param builder {@link FeatureTypeBuilder}
+   */
+  public void setFeatureTypeBuilder(FeatureTypeBuilder builder) {
+    if ( builder == null )
+      builder = FeatureTypeBuilder.newInstance("New Type");
+
+    this.featureTypeBuilder = builder;
+    // Attribut-Typen in Array speichern
+    attrDefinitions.clear();
+    if ( featureTypeBuilder != null )
+      for (int i=0; i<featureTypeBuilder.getAttributeCount(); i++) {
+        AttributeType type = featureTypeBuilder.get(i);
+        attrDefinitions.add( new AttributeDefinition(
+            type.getLocalName(),
+            type.getBinding(),
+            type.isNillable(),
+            false,
+            type.createDefaultValue()
+        ) );
+      }
+    this.fireTableDataChanged();
+  }
+
+  /**
+   * Setzt den {@link FeatureTypeBuilder}, der in der Tabelle dargestellt wird.
+   * Dieser wird mit den Attributen des angegebenen {@link FeatureType}
+   * initialisiert
+   * @param ftype {@link FeatureType}
+   */
+  public void setFeatureType(FeatureType ftype) {
+    if ( ftype != null ) {
+      FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance(ftype.getTypeName());
+      builder.addTypes(ftype.getAttributeTypes());
+      setFeatureTypeBuilder(builder);
+    } else
+      setFeatureTypeBuilder(null);
+  }
+
+  /**
+   * Erzeugt einen neuen {@link FeatureType}, aus den in der Tabelle dargestellen
+   * Attributen.
+   */
+  public FeatureType createFeatureType() {
+    try {
+//      this.featureTypeBuilder.removeAll(); // does not work because of BUG in gt2-2.4.4
+      for (;featureTypeBuilder.getAttributeCount() >0;)
+        this.featureTypeBuilder.removeType(0);
+
+      for (AttributeDefinition aDef : attrDefinitions) {
+        AttributeType aType = aDef.createAttributeType();
+        featureTypeBuilder.addType( aType );
+        if ( featureTypeBuilder.getDefaultGeometry() == null &&
+             aType instanceof GeometricAttributeType )
+          featureTypeBuilder.setDefaultGeometry( (GeometricAttributeType)aType );
+      }
+      return this.featureTypeBuilder.getFeatureType();
+    } catch (SchemaException err) {
+      throw new RuntimeException(err);
+    }
+  }
+
+  /**
+   * Prueft, ob eine Tabellen-Zelle editierbar ist.
+   * @return {@code true} fuer jede Zelle
+   */
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    return true;
+  }
+
+  /**
+   * Liefert einen Wert der Tabelle.
+   * @param rowIndex  Zeilen-Index (beginnend bei 0)
+   * @param columnIndex Spalten-Index (beginnend bei 0)
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    AttributeDefinition aDef = attrDefinitions.elementAt(rowIndex);
+    switch ( columnIndex ) {
+      case 0: return aDef.name.getValue();
+      case 1: return aDef.type.getSelectedDisplayItem();
+      case 2: return aDef.nillable.getValue();
+      case 3: return aDef.autoValue.getValue();
+      case 4: return aDef.defaultValue.getValue();
+    }
+    return null;
+  }
+
+  /**
+   * Setzt einen Wert der Tabelle.
+   * @param value neuer Wert
+   * @param rowIndex  Zeilen-Index (beginnend bei 0)
+   * @param columnIndex Spalten-Index (beginnend bei 0)
+   */
+  public void setValueAt(Object value, int rowIndex, int columnIndex) {
+    AttributeDefinition aDef = attrDefinitions.elementAt(rowIndex);
+    switch ( columnIndex ) {
+      case 0: aDef.name.setValue(value); break;
+      case 1: aDef.type.setSelectedDisplayItem(value); break;
+      case 2: aDef.nillable.setValue(value); break;
+      case 3: aDef.autoValue.setValue(value); break;
+      case 4: aDef.defaultValue.setValue(value); break;
+    }
+  }
+
+  /**
+   * Fuegt der Tabelle ein neues Standard-Attribut hinzu.
+   */
+  public void performAddRow() {
+    AttributeDefinition newAttrDef = new AttributeDefinition();
+    newAttrDef.name.setValue( GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.FeatureTypeBuilderTableModel.NewAttr",getRowCount() ) );
+    attrDefinitions.add( newAttrDef );
+    fireTableDataChanged();
+  }
+
+  /**
+   * Macht nichts.
+   */
+  public void performChangeData(int row, int col) {
+    // does nothing
+  }
+
+  /**
+   * Entfernt ein Attribut aus der Tabelle.
+   */
+  public void performRemoveRow(int row) {
+    attrDefinitions.removeElementAt(row);
+    fireTableDataChanged();
+  }
+
+  /**
+   * Defines an Attribute displayed in the table. The advantage of this helper
+   * class is that it does <b>not</b> check the consistency of the inputs
+   * immediately on the input! This is importend richtig
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   *
+   */
+  protected class AttributeDefinition {
+    /** Manual input field for the attribute name. */
+    protected ManualInputOption.Text name;
+    /** Choice input field for the attribute type.
+     *  @see FeatureTypeBuilderTableModel.ATTR_TYPES */
+    protected SelectionInputOption.Combo<Class> type = null;
+    /** Manual input field for the attribute's default value. */
+    protected ManualInputOption.Text defaultValue;
+    /** Checkbox for the attribute's nillable property. */
+    protected BooleanInputOption nillable = null;
+    /** Checkbox to indicate that the attribute value is generated
+     *  automatically. */
+    protected BooleanInputOption autoValue = null;
+
+    /**
+     * Creates an empty attribute definition.
+     */
+    public AttributeDefinition() {
+      name         = new ManualInputOption.Text(null, true); // Attribute name is mandatory
+      type         = new SelectionInputOption.Combo<Class>(null,true,ATTR_TYPES,0,ATTR_TYPES_DESC);
+      defaultValue = new ManualInputOption.Text(null,false); // Default value is not mandatory
+      nillable     = new BooleanInputOption(null,true);
+      autoValue    = new BooleanInputOption(null,false);
+    }
+
+    /**
+     * Creates a new attribute definition. The consistency of the inputs (e.g. type and default
+     * value) is not checked before {@link #createAttributeType()} is called.
+     * @param name name of the attribute
+     * @param type value type of the attribute
+     * @param nillable indicates whether the attribute is mandatory
+     * @param defaultValue default value for the attribute
+     */
+    public AttributeDefinition(String name, Class type, boolean nillable, boolean autoValue, Object defaultValue) {
+      this();
+      this.name.setValue(name);
+      this.type.setValue(type);
+      this.nillable.setValue(nillable);
+      this.autoValue.setValue(autoValue);
+      this.defaultValue.setValue(defaultValue);
+    }
+
+    /**
+     * Creates an {@link AttributeType} from the definition.
+     * @return {@link DefaultAttributeType} or {@link GeometricAttributeType}
+     */
+    public AttributeType createAttributeType() {
+      Class   type         = (Class)this.type.getValue();
+      boolean nillable     = (Boolean)this.nillable.getValue();
+      Object  defaultValue = this.defaultValue.getValue();
+      if ( "".equals(defaultValue) )
+        defaultValue = null;
+      if ( defaultValue != null &&  Number.class.isAssignableFrom(type) )
+        defaultValue = BaseTypeUtil.convertFromString(defaultValue.toString(), type);
+      if ( defaultValue == null && !nillable )
+        defaultValue = FeatureUtil.getDefaultAttributeValue(type);
+
+      AttributeType aType = AttributeTypeFactory.newAttributeType(
+          (String)name.getValue(),
+          type,
+          nillable,
+          null,
+          defaultValue,
+          null
+      );
+
+      AutoValueGenerator valueGenerator = null;
+      if ( autoValue.getValue() ) {
+        if ( Number.class.isAssignableFrom(type) )
+          valueGenerator = new NumberValueGenerator((Number)defaultValue);
+        else
+          throw new UnsupportedOperationException( GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorToolBar.NewLayer.Err.AutoVal", type.getSimpleName()));
+        FeatureUtil.registerAutoValueGenerator(aType, valueGenerator);
+      } else
+        FeatureUtil.unregisterAutoValueGenerator(aType);
+
+
+      return aType;
+    }
+  }
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureTypeTableModel.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureTypeTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureTypeTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,128 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import java.util.Vector;
+
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.FeatureType;
+
+import schmitzm.swing.table.AbstractTableModel;
+
+
+/**
+ * Diese Klasse stellt ein {@link TableModel} auf einem {@link FeatureType}
+ * dar. Dieses definiert zwei Spalten, in denen der Name (0) und der
+ * Typ (1) der Attribute dargestellt werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureTypeTableModel extends AbstractTableModel {
+  /** {@link FeatureType} das in der Tabelle dargestellt wird. */
+  protected FeatureType featureType = null;
+  /** Bestimmt die angezeigten Features */
+  protected AttributeTypeFilter attrFilter = AttributeTypeFilter.ALL;
+
+  /** Speichert die (die gefilterten!) Attibut-Typen des Features.*/
+  protected Vector<AttributeType> attrTypes = new Vector<AttributeType>();
+
+  /**
+   * Erstellt ein leeres Tabellen-Modell.
+   */
+  public FeatureTypeTableModel() {
+    this(null);
+  }
+
+  /**
+   * Erstellt ein neues Tabellen-Modell.
+   * @param type ein FeatureType
+   */
+  public FeatureTypeTableModel(FeatureType type) {
+    super();
+    setFeatureType( type );
+  }
+
+  /**
+   * Setzt den {@link FeatureType}, der in der Tabelle dargestellt wird.
+   * @param ftype {@link FeatureType}
+   */
+  public void setFeatureType(FeatureType ftype) {
+    this.featureType = ftype;
+    // Attribut-Typen in Array speichern
+    attrTypes.clear();
+    if ( featureType != null )
+      for (int i=0; i<featureType.getAttributeCount(); i++) {
+        AttributeType type = featureType.getAttributeType(i);
+        if ( attrFilter == null || attrFilter.accept( type, i ) )
+          attrTypes.add( type );
+      }
+    this.fireTableDataChanged();
+  }
+
+  /**
+   * Liefert den Filter, der die dargestellten Attribute bestimmt.
+   */
+  public AttributeTypeFilter getAttributeFilter() {
+    return attrFilter;
+  }
+
+  /**
+   * Setzt den Filter, der die dargestellten Attribute bestimmt.
+   * @param attrFilter Filter
+   */
+  public void setAttributeFilter(AttributeTypeFilter attrFilter) {
+    this.attrFilter = attrFilter;
+    // Filter neu anwenden
+    setFeatureType( featureType );
+  }
+
+  /**
+   * Liefert den {@link FeatureType}, der in der Tabelle dargestellt wird.
+   */
+  public FeatureType getFeatureType() {
+    return this.featureType;
+  }
+
+
+
+  /**
+   * Liefert die Spaltennamen der Tabelle.
+   */
+  public String[] createColumnNames() {
+    return new String[] {
+        "Attribute-Name",
+        "Attribute-Type"
+    };
+  }
+
+
+  /**
+   * Liefert die Anzahl an Zeilen. Diese entspricht der Anzahl an Attributen
+   * des {@link FeatureType}.
+   */
+  public int getRowCount() {
+    return /*featureType*/attrTypes != null ? attrTypes.size() : 0;
+  }
+
+  /**
+   * Liefert einen Wert der Tabelle.
+   * @param rowIndex  Zeilen-Index (beginnend bei 0)
+   * @param columnIndex Spalten-Index (beginnend bei 0)
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    AttributeType aType = attrTypes.elementAt(rowIndex);
+    switch ( columnIndex ) {
+      case 0: return aType.getLocalName();
+      case 1: return aType.getBinding().getSimpleName();
+    }
+    return null;
+  }
+}

Added: trunk/src/schmitzm/geotools/feature/FeatureUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/FeatureUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/FeatureUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,1231 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.feature;
+
+
+import java.awt.Color;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.apache.log4j.Logger;
+import org.geotools.data.DataUtilities;
+import org.geotools.data.FeatureSource;
+import org.geotools.data.memory.MemoryFeatureCollection;
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.AttributeTypeFactory;
+import org.geotools.feature.DefaultFeatureCollection;
+import org.geotools.feature.DefaultFeatureCollections;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureTypeBuilder;
+import org.geotools.feature.GeometryAttributeType;
+import org.geotools.feature.IllegalAttributeException;
+import org.geotools.feature.SchemaException;
+import org.geotools.feature.type.GeometricAttributeType;
+import org.geotools.map.MapLayer;
+import org.geotools.renderer.lite.RendererUtilities;
+import org.geotools.styling.FeatureTypeStyle;
+import org.geotools.styling.LineSymbolizer;
+import org.geotools.styling.PointSymbolizer;
+import org.geotools.styling.PolygonSymbolizer;
+import org.geotools.styling.Rule;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.geotools.styling.Symbolizer;
+import org.geotools.styling.Mark;
+import org.geotools.styling.Graphic;
+import org.opengis.filter.Filter;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.lang.LangUtil;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.MultiLineString;
+import com.vividsolutions.jts.geom.MultiPoint;
+import com.vividsolutions.jts.geom.MultiPolygon;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+
+/**
+ * Diese Klasse beinhaltet statische Methoden zum Arbeiten mit Features.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ * @version 1.1
+ */
+public class FeatureUtil {
+  private static final Logger LOGGER = LangUtil.createLogger(FeatureUtil.class);
+
+  private static final String DEFAULT_VECTOR_STYLE_NAME = "default vector style";
+
+  private static final String DEFAULT_JOIN_ATTR = "OID";
+
+  private static StyleBuilder STYLE_BUILDER = new StyleBuilder();
+
+  /** Instance of JTS-GeometryFactory. */
+  public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
+
+  /** Instance of AttributeTypeFactory. */
+  public static final AttributeTypeFactory ATTRTYPE_FACTORY = AttributeTypeFactory.defaultInstance();
+
+  /** Join-Types. */
+  public static enum JoinType {
+    /** The feature of the left collection is taken into the result, even
+     *  there is no join feature in the right collection. */
+    LEFT_OUTER,
+    /** The feature of the right collection is taken into the result, even
+     *  there is no join feature in the left collection. */
+    RIGHT_OUTER,
+    /** Only features with a join feature in the other collection are
+     *  taken into the result (Default). */
+    FULL
+  }
+
+  /** The geometry type of a {@link FeatureCollection} */
+  public static enum GeometryForm {
+    /** Point */
+    POINT,
+    /** Line or LineString */
+    LINE,
+    /** Polygon */
+    POLYGON
+  }
+
+  /**
+   * Enthaelt die {@link AttributeType AttributeTypes}, fuer die
+   * automatisch Werte generiert werden koennen.
+   */
+  private static HashMap<AttributeType, AutoValueGenerator> autoAttrValueGenerators = new HashMap<AttributeType, AutoValueGenerator>();
+
+  /**
+   * Determines the kind of geometry of a {@link FeatureType}.
+   * @param fType a feature type
+   */
+  public static GeometryForm getGeometryForm(FeatureType fType) {
+    if ( fType.getDefaultGeometry() == null )
+      return null;
+
+    Class geomType = fType.getDefaultGeometry().getType();
+    // POINT
+    if ( Point.class.isAssignableFrom(geomType) ||
+        MultiPoint.class.isAssignableFrom(geomType) )
+     return GeometryForm.POINT;
+    // LINE
+    if ( LineString.class.isAssignableFrom(geomType) ||
+         MultiLineString.class.isAssignableFrom(geomType) )
+      return GeometryForm.LINE;
+    // POLYGON
+    if ( Polygon.class.isAssignableFrom(geomType) ||
+         MultiPolygon.class.isAssignableFrom(geomType) )
+      return GeometryForm.POLYGON;
+
+    throw new UnsupportedOperationException("Unknown geometry type: "+geomType.getName());
+  }
+
+  /**
+   * Determines the kind of geometry of a {@link FeatureCollection}.
+   * @param fc a feature collection
+   */
+  public static GeometryForm getGeometryForm(FeatureCollection fc) {
+    return getGeometryForm(fc.getSchema());
+  }
+
+  /**
+   * Determines the kind of geometry of a {@link FeatureSource}.
+   * @param fs a feature source
+   */
+  public static GeometryForm getGeometryForm(FeatureSource fs) {
+    return getGeometryForm(fs.getSchema());
+  }
+
+  /**
+   * Determines the kind of geometry of a {@link MapLayer}.
+   * @param layer a map layer
+   */
+  public static GeometryForm getGeometryForm(MapLayer layer) {
+    return getGeometryForm(layer.getFeatureSource());
+  }
+
+    /**
+	 *  SLD Rules können die Paramter MinScaleDenominator und
+	 * MaxScaleDenominator enthalten. Dadurch können Elemente für manche
+	 * Zoom-Stufen deaktiviert werden.
+	 *
+	 * Kommentar: "Sichtbarkeit" bezieht es nicht darauf, ob die Elemente auf dem Kartenausschnitt sichtbar sind, sondern um die SLD Regeln, welche das Feature evt. nur Scalenabhängig zeichnen.
+	 *
+	 * @param fc
+	 *            Die zu filternde FeatureCollection. Diese wird nicht
+	 *            verändert.
+	 * @param style
+	 *            Der Style, mit dem die Features gerendert werden (z.b.
+	 *            layer.getStyle() )
+	 *
+	 * @param scaleDenominator Der aktuelle ScaleDenomitor für den die Sichtbarkeit ermittelt wird.
+	 *
+	 * @see RendererUtilities.calculateOGCScale
+   *
+	 * @return Eine FeatureCollection in welcher nur die Features enthalten
+	 *         sind, welche bei aktuellen Scale mit dem übergebenen Style
+	 *         gerendert werden.
+	 *
+	 *         TODO Was ist mit raster?!
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static MemoryFeatureCollection filterSLDVisibleOnly(final FeatureCollection fc,
+			final Style style, final Double scaleDenominator) {
+
+		// Eine im Speicher gehaltene FeatureCollection der sichtbaren
+		MemoryFeatureCollection fcVisible = new MemoryFeatureCollection(fc
+				.getFeatureType());
+
+		// Prüfen aller Features in der Collection
+		Iterator<Feature> fcIt = fc.iterator();
+		while (fcIt.hasNext()) {
+			Feature feature = fcIt.next();
+
+			for (FeatureTypeStyle fts : style.getFeatureTypeStyles()) {
+				List<Rule> rules = fts.rules();
+
+				for (Rule rule : rules) {
+					double maxScaleDenominator = rule.getMaxScaleDenominator();
+					double minScaleDenominator = rule.getMinScaleDenominator();
+
+					// 1. Check: Trifft die Rule auf den aktuellen Scale der JMapPane?
+					if ((minScaleDenominator > scaleDenominator)
+							|| (scaleDenominator > maxScaleDenominator)) {
+						continue;
+					}
+
+					// 2. Check: Eine Rule ist gülrig, aber trifft der Filter für dieses Feature?
+					Filter rFilter = rule.getFilter();
+					if ( (rFilter != null) && (!rFilter.evaluate(feature))) continue;
+
+
+					// 3. Check: Passt einer der Symbolizer dieser Rule zu dem Feature? Dieser Check wird in
+				    // "real world" meistens klappen, es sei denn, ein Hansel gibt einen LineSymbolizer für ein PointLayer an.
+					boolean passt = false;
+					for (Symbolizer symb : rule.getSymbolizers()){
+						Geometry geom = feature.getDefaultGeometry();
+						if ( (geom instanceof MultiPoint) || (geom instanceof com.vividsolutions.jts.geom.Point)){
+							if (symb instanceof PointSymbolizer) {
+								passt =true; break;
+							}
+					    } else
+							if ( (geom instanceof MultiLineString) || (geom instanceof LineString)){
+								if (symb instanceof LineSymbolizer) {
+									passt =true; break;
+								}
+					    	}else
+								if ( (geom instanceof MultiPolygon) || (geom instanceof Polygon)){
+									if (symb instanceof PolygonSymbolizer) {
+										passt =true; break;
+									}
+						    	}
+					}
+					if (!passt) continue;
+
+					// Nun sollte das feature wiklich sichtbar sein
+					fcVisible.add( feature);
+				}
+			}
+		}
+
+//		LOGGER.info("filterSLDVisibleOnly removed "+ (fc.size() - fcVisible.size()) + " features.");
+
+		return fcVisible;
+	}
+
+
+
+
+  /**
+   * Extrahiert alle Geometrien aus einem Feature.
+   * @param f Feature
+   * @return Array aller Geometrien im Feature.
+   */
+  public static Geometry[] extractGeometriesToGeometry(Feature f) {
+    Vector<Object> geomVec = new Vector<Object>();
+
+    for (int j=0; j<f.getFeatureType().getAttributeCount(); j++)
+//      if ( f.getFeatureType().getAttributeType(j).isGeometry() )
+      if ( f.getFeatureType() instanceof GeometryAttributeType )
+        geomVec.add(f.getAttribute(j));
+
+    Geometry[] geomArr = new Geometry[geomVec.size()];
+    for (int i=0; i<geomVec.size(); i++)
+      geomArr[i] = (Geometry)geomVec.elementAt(i);
+    return geomArr;
+  }
+
+  /**
+   * Extrahiert alle Default-Geometrien aus einer FeatureCollection.
+   * @param fc FeatureCollection
+   * @return Array aller Geometrien in der FeatureCollection.
+   */
+  public static Vector<Geometry> extractGeometries(FeatureCollection fc, Vector<Geometry> result) {
+    if ( result == null )
+      result = new Vector<Geometry>();
+
+    FeatureIterator fi = fc.features();
+    for (;fi.hasNext();) {
+      Feature f = fi.next();
+      result.add( f.getDefaultGeometry() );
+    }
+    return result;
+  }
+
+  /**
+   * Erzeugt einen Standard-Style fuer eine {@link FeatureCollection}
+   * Und setzt eine default namen
+   * @param fc FeatureCollection
+   */
+  public static Style createDefaultStyle(FeatureCollection fc) {
+    GeometryAttributeType geometryAttrib = fc.getSchema().getDefaultGeometry();
+    return createDefaultStyle( geometryAttrib );
+  }
+
+  /**
+   * Erzeugt einen Standard-Style fuer einen {@link GeometryAttributeType}
+   * Und setzt eine default Namen.
+   * @param geometryAttrib GeometryAttributeType
+   */
+  public static Style createDefaultStyle(GeometryAttributeType geometryAttrib) {
+    Style style = null;
+
+    if ( geometryAttrib != null &&
+        (com.vividsolutions.jts.geom.Polygon.class.isAssignableFrom(geometryAttrib.getType()) ||
+         com.vividsolutions.jts.geom.MultiPolygon.class.isAssignableFrom(geometryAttrib.getType())) )
+     style = createPolygonStyle(Color.ORANGE, Color.BLACK, 1);
+    else if (geometryAttrib != null &&
+             (com.vividsolutions.jts.geom.Point.class.isAssignableFrom(geometryAttrib.getType()) ||
+              com.vividsolutions.jts.geom.MultiPoint.class.isAssignableFrom(geometryAttrib.getType())) )
+      style = createPointStyle(Color.RED);
+    else
+      style = createLineStyle(Color.BLUE, 1);
+
+    style.setName(DEFAULT_VECTOR_STYLE_NAME);
+    style.setTitle(DEFAULT_VECTOR_STYLE_NAME);
+    return style;
+  }
+
+  /**
+   * Erzeugt einen Polygon-Style.
+   * @param fillColor Fuell-Farbe
+   * @param borderColor Rand-Farbe
+   * @param borderWidth Breite des Rands
+   */
+  public static Style createPolygonStyle(Color fillColor, Color borderColor, double borderWidth) {
+    final Symbolizer symb = STYLE_BUILDER.createPolygonSymbolizer(fillColor, borderColor, borderWidth);
+    return STYLE_BUILDER.createStyle( symb );
+  }
+
+  /**
+   * Erzeugt einen (randlosen) Punkt-Style in Form eines Kreises.
+   * @param color Farbe des Punkts
+   */
+  public static Style createPointStyle(Color fillColor) {
+    return createPointStyle(fillColor, fillColor, 5);
+  }
+
+  /**
+   * Erzeugt einen (randlosen) Punkt-Style in Form eines Kreises.
+   * @param fillColor Fuell-Farbe des Punkts
+   * @param borderColor Rand-Farbe des Punkts
+   * @param width Groesse des Punkts
+   */
+  public static Style createPointStyle(Color fillColor, Color borderColor, double width) {
+    return createPointStyle(STYLE_BUILDER.MARK_CIRCLE, fillColor, borderColor, 1, 1, width, 0);
+  }
+
+  /**
+   * Erzeugt einen Punkt-Style.
+   * @param color Farbe des Punkts
+   * @param markStyle Darstellungsweise des Punkts
+   * @param opacity
+   * @see StyleBuilder#MARK_ARROW;
+   * @see StyleBuilder#MARK_CIRCLE;
+   * @see StyleBuilder#MARK_CROSS;
+   * @see StyleBuilder#MARK_SQUARE;
+   * @see StyleBuilder#MARK_STAR;
+   * @see StyleBuilder#MARK_TRIANGLE;
+   * @see StyleBuilder#MARK_X;
+   */
+  public static Style createPointStyle(String markStyle, Color fillColor, Color borderColor, double borderWidth, double opacity, double size, double rotation) {
+    final Mark       mark = STYLE_BUILDER.createMark(markStyle, fillColor, borderColor, borderWidth);
+    final Graphic    g    = STYLE_BUILDER.createGraphic(null, mark, null, opacity, size, rotation);
+    final Symbolizer symb = STYLE_BUILDER.createPointSymbolizer(g);
+    return STYLE_BUILDER.createStyle( symb );
+  }
+
+  /**
+   * Erzeugt einen Linien-Style.
+   * @param lineColor Farbe der Linie
+   */
+  public static Style createLineStyle(Color lineColor) {
+    return createLineStyle(lineColor, 1);
+  }
+
+  /**
+   * Erzeugt einen Linien-Style.
+   * @param lineColor Farbe der Linie
+   * @param lineWidth Breite der Linie
+   */
+  public static Style createLineStyle(Color lineColor, double lineWidth) {
+    final Symbolizer symb = STYLE_BUILDER.createLineSymbolizer(lineColor, lineWidth);
+    return STYLE_BUILDER.createStyle( symb );
+  }
+
+
+ /**
+  * Erzeugt einen Array von {@link Feature Features} auf einer
+  * {@link FeatureCollection}. Teilweise liefert
+  * {@code FeatureCollection.toArray(new Feature[.])} statt einem {@code Feature}-Array
+  * einfach {@code null}. In diesem Fall wird der Array "naiv" ueber Durchlaufen
+  * eines {@link FeatureIterator } erzeugt.
+  *
+  * @param fc
+  *            eine Feature Collection
+  * @return einen leeren Array, wenn die Collection leer oder {@code null} ist
+  */
+  public static Feature[] featuresToArray(FeatureCollection fc) {
+    if ( fc == null )
+      return new Feature[0];
+    Feature[] featureArray = (Feature[])fc.toArray(new Feature[fc.size()]);
+    if ( featureArray != null )
+      return featureArray;
+    // Sonst "naiv" kopieren
+    featureArray = new Feature[fc.size()];
+    FeatureIterator fi = fc.features();
+    for (int i=0; fi.hasNext(); i++)
+      featureArray[i] = fi.next();
+    return featureArray;
+  }
+
+  /**
+   * Clones an {@link FeatureType}.
+   * @param fType     type to clone
+   * @param fTypeName the name for the clone (if {@code null} the name is
+   *                  taken from {@code fType})
+   * @param nillable  nillable property for <b>all</b> attributes (if {@code null} the
+   *                  nillable property is taken individually from the source attributes)
+   */
+  public static FeatureType cloneFeatureType(FeatureType fType, String fTypeName, Boolean nillable) throws SchemaException {
+    if ( fTypeName == null )
+      fTypeName = fType.getTypeName();
+
+    FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance(fType.getTypeName());
+    for (AttributeType aType : fType.getAttributeTypes() ) {
+      AttributeType aTypeClone = cloneAttributeType(aType, nillable);
+      if ( aTypeClone instanceof GeometryAttributeType && builder.getDefaultGeometry() == null )
+        builder.setDefaultGeometry( (GeometryAttributeType)aTypeClone );
+      else
+        builder.addType(aTypeClone);
+    }
+    return builder.getFeatureType();
+  }
+
+  /**
+   * Clones an {@link AttributeType}.
+   * @param aType type to clone
+   * @param nillable the clone gets this value for its nillable property independently
+   *                 of {@code aType.isNillable()} (if {@code null} the nillable property
+   *                 is taken from {@code aType})
+   *
+   */
+  public static AttributeType cloneAttributeType(AttributeType aType, Boolean nillable) {
+    // If no special nillable property is specified, take it from the
+    // given AttributeType
+    if ( nillable == null )
+      nillable = aType.isNillable();
+
+    // Create a default value for the attribute
+    Object defaultValue = nillable ? null : getDefaultAttributeValue(aType);
+
+    Object metaData = null;
+    // if it is a GeometryAttributeType, the CRS must be stored
+    // in the meta data
+    if ( aType instanceof GeometryAttributeType )
+      metaData = ((GeometryAttributeType)aType).getCoordinateSystem();
+
+    // Create the clone
+    return AttributeTypeFactory.newAttributeType(
+            aType.getLocalName(),
+            aType.getBinding(),
+            nillable,
+            aType.getRestriction(),
+            defaultValue,
+            metaData
+           );
+  }
+
+  /**
+   * Erweitert das Schema einer {@link FeatureCollection} um eine Reihe von
+   * Attributen.
+   * @param fType  zu erweiterndes Feature-Schema
+   * @param aTypes Typen der neuen Attribute
+   * @return eine <b>neue</b> Instanz von {@link FeatureType}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws NullPointerException falls {@code fType = null}
+   */
+  public static FeatureType extendFeatureType(FeatureType fType, AttributeType... aTypes) throws SchemaException {
+//BEMERKUNG: Folgende Methode funktioniert, fuegt die abgeleiteten (alten)
+//           Attribute aber hinten an die neuen Attribute an. Dies ist
+//           nicht gewuenscht und erschwert auch den Aufbau des fValues-Array
+//    FeatureType resultType = FeatureTypeBuilder.newFeatureType(
+//        aTypes,                    // new Attributes
+//        fType.getTypeName(),       // new type name
+//        null,                      // namespace
+//        false,                     // abstact?
+//        new FeatureType[] { fType },
+//        null
+//    );
+    FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance(fType.getTypeName());
+    builder.addTypes( fType.getAttributeTypes() ); // old attributes
+    for (AttributeType aType : aTypes )
+      try {
+        if ( aType instanceof GeometryAttributeType && builder.getDefaultGeometry() == null )
+          builder.setDefaultGeometry( (GeometryAttributeType)aType );
+        else
+          builder.addType(aType);
+      } catch (IllegalArgumentException err) {
+        builder.addType( AttributeTypeFactory.newAttributeType(
+            aType.getLocalName()+"_2",
+            aType.getBinding(),
+            aType.isNillable(),
+            aType.getRestriction(),
+            aType.createDefaultValue(),
+            null
+        ));
+      }
+    FeatureType resultType = builder.getFeatureType();
+
+    // As long as DataUtilities.defaultValue(AttributeType) does
+    // not handle the default value correctly for not-nillable
+    // attributes, create a new FeatureType with all attributes
+    // nillable
+// not necessary as long a correct default value can be created
+// in createFeatureType(ResultSetMetaData, ...)
+//    resultType = cloneFeatureType(resultType, null, true);
+
+    return resultType;
+  }
+
+  /**
+   * Erweitert das Schema einer {@link FeatureCollection} um eine Reihe von
+   * Attributen.
+   * @param fc     zu erweiternde FeatureCollection
+   * @param aTypes Typen der neuen Attribute
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection extendFeatureCollection(FeatureCollection fc, AttributeType... aTypes) throws SchemaException, IllegalAttributeException  {
+    // Schema erstellen
+    FeatureType fType = fc.getSchema();
+    FeatureType resultType = extendFeatureType( fType, aTypes );
+
+    // Neue Collection erstellen
+    FeatureCollection resultFc = DefaultFeatureCollections.newCollection();
+    FeatureIterator   fi       = fc.features();
+    // Array fuer die Attribut-Werte eines Features
+    Object[]          fValues  = new Object[resultType.getAttributeCount()];
+    for ( Feature f = null; fi.hasNext(); ) {
+      // Werte der alten Attribute in Array schreiben
+      fi.next().getAttributes(fValues);
+      // Default-Werte der neuen Attribute in Array schreiben
+      for ( int i=fType.getAttributeCount(); i<fValues.length; i++)
+        fValues[i] = resultType.getAttributeType(i).createDefaultValue();
+      // Erweitertes Feature erzeugen und FeatureCollection fuellen
+      resultFc.add( resultType.create( fValues ) );
+    }
+
+    return resultFc;
+  }
+
+  /**
+   * Fuehrt eine Join-Operation zwischen zwei {@link FeatureCollection} durch.
+   * Hierfuer wird eine einfacher Nested-Loop-Join durchgefuehrt.
+   * In den optionalen Parametern koennen Attribut-Namen angegeben werden, die
+   * in die Ergebnis-Colletion uebernommen werden (Projektion).<br>
+   * <b>Achtung:</b> Wird diese Option verwendet, sollte sichergestellt sein,
+   * dass {@code fc1} und {@code fc2} KEINE gleichen Attributnamen hat! Ansonsten
+   * wird (aus technischen Gruenden) das Attribut von {@code fc2} uebernommen.
+   * @param fc1       erste FeatureCollection
+   * @param joinAttr1 Attribut-Name in der ersten FeatureCollection
+   * @param compareOp Operation, mit der der JOIN-Vergleich durchgefuehrt wird
+   * @param fc2       zweiten FeatureCollection
+   * @param joinAttr2 Attribut-Name in der zweiten FeatureCollection
+   * @param projAttr  Attribute (von {@code fc1} und {@code fc2}), die in das
+   *                  Ergebnis übernommen werden (Projektion)
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection joinFeatureCollection(JoinType joinType, FeatureCollection fc1, String joinAttr1, AttributeFilter compareOp, FeatureCollection fc2, String joinAttr2, String... projAttr) throws SchemaException, IllegalAttributeException  {
+    // RIGHT-OUTER-JOIN reaslisiert als "inverser" LEFT-OUTER-JOIN
+    // Bemerkung: nur Uebergangsloesung, da nicht ganz sauber!!
+    //            Attribut-Reihenfolge wird vertauscht...
+    if ( joinType == JoinType.RIGHT_OUTER )
+//      throw new IllegalArgumentException("Join type RIGHT_OUTER not yet supported.");
+      return joinFeatureCollection(
+          JoinType.LEFT_OUTER,
+          fc2,
+          joinAttr2,
+          compareOp.inverse(),
+          fc1,
+          joinAttr1,
+          projAttr
+      );
+
+    // Flag, ob Projektion erfolgen soll
+    boolean projection = projAttr.length > 0;
+
+    // Schema erstellen
+    FeatureType fType1 = fc1.getSchema();
+    FeatureType fType2 = fc2.getSchema();
+    FeatureType resultType = null;
+    if ( fType1 == null )
+      throw new UnsupportedOperationException("JOIN can not be performed. FeatureCollection 1 seems to be empty.");
+    if ( fType2 == null )
+      throw new UnsupportedOperationException("JOIN can not be performed. FeatureCollection 2 seems to be empty.");
+    if ( fType1.getAttributeType(joinAttr1) == null )
+      throw new UnsupportedOperationException("JOIN can not be performed. FeatureCollection 1 does not contain the JOIN-Attribute: "+joinAttr1);
+    if ( fType2.getAttributeType(joinAttr2) == null )
+      throw new UnsupportedOperationException("JOIN can not be performed. FeatureCollection 2 does not contain the JOIN-Attribute: "+joinAttr2);
+
+    // Wenn keine Projektion, dann alle Attribute von fc1 mit denen
+    // von fc2 vereinen; sonst neuen FeatureType aufbauen
+    if ( !projection )
+        resultType = extendFeatureType(fType1, fType2.getAttributeTypes());
+    else {
+      // Leeren Feature-Type erzeugen
+      FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance(fType1.getTypeName());
+      // Projektions-Attribute hinzufuegen
+      for (String attrName : projAttr) {
+        // erste Wahl: Attribut von fc2 uebernehmen (da beim Belegen
+        //             von "fValues" der Attributwert aus fc1 in der inneren
+        //             FOR-Schleife ggf. durch den Attributwert von fc2
+        //             ueberschrieben wird!)
+        AttributeType aType = fType2.getAttributeType(attrName);
+        // zweite Wahl: Attribut von fc1 uebernehmen
+        if ( aType == null )
+          aType = fType1.getAttributeType(attrName);
+        // Attribut in den FeatureType einfuegen
+        if ( aType != null )
+          builder.addType( aType );
+      }
+      // FeatureType erzeugen
+      resultType = builder.getFeatureType();
+    }
+
+    // Neue Collection erstellen
+    DefaultFeatureCollection resultFc = (DefaultFeatureCollection)DefaultFeatureCollections.newCollection();
+    FeatureIterator fi = fc1.features();
+    // Array fuer die Attribut-Werte eines Features
+    Object[] fValues = new Object[resultType.getAttributeCount()];
+
+    // NestedLoopJoin: Alle Features der ersten Collection durchgehen
+    //                 und die passenden in der zweiten Collection suchen
+    AttributeFilter filter = compareOp.clone();
+    for (Feature f1 = null; fi.hasNext(); ) {
+      f1 = fi.next();
+      // Werte der 1. Features in Array schreiben
+      if ( !projection )
+        // Alle Attribut-Werte aus fc1
+        f1.getAttributes(fValues);
+      else
+        // Nur Werte der Projektions-Attribute aus fc1
+        getAttributeValues(f1,fValues,0,projAttr);
+
+      // Passende Features in 2. Colletion suchen
+      filter.setAttributeName(joinAttr2);
+      filter.setCompareValue(f1.getAttribute(joinAttr1));
+      FeatureIterator joinedFeatures = fc2.subCollection(filter).features();
+
+      // Wenn LEFT-OUTER-JOIN und JOIN nicht erfolgreich, ein Dummy
+      // Feature mit Default-Werten in die Collection einfuegen
+      // Sonst: JOIN-Paare "normal" bilden
+      if ( !joinedFeatures.hasNext() && joinType == JoinType.LEFT_OUTER ) {
+        if ( !projection )
+          getDefaultAttributeValues(fType2,fValues,fType1.getAttributeCount());
+        else
+          getDefaultAttributeValues(fType2,fValues,0,projAttr);
+        // Erweitertes Feature erzeugen und FeatureCollection fuellen
+        resultFc.add( resultType.create( fValues ) );
+      } else {
+        for (Feature f2 = null; joinedFeatures.hasNext(); ) {
+          f2 = joinedFeatures.next();
+          // Werte der 2. Features in Array schreiben
+          if ( !projection )
+            getAttributeValues(f2,fValues,fType1.getAttributeCount());
+          else
+            getAttributeValues(f2,fValues,0,projAttr);
+          // Erweitertes Feature erzeugen und FeatureCollection fuellen
+          Feature feature = resultType.create( fValues );
+          resultFc.add( feature );
+        }
+      }
+    }
+
+    return resultFc;
+  }
+
+  /**
+   * Fuehrt eine (Full-Outer-) Join-Operation zwischen zwei {@link FeatureCollection}
+   * durch.
+   * Hierfuer wird eine einfacher Nested-Loop-Join durchgefuehrt.
+   * In den optionalen Parametern koennen Attribut-Namen angegeben werden, die
+   * in die Ergebnis-Colletion uebernommen werden (Projektion).<br>
+   * <b>Achtung:</b> Wird diese Option verwendet, sollte sichergestellt sein,
+   * dass {@code fc1} und {@code fc2} KEINE gleichen Attributnamen hat! Ansonsten
+   * wird (aus technischen Gruenden) das Attribut von {@code fc2} uebernommen.
+   * @param fc1       erste FeatureCollection
+   * @param joinAttr1 Attribut-Name in der ersten FeatureCollection
+   * @param compareOp Operation, mit der der JOIN-Vergleich durchgefuehrt wird
+   * @param fc2       zweiten FeatureCollection
+   * @param joinAttr2 Attribut-Name in der zweiten FeatureCollection
+   * @param projAttr  Attribute (von {@code fc1} und {@code fc2}), die in das
+   *                  Ergebnis übernommen werden (Projektion)
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection joinFeatureCollection(FeatureCollection fc1, String joinAttr1, AttributeFilter compareOp, FeatureCollection fc2, String joinAttr2, String... projAttr) throws SchemaException, IllegalAttributeException  {
+    return joinFeatureCollection(JoinType.FULL, fc1, joinAttr1, compareOp, fc2, joinAttr2, projAttr);
+  }
+
+  /**
+   * Fuehrt eine (Full-Outer-) Equi-Join-Operation zwischen zwei
+   * {@link FeatureCollection} durch.
+   * @param fc1       erste FeatureCollection
+   * @param joinAttr1 Attribut-Name in der ersten FeatureCollection
+   * @param fc2       zweiten FeatureCollection
+   * @param joinAttr2 Attribut-Name in der zweiten FeatureCollection
+   * @param projAttr  Attribute (von {@code fc1} und {@code fc2}), die in das
+   *                  Ergebnis übernommen werden (Projektion)
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   * @see #joinFeatureCollection(FeatureCollection, String, AttributeFilter, FeatureCollection, String)
+   * @see #AttributeFilter.EQUALS
+   */
+  public static FeatureCollection joinFeatureCollection(FeatureCollection fc1, String joinAttr1, FeatureCollection fc2, String joinAttr2, String... projAttr) throws SchemaException, IllegalAttributeException  {
+    return joinFeatureCollection(fc1,joinAttr1,AttributeFilter.EQUALS,fc2,joinAttr2,projAttr);
+  }
+
+  /**
+   * Liefert die Default-Werte fuer alle Attribute eines {@link FeatureType}.
+   * @param ft ein Feature-Type
+   */
+  public static Object[] getDefaultAttributeValues(FeatureType ft) {
+    return getDefaultAttributeValues(ft,null,0);
+  }
+
+  /**
+   * Liefert die Default-Werte fuer eine Reihen von Attributwerten
+   * eines FeatureTypes.
+   * @param ft ein Feature-Type
+   * @param destArray Array, in dem die Objekte abgelegt werden (wenn {@code null},
+   *                 wird ein neuer Array erzeugt)
+   * @param destStartIdx Start-Index in {@code destArray} ab dem die Attributwerte
+   *                     abgelegt werden
+   * @param attrName Attribute aus {@link f}
+   * @return {@code destArray} oder einen neuen Array {@code Object[destStartIdx + attrName.length]}
+   */
+  public static Object[] getDefaultAttributeValues(FeatureType ft, Object[] destArray, int destStartIdx, String... attrName) {
+    // Wenn keine Attribute angegeben sind, alle verwenden
+    int attrCount = attrName.length > 0 ? attrName.length : ft.getAttributeCount();
+
+    if ( destArray == null )
+      destArray = new Object[destStartIdx + attrCount];
+    if ( destArray.length < destStartIdx + attrCount )
+      throw new IllegalArgumentException("Destination array "+destArray.length+" with start index "+destStartIdx+" too small for "+attrName.length+" attribute values!");
+
+    // Wenn keine Attribute angegeben sind, alle verwenden
+    for (int i=0; i<attrCount; i++) {
+      AttributeType attrType = attrName.length > 0 ? ft.getAttributeType(attrName[i]) : ft.getAttributeType(i);
+      // wenn attrType = null, dann gibt es das Attribut im FeatureType nicht
+      // -> destArray nicht ueberschreiben
+      if ( attrType != null )
+        destArray[ destStartIdx+i ] = getDefaultAttributeValue(attrType);
+    }
+    return destArray;
+  }
+
+  /**
+   * Liefert den Default-Wert fuer ein Attribut.
+   * @param attrType Attribut-Typ
+   * @return {@code null}, wenn das Attribut NILLABLE ist
+   */
+  public static Object getDefaultAttributeValue(AttributeType attrType) {
+    return( getDefaultAttributeValue(attrType, true) );
+  }
+
+  /**
+   * Liefert den Default-Wert fuer ein Attribut.
+   * @param attrType Attribut-Typ
+   * @param allowNull wenn {@code false}, wird in jedem Fall ein Wert ungleich
+   *                  {@code null} geliefert, auch wenn das Attribut NILLABLE ist
+   */
+  public static Object getDefaultAttributeValue(AttributeType attrType, boolean allowNull) {
+//    AutoValueGenerator avg = getAutoValueGenerator(attrType);
+//    Object attrValue = (avg == null) ? attrType.createDefaultValue() : avg.getNextValue();
+    Object attrValue = attrType.createDefaultValue();
+    if ( attrValue == null && ( !attrType.isNillable() || !allowNull ) ) {
+      attrValue = getDefaultAttributeValue( attrType.getBinding() );
+      if ( attrValue == null )
+        LOGGER.warn("Could not create default value for not-null attribute '"+attrType.getLocalName()+"': "+attrType.getBinding().getSimpleName());
+    }
+    return attrValue;
+  }
+
+  /**
+   * Liefert den Default-Wert fuer ein Attribut.
+   * @param attrType Attribut-Typ
+   * @return {@code null}, falls {@link DataUtilities#defaultValue(Class)} keinen
+   *         Defaul-Wert ermitteln kann (anstelle einer Exception!)
+   */
+  public static Object getDefaultAttributeValue(Class attrType) {
+    Object attrValue = null;
+    try {
+      // DataUtilities.defaultValue(.) kann BigDecimal und BigInteger
+      // nicht verarbeiten (gibt null zurueck!)
+      if ( BigDecimal.class.isAssignableFrom( attrType ) )
+        return new BigDecimal(0.0);
+      if ( BigInteger.class.isAssignableFrom( attrType ) )
+        return new BigInteger("0");
+      attrValue = DataUtilities.defaultValue(attrType);
+    } catch (IllegalArgumentException err) {
+      // no default value could be generated for this type
+      // --> ignore that
+    }
+    return attrValue;
+  }
+
+  /**
+   * Liefert eine Reihen von Attributwerten eines Features.
+   * @param f ein Feature
+   * @param destArray Array, in dem die Objekte abgelegt werden (wenn {@code null},
+   *                 wird ein neuer Array erzeugt)
+   * @param destStartIdx Start-Index in {@code destArray} ab dem die Attributwerte
+   *                     abgelegt werden
+   * @param attrName Attribute des Features, deren Werte geliefert werden (wenn
+   *                 {@code null} werden alle Attribute geliefert)
+   * @return {@code destArray} oder einen neuen Array {@code Object[destStartIdx + attrName.length]}
+   */
+  public static Object[] getAttributeValues(Feature f, Object[] destArray, int destStartIdx, String... attrName) {
+    if ( destArray == null )
+      destArray = new Object[destStartIdx + attrName.length];
+    if ( destArray.length < destStartIdx + attrName.length )
+      throw new IllegalArgumentException("Destination array "+destArray.length+" with start index "+destStartIdx+" too small for "+attrName.length+" attribute values!");
+
+    // Wenn keine Attribute angegeben sind, alle verwenden
+    int attrCount = attrName.length > 0 ? attrName.length : f.getNumberOfAttributes();
+    for (int i=0; i<attrCount; i++) {
+      Object attr = attrName.length > 0 ? f.getAttribute(attrName[i]) : f.getAttribute(i);
+      // wenn attr = null, dann gibt es das Attribut im Feature nicht
+      // -> destArray nicht ueberschreiben
+      if ( attr != null )
+        destArray[ destStartIdx+i ] = attr;
+    }
+    return destArray;
+  }
+
+  /**
+   * Liefert eine Reihen von Attributwerten eines Features.
+   * @param f ein Feature
+   * @param attrName Attribute des Features, deren Werte geliefert werden (wenn
+   *                 {@code null} werden alle Attribute geliefert)
+   * @return neuer Array {@code Object[attrName.length]}
+   */
+  public static Object[] getAttributeValues(Feature f, String... attrName) {
+    return getAttributeValues(f,null,0,attrName);
+  }
+
+  /**
+   * Liefert eine Reihen von Attributwerten eines Features.
+   * @param f ein Feature
+   * @param destMap Map, in dem die Objekte abgelegt werden (wenn {@code null},
+   *                 wird eine neue {@link HashMap} erzeugt)
+   * @param attrName Attribute des Features, deren Werte geliefert werden (wenn
+   *                 {@code null} werden alle Attribute geliefert)
+   * @return {@code destArray} oder einen neuen Array {@code Object[destStartIdx + attrName.length]}
+   */
+  public static Map<String,Object> getAttributeValues(Feature f, Map<String,Object> destMap, String... attrName) {
+    if ( destMap == null )
+      destMap = new HashMap<String,Object>();
+
+    if ( attrName.length > 0 )
+      for (int i=0; i<attrName.length; i++)
+        destMap.put( attrName[i], f.getAttribute(attrName[i]) );
+    else
+      for (int i=0; i<f.getNumberOfAttributes(); i++)
+        destMap.put( f.getFeatureType().getAttributeType(i).getName(), f.getAttribute(i) );
+
+    return destMap;
+  }
+
+  /**
+   * Fuehrt eine Join-Operation zwischen einer {@link FeatureCollection} und
+   * einem {@link ResultSet} durch, in dem Zusatz-Informationen hinterlegt.
+   * Hierzu wird das ResultSet zunaecht in eine {@link FeatureCollection}
+   * umgewandelt und anschliessend ein Left-Outer-EquiJoin zwischen
+   * den beiden Collections ausgewertet.
+   * @param fc          eine FeatureCollection
+   * @param fcJoinAttr  JOIN-Attribut in der FeatureCollection
+   * @param rs          ResultSet mit Zusatz-Informationen
+   * @param rsJoinAttr  JOIN-Attribut im ResultSet
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection joinFeatureCollection(FeatureCollection fc, String fcJoinAttr, ResultSet rs, String rsJoinAttr, String... projAttr) throws SchemaException, IllegalAttributeException, SQLException  {
+    return joinFeatureCollection(fc, fcJoinAttr, AttributeFilter.EQUALS, rs, rsJoinAttr);
+  }
+
+  /**
+   * Fuehrt eine Join-Operation zwischen einer {@link FeatureCollection} und
+   * einem {@link ResultSet} durch, in dem Zusatz-Informationen hinterlegt.
+   * Hierzu wird das ResultSet zunaecht in eine {@link FeatureCollection}
+   * umgewandelt und anschliessend ein Left-Outer-Join zwischen
+   * den beiden Collections ausgewertet.
+   * @param fc          eine FeatureCollection
+   * @param fcJoinAttr  JOIN-Attribut in der FeatureCollection
+   * @param compareOp   Operation, mit der der JOIN-Vergleich durchgefuehrt wird
+   * @param rs          ResultSet mit Zusatz-Informationen
+   * @param rsJoinAttr  JOIN-Attribut im ResultSet
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection joinFeatureCollection(FeatureCollection fc, String fcJoinAttr, AttributeFilter compareOp, ResultSet rs, String rsJoinAttr, String... projAttr) throws SchemaException, IllegalAttributeException, SQLException  {
+    return joinFeatureCollection(JoinType.LEFT_OUTER, fc, fcJoinAttr, compareOp, rs, rsJoinAttr, projAttr);
+
+  }
+
+  /**
+   * Fuehrt eine Join-Operation zwischen einer {@link FeatureCollection} und
+   * einem {@link ResultSet} durch, in dem Zusatz-Informationen hinterlegt.
+   * Hierzu wird das ResultSet zunaecht in eine {@link FeatureCollection}
+   * umgewandelt und anschliessend ein Join zwischen
+   * den beiden Collections ausgewertet.
+   * @param joinType    Join-Typ
+   * @param fc          eine FeatureCollection
+   * @param fcJoinAttr  JOIN-Attribut in der FeatureCollection
+   * @param compareOp   Operation, mit der der JOIN-Vergleich durchgefuehrt wird
+   * @param rs          ResultSet mit Zusatz-Informationen
+   * @param rsJoinAttr  JOIN-Attribut im ResultSet
+   * @return eine <b>neue</b> Instanz von {@link FeatureCollection}
+   * @throws SchemaException falls das Erweitern des Feature-Schemas scheitert
+   * @throws IllegalAttributeException falls das Einfuegen der Features in die
+   *         neue {@link FeatureCollection} scheitert
+   */
+  public static FeatureCollection joinFeatureCollection(JoinType joinType, FeatureCollection fc, String fcJoinAttr, AttributeFilter compareOp, ResultSet rs, String rsJoinAttr, String... projAttr) throws SchemaException, IllegalAttributeException, SQLException  {
+    if ( fcJoinAttr == null || fcJoinAttr.trim().equals("") ) {
+      // Erste Nicht-Geometrie-Spalte suchen
+      for (AttributeType aType : fc.getSchema().getAttributeTypes()) {
+        if ( aType != fc.getSchema().getDefaultGeometry() ) {
+          fcJoinAttr = aType.getName();
+          break;
+        }
+      }
+      LOGGER.warn("Join-Attribut for FeatureCollection not set. First attribute used: "+fcJoinAttr);
+    }
+    if ( rsJoinAttr == null || rsJoinAttr.trim().equals("") ) {
+      rsJoinAttr = rs.getMetaData().getColumnName(1);
+      LOGGER.warn("Join-Attribut for ResultSet not set. First column used: "+rsJoinAttr);
+    }
+
+    // FeatureCollection aus ResultSet erzeugen
+    FeatureCollection rsFeatureCollection = createFeatureCollection(
+        rs,
+        // fuer den JOIN muss das Join-Attribut vorhanden sein!
+        projAttr.length > 0 ? LangUtil.extendArray(projAttr,rsJoinAttr) : projAttr
+    );
+
+    // JOIN durchfuehren
+    return joinFeatureCollection(
+        joinType,              // Join-Typ
+        fc,                    // FeatureCollection
+        fcJoinAttr,            // Join-Attr. in FeatureCollection
+        compareOp,             // Join-Operator
+        rsFeatureCollection,   // ResultSet als FeatureCollection
+        rsJoinAttr,            // Join-Attr. in ResultSet
+        projAttr               // Projektions-Attribute
+    );
+  }
+
+
+  /**
+   * Erzeugt eine {@link FeatureCollection} aus einem {@linkplain ResultSet SQL-Result}.
+   * @param  rs        SQL-Result
+   * @param  projAttr  nur diese Attribute des ResultSet werden in die FeatureCollection
+   *                   uebernommen (wenn nicht angegeben, werden ALLE Attribute
+   *                   uebernommen)
+   * @throws SQLException falls der Zugriff auf die SQL-Metadaten scheitert
+   * @throws SchemaException falls das Erzeugen des FeatureType scheitert
+   * @throws IllegalAttributeException falls das Erzeugen eines Features scheitert
+   */
+  public static FeatureCollection createFeatureCollection(ResultSet rs, String... projAttr) throws SchemaException, IllegalAttributeException, SQLException {
+    // Schema der FeatureCollection erzeugen
+    FeatureType rsFeatureType = createFeatureType(rs.getMetaData(),projAttr);
+
+    // Array fuer die Attribut-Werte eines Features
+    Object[] rsFeatureValues = new Object[rsFeatureType.getAttributeCount()];
+
+    // ResultSet in FeatureCollection uebertragen
+    FeatureCollection rsFeatureCollection = DefaultFeatureCollections.newCollection();
+    for ( ;rs.next(); ) {
+      for (int i=0; i<rsFeatureType.getAttributeCount(); i++)
+        rsFeatureValues[i] = rs.getObject( rsFeatureType.getAttributeType(i).getName() );
+      rsFeatureCollection.add( rsFeatureType.create(rsFeatureValues) );
+    }
+
+    return rsFeatureCollection;
+  }
+
+  /**
+   * Erzeugt einen {@link FeatureType} aus einem {@linkplain ResultSetMetaData SQL-Result}.
+   * @param  metaData         SQL-Metadaten des {@link ResultSet}
+   * @param  projAttr         nur diese Attribute des ResultSet werden in den FeatureType
+   *                          uebernommen (wenn nicht angegeben, werden ALLE
+   *                          Attribute uebernommen)
+   * @throws SQLException falls der Zugriff auf die SQL-Metadaten scheitert
+   * @throws SchemaException falls das Erzeugen des FeatureType scheitert
+   */
+  public static FeatureType createFeatureType(ResultSetMetaData metaData, String... projAttr) throws SQLException, SchemaException {
+    try {
+      return createFeatureType(metaData, null, Object.class, projAttr);
+    } catch (ClassNotFoundException err) {
+      // KANN NICHT VORKOMMEN, da Object.class als Standard-Klasse angegeben
+      // ist. Exception wird nur abgefangen, damit sie nicht in throw-clause
+      // deklariert (und in uebergeordneter Methode) abgefangen werden muss
+      return null;
+    }
+  }
+
+  /**
+   * Erzeugt einen {@link FeatureType} aus einem {@linkplain ResultSet SQL-Result}.
+   * @param  metaData         SQL-Metadaten des {@link ResultSet}
+   * @param  featureTypeName  Name fuer den FeatureType (kann {@code null} sein)
+   * @param  defaultAttrClass Klasse, die verwendet als Attribut-Typ wird, wenn
+   *                          die entsprechende Klasse des ResultSet nicht zur
+   *                          Verfuegung steht (wenn {@code null}, wird eine
+   *                          Exception geworfen)
+   * @param  projAttr         nur diese Attribute des ResultSet werden in den FeatureType
+   *                          uebernommen (wenn nicht angegeben, werden ALLE
+   *                          Attribute uebernommen)
+   * @throws SQLException falls der Zugriff auf die SQL-Metadaten scheitert
+   * @throws SchemaException falls das Erzeugen des FeatureType scheitert
+   * @throws ClassNotFoundException falls eine Attribut-Klasse aus dem ResultSet
+   *         nicht erzeugt werden kann und keine Default-Klasse angegeben ist
+   */
+  public static FeatureType createFeatureType(ResultSetMetaData metaData, String featureTypeName, Class defaultAttrClass, String... projAttr) throws SQLException, SchemaException, ClassNotFoundException  {
+    if ( featureTypeName == null )
+      featureTypeName = "ResultSetFeatureType";
+
+    // Projektions-Attribute in ArrayList uebertragen
+    Set<String> projAttrList = new HashSet<String>();
+    for (String attrName : projAttr)
+      projAttrList.add(attrName);
+
+    // Fuer alle Attribute aus Metadaten einen AttributeType erzeugen
+    // und diesen in FeatureType uebernehmen
+    FeatureTypeBuilder   featureTypeFac = FeatureTypeBuilder.newInstance(featureTypeName);
+    for (int i=1; i<=metaData.getColumnCount(); i++) {
+      String columnName = metaData.getColumnName(i);
+      // Attribut nur uebernehmen, wenn als Projektions-Attribut angegeben
+      if ( projAttr.length > 0 && !projAttrList.contains(columnName) )
+        continue;
+
+      Class columnClass = defaultAttrClass;
+      try {
+        columnClass = Class.forName( metaData.getColumnClassName(i) );
+      } catch (ClassNotFoundException err) {
+        if ( defaultAttrClass == null )
+          throw err;
+        // Fehler Ignorieren und Object als Attribut-Klasse verwenden
+        LOGGER.warn("Column '"+columnName+"' ("+i+"): Class '"+metaData.getColumnClassName(i)+ "' not found. '"+columnClass.getName()+"' used.");
+      }
+      boolean nillable     = metaData.isNullable(i) != ResultSetMetaData.columnNoNulls;
+      Object  defaultValue = getDefaultAttributeValue(columnClass);
+      if ( defaultValue == null && !nillable)
+        LOGGER.warn("Could not create default value for not-null attribute '"+columnName+"': "+columnClass.getSimpleName());
+
+      // Add an attribute for the column to the FeatureType
+      featureTypeFac.addType( AttributeTypeFactory.newAttributeType(
+        columnName,
+        columnClass,
+        nillable,
+        null, // no restrictions
+        defaultValue,
+        null  // no meta data
+      ));
+    }
+    return featureTypeFac.getFeatureType();
+  }
+
+  /**
+   * Erzeugt einen {@link FeatureType} aus Beispiel-Attribut-Werten.
+   * @param  featureTypeName  Name fuer den FeatureType (kann {@code null} sein)
+   * @param  attrValues       Attribut-Werte
+   * @param  defaultAttrClass Standard-Typ, der fuer ein Attribut verwendet wird,
+   *                          wenn der angegebene Attribut-Wert {@code null} ist.
+   * @throws SchemaException falls das Erzeugen des FeatureType scheitert
+   */
+  public static FeatureType createFeatureType(String featureTypeName, Map<String,Object> attrValues,  Class defaultAttrClass) throws SchemaException  {
+    if ( featureTypeName == null )
+      featureTypeName = "FeatureType";
+
+    FeatureTypeBuilder featureTypeFac = FeatureTypeBuilder.newInstance(featureTypeName);
+    for (String attrName : attrValues.keySet()) {
+      Object attrValue = attrValues.get( attrName );
+      Class  attrClass = attrValue == null ? defaultAttrClass : attrValue.getClass();
+      featureTypeFac.addType( AttributeTypeFactory.newAttributeType(
+        attrName,
+        attrClass
+      ));
+    }
+    return featureTypeFac.getFeatureType();
+  }
+
+  /**
+   * Erzeugt einen Geometrie-Attribut-Typ.
+   * @param geomType  Art der Geometrie
+   * @param defGeom   Standard-Wert
+   * @param crs       Koordinatensystem
+   */
+  public static GeometricAttributeType createGeometryAttributeType(Class<? extends Geometry> geomType, Geometry defGeom, CoordinateReferenceSystem crs) {
+    return new GeometricAttributeType(
+      "the_geom",
+      geomType,
+      false,
+      defGeom,
+      crs,
+      null
+    );
+  }
+
+  /**
+   * Registriert einen {@link AutoValueGenerator} fuer einen {@link AttributeType}, so
+   * dass {@link #getNextAutoValue(AttributeType)} fuer diesen {@link AttributeType}
+   * verwendet werden kann.
+   * @param aType ein {@link AttributeType}
+   * @param generator generiert automatische Attribut-Werte
+   */
+  public static void registerAutoValueGenerator(AttributeType aType, AutoValueGenerator generator) {
+    autoAttrValueGenerators.put(aType, generator);
+  }
+
+  /**
+   * Entfernt einen {@link AutoValueGenerator} fuer einen {@link AttributeType}.
+   * @param aType ein {@link AttributeType}
+   */
+  public static void unregisterAutoValueGenerator(AttributeType aType) {
+    if ( autoAttrValueGenerators.containsKey( aType ) )
+      autoAttrValueGenerators.remove(aType);
+  }
+
+  /**
+   * Liefert den {@link AutoValueGenerator} fuer einen {@link AttributeType}.
+   * @param aType ein {@link AttributeType}
+   * @return {@code null}, wenn fuer den {@link AttributeType} noch kein
+   *         {@link AutoValueGenerator} registriert worden ist
+   */
+  public static AutoValueGenerator getAutoValueGenerator(AttributeType aType) {
+    return autoAttrValueGenerators.get(aType);
+  }
+
+  /**
+   * Liefert den naechsten Auto-Wert fuer einen {@link AttributeType}.
+   * @param aType ein {@link AttributeType}
+   * @exception UnsupportedOperationException falls fuer das Attribut noch
+   *            kein {@link AutoValueGenerator} registriert ist
+   */
+  public static Object getNextAutoValue(AttributeType aType) {
+    AutoValueGenerator avg = getAutoValueGenerator(aType);
+    if ( avg == null )
+      throw new UnsupportedOperationException("No AutoValueGenerator registered for attribute type: "+aType.getLocalName());
+    return avg.getNextValue();
+  }
+
+//  /**
+//   * Extrahiert alle Geometrien aus einer FeatureCollection. Fuer jedes
+//   * Geometry-Attribut der FeatureCollection wird eine GeometrieCollection
+//   * (<code>JTSGeometries</code>) angelegt, in der die Geometrien des jeweiligen
+//   * Attributs zusammengefasst werden.
+//   * @param fc FeatureCollection
+//   * @return Array aller Geometrien im Feature.
+//   * @see org.geotools.feature.FeatureCollection
+//   * @see org.geotools.renderer.geom.JTSGeometries
+//   */
+//  public static GeometryCollection[] extractGeometriesToGeometryCollection(FeatureCollection fc) throws org.opengis.referencing.operation.TransformException{
+//    FeatureIterator it        = fc.features(); // Zeiger durch die einzelnen Features
+//    Vector          gcVec     = new Vector();  // Speichert die Collections
+//    int             nextGCIdx = 0;             // verweist innerhaln eines Features
+//                                               // auf die naechste zu verwendende
+//                                               // Collection
+//
+//    // alle Features nach Geometrien durchsuchen
+//    for (int i=0; it.hasNext(); i++) {
+//      Feature f = it.next();
+//
+//      // Das erste Geometry-Attribut des Features wird in GeometryCollection
+//      // eins gespeichert, das zweite in der zweiten, usw.
+//      // Bei jedem Feature wird also wieder von vorne begonnen.
+//      nextGCIdx = 0;
+//
+//      // Alle Attribute auf Geometrien checken
+//      for (int j=0; j<f.getFeatureType().getAttributeCount(); j++) {
+//        // Handelt es sich um eine Geometrie, wird sie in einer
+//        // GeometryCollection gespeichert (fuer jedes Geometry-Attr.
+//        // gibt es eine eigene Collection!!)
+////        if ( f.getFeatureType().getAttributeType(j).isGeometry() ){
+//        if ( f.getFeatureType() instanceof GeometryAttributeType ) {
+//          // gibt es noch keine Collection fuer das Attr., wird eine
+//          // neue erstellt
+//          if ( nextGCIdx >= gcVec.size() )
+//            gcVec.add( new JTSGeometries() );
+//          // Geometrie in Collection einfuegen
+//          ((JTSGeometries)gcVec.elementAt(nextGCIdx++)).add((Geometry)f.getAttribute(j));
+//        }
+//      }
+//    }
+//
+//    GeometryCollection[] gcArr = new GeometryCollection[gcVec.size()];
+//    for (int i=0; i<gcVec.size(); i++)
+//      gcArr[i] = (GeometryCollection)gcVec.elementAt(i);
+//    return gcArr;
+//  }
+}
+

Added: trunk/src/schmitzm/geotools/feature/NumberValueGenerator.java
===================================================================
--- trunk/src/schmitzm/geotools/feature/NumberValueGenerator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/NumberValueGenerator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,68 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.feature;
+
+import org.geotools.feature.AttributeType;
+
+/**
+ * This interface represents a generator for {@link Number} values.
+ * Starting with an individual value, the next value is increased
+ * with every call of {@link #getNextValue()}. The increase interval
+ * can also be set individually.
+ * @see FeatureUtil#registerAutoValueGenerator(AttributeType, AutoValueGenerator)
+ * @see FeatureUtil#getAutoValueGenerator(AttributeType)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class NumberValueGenerator extends AbstractAutoValueGenerator<Number> {
+
+  /** Holds the increment the next value is generated with. */
+  protected Number increment = 0;
+
+  /**
+   * Creates a new generator with first value 0 and increment 1.
+   */
+  public NumberValueGenerator() {
+    this(0);
+  }
+
+  /**
+   * Creates a new generator with increment 1.
+   * @param firstValue first value generated by {@link #generateNextValue()}
+   */
+  public NumberValueGenerator(Number firstValue) {
+    this(firstValue,1);
+  }
+
+  /**
+   * Creates a new generator.
+   * @param firstValue first value generated by {@link #generateNextValue()}
+   * @param increment  increment the next value is generated with (can also be
+   *                   less zero)
+   */
+  public NumberValueGenerator(Number firstValue, Number increment) {
+    super(firstValue == null ? 0 : firstValue);
+    if ( increment.doubleValue() == 0 )
+      throw new IllegalArgumentException("Increment 0 not allowed!");
+    this.increment = increment;
+  }
+
+  /**
+   * Returns the next value.
+   */
+  public Number getNextValue() {
+    if ( lastValue == null )
+      lastValue = firstValue;
+    else
+      lastValue = lastValue.doubleValue() + increment.doubleValue();
+    return lastValue;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/feature/package.html
===================================================================
--- trunk/src/schmitzm/geotools/feature/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/feature/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen für die Feature-Verwaltung, die auf der
+	<a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek basieren.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/geotools/grid/GridStatistic.java
===================================================================
--- trunk/src/schmitzm/geotools/grid/GridStatistic.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/GridStatistic.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,68 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.grid;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+// nur fuer Doku
+import org.geotools.coverage.grid.GridCoverage2D;
+
+/**
+ * Diese Klasse stellt Informationen ueber ein Raster dar, die mit
+ * {@link GridUtil#determineStatistic(GridCoverage2D,int)} erstellt wurden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GridStatistic {
+  /** Breite des Rasters in Zellen.*/
+  public int    widthC    = 0;
+  /** Hoehe des Rasters in Zellen.*/
+  public int    heightC   = 0;
+  /** Kleinster horizontaler Zellen-Index des Rasters (inklusive).*/
+  public int    minX     = 0;
+  /** Kleinster vertikalter Zellen-Index des Rasters (inklusive).*/
+  public int    minY     = 0;
+  /** Groesster horizontaler Zellen-Index des Rasters (exklusive).*/
+  public int    maxX     = 0;
+  /** Groesster vertikaler Zellen-Index des Rasters (exklusive).*/
+  public int    maxY     = 0;
+  /** Reale Breite des Rasters.*/
+  public double widthR    = 0;
+  /** Reale Hoehe des Rasters.*/
+  public double heightR   = 0;
+  /** Latitude der suedwestlichen Ecke.*/
+  public double latSW    = 0;
+  /** Longitude der suedwestlichen Ecke.*/
+  public double lonSW   = 0;
+  /** Breite einer Raster-Zelle.*/
+  public double cellWidth = 0;
+  /** Hoehe des Rasters in Zellen.*/
+  public double cellHeight   = 0;
+  /** Werte, die NoData signalisieren. */
+  public double[] noDataVal = new double[0];
+  /** Kleinster Raster-Wert (ohne NoData-Zellen).*/
+  public double minValue = 0;
+  /** Groesster Raster-Wert (ohne NoData-Zellen).*/
+  public double maxValue = 0;
+  /** Summe aller Raster-Werte (ohne NoData-Zellen).*/
+  public double sumValue = 0;
+  /** Durchschnittlicher Raster-Wert (ohne NoData-Zellen).*/
+  public double avgValue = 0;
+  /** Anzahl an Wert-Zellen */
+  public double cellCnt = 0;
+  /** Anzahl an NoData-Zellen */
+  public double nodataCnt = 0;
+  /** Anzahl an Zellen unterschiedlicher Ausprägung - jede Wert erhält einen Key, Value ist die jeweilige Anzahl der Zellen ohne NaN */
+  public SortedMap<String, Integer> histogramm = new TreeMap<String, Integer>();
+
+}

Added: trunk/src/schmitzm/geotools/grid/GridUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/grid/GridUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/GridUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,845 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.grid;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+
+import org.geotools.brewer.color.ColorPalette;
+import org.geotools.coverage.FactoryFinder;
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.RasterSymbolizer;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.opengis.coverage.grid.GridRange;
+import org.opengis.coverage.PointOutsideCoverageException;
+import org.opengis.coverage.SampleDimension;
+import org.opengis.geometry.Envelope; // gt2-2.4.2
+//import org.opengis.spatialschema.geometry.Envelope; // gt2-2.3.4
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.LinearRing;
+
+import schmitzm.data.WritableGrid;
+import schmitzm.data.WritableGridArray;
+import schmitzm.data.AbstractWritableGrid;
+import schmitzm.geotools.GTUtil;
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.lang.LangUtil;
+import schmitzm.swing.SwingUtil;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse stellt statische Funktionen fuer die Arbeit mit
+ * {@linkplain GridCoverage2D Rasterdaten} zur Verfuegung.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.01
+ */
+public class GridUtil {
+
+        /** log4j initialize */
+        private static final Category cCat = Logger.getLogger(GridUtil.class.getName());
+
+      public static final GridCoverageFactory GRID_FAC = new GridCoverageFactory();
+
+	/**
+	 * The default name for style created by createDefaultStyle();
+	 */
+	  public static final String DEFAULT_RASTER_STYLE_NAME = "default raster style";
+	  public static final String DEFAULT_RASTER_STYLE_TITLE = "default raster style";
+	  public static final String UNNAMED_RASTER_STYLE_NAME = "unnamed raster style";
+	  public static final String UNTITLED_RASTER_STYLE_TITLE = "untitled raster style";
+
+	  /**
+	   * 11 Default colors used by {@link #createDiscreteStyle(GridCoverage2D, double, Color...)} if
+	   * no colors are given.
+	   * <ol>
+	   *   <li>White</li>
+	   *   <li>Red</li>
+	   *   <li>Blue</li>
+	   *   <li>Yellow</li>
+	   *   <li>Green</li>
+	   *   <li>Gray</li>
+	   *   <li>Orange</li>
+	   *   <li>Pink</li>
+	   *   <li>Magenta</li>
+       *   <li>Light gray</li>
+	   *   <li>Cyan</li>
+	   *   <li>Black</li>
+	   * </ol>
+	   */
+      public static final Color[] DEFAULT_COLORS = new Color[] {
+          Color.WHITE,
+          Color.RED,
+          Color.BLUE,
+          Color.YELLOW,
+          Color.GREEN,
+          Color.GRAY,
+          Color.ORANGE,
+          Color.PINK,
+          Color.MAGENTA,
+          Color.LIGHT_GRAY,
+          Color.CYAN,
+          Color.BLACK
+      };
+
+
+      /**
+	   * Liefert statistische Daten ueber ein Grid und erstellt ein Histogramm
+	   * @param gc ein Raster
+	   * @param band zu untersuchendes Band
+	   * @param histogramSteps Schritte des Histogramms, -1 wenn alle Ausprägungen, 0 für ohne Histogramm
+	   * @param histogrammDigits Anzahl an Nachkommastellen auf die die Histogramm-Klassen gerundet werden
+	   *                         (negative Werte = Vorkommastellen; null = kein Runden). Nur relevant, wenn
+	   *                         {@code hishistogramSteps} = -1
+	   * @author Martin Schmitz
+	   * @author Andreas Enders
+	   */
+	  public static GridStatistic determineStatistic(GridCoverage2D gc, int band, int histogramSteps, Integer histogrammDigits) {
+	    GridStatistic gs = new GridStatistic();
+	    GridRange     gr = gc.getGridGeometry().getGridRange();
+	    Envelope2D    ev = gc.getEnvelope2D();
+	    gs.minX          = gr.getLower(0);
+	    gs.minY          = gr.getLower(1);
+	    gs.maxX          = gr.getUpper(0);
+	    gs.maxY          = gr.getUpper(1);
+	    gs.widthC        = gs.maxX - gs.minX;
+	    gs.heightC       = gs.maxY - gs.minY;
+	    gs.widthR        = ev.getLength(0);
+	    gs.heightR       = ev.getLength(1);
+	    gs.latSW         = ev.getX();
+	    gs.lonSW         = ev.getY();
+	    gs.cellWidth     = (gs.widthC != 0) ? gs.widthR / gs.widthC : 0;
+	    gs.cellHeight    = (gs.heightC != 0) ? gs.heightR / gs.heightC : 0;
+
+	    gs.nodataCnt     = 0;
+	    gs.cellCnt       = 0;
+	    gs.noDataVal     = gc.getSampleDimensions()[band].getNoDataValues();
+	    if ( gs.noDataVal == null )
+	      gs.noDataVal = new double[0];
+	    gs.sumValue      = 0;
+
+
+	    // Check auf NoData-Zellen erfolgt ueber Hash-Tabelle
+	    Hashtable isNoData = new Hashtable<Double,Boolean>(gs.noDataVal.length);
+	    for (int i=0; i<gs.noDataVal.length; i++)
+	      isNoData.put(gs.noDataVal[i],true);
+
+	    // Objekte fuer Koordinate
+	    Point2D  p        = new Point2D.Float();
+	    double[] val      = new double[ gc.getSampleDimensions().length ];
+
+	    double lon = gs.lonSW + gs.cellHeight/2;
+	    for (int y=gs.minY; y<gs.maxY; y++, lon+=gs.cellHeight) {
+	      double lat = gs.latSW + gs.cellWidth/2;
+	      for (int x=gs.minX; x<gs.maxX; x++, lat+=gs.cellWidth) {
+	        p.setLocation(lat,lon);
+	        gc.evaluate(p,val);
+	        // NoData-Zellen gehen nicht in die Statistik ein
+	        if ( Double.isNaN(val[band]) || isNoData.containsKey(val[band]) )
+	          gs.nodataCnt++;
+	        else {
+	          gs.minValue = Math.min(gs.minValue, val[band]);
+	          gs.maxValue = Math.max(gs.maxValue, val[band]);
+	          gs.sumValue += val[band];
+	          gs.cellCnt++;
+	        }
+	      }
+	    }
+	    //histogramm berechnen, wenn nicht ausgeschaltet
+	    if (histogramSteps != 0)
+	    {
+	      lon = gs.lonSW + gs.cellHeight/2;
+	      for (int y=gs.minY; y<gs.maxY; y++, lon+=gs.cellHeight)
+	      {
+	        double lat = gs.latSW + gs.cellWidth/2;
+	        for (int x=gs.minX; x<gs.maxX; x++, lat+=gs.cellWidth)
+	        {
+	          p.setLocation(lat,lon);
+	          gc.evaluate(p,val);
+	          // NoData-Zellen gehen nicht in die Statistik ein
+	          if ( !Double.isNaN(val[band]) && !isNoData.containsKey(val[band]) )
+	          {
+	            double tVal = val[band];
+	            if (histogramSteps < 0)
+	            {
+	              //String tValue = ""+(new Double(tVal).longValue());
+	              String value = String.valueOf( histogrammDigits != null ? LangUtil.round(tVal,histogrammDigits) : tVal );
+	              Integer pixelCount = gs.histogramm.get(value);
+	              if (pixelCount == null)
+	                pixelCount = 1;
+	              else
+	                pixelCount++;
+	              gs.histogramm.put(value, pixelCount);
+	            }
+	            else
+	            {
+	              double stepRange = (gs.maxValue - gs.minValue)/histogramSteps;
+	              int step = new Double((tVal - gs.minValue)/stepRange).intValue();
+	              String stepName = ""+gs.minValue + step * stepRange;
+	              Integer number = gs.histogramm.get(stepName);
+	              if (number == null) number = 1;
+	              else number++;
+	              gs.histogramm.put(stepName, number);
+	            }
+	          }
+	        }
+	      }
+              cCat.debug("Histogram created with map: "+gs.histogramm);
+	    }
+	    // Durchschnittswert berechnen (nicht ueber NoData-Zellen)
+	    gs.avgValue = (gs.cellCnt != 0) ? gs.sumValue/gs.cellCnt : 0;
+
+	    return gs;
+	  }
+
+
+      /**
+       * Liefert statistische Daten ueber ein Grid und erstellt ein Histogramm.
+       * Die Histogramm-Klassen werden auf ganze Zahlen gerundet.
+       * @param gc ein Raster
+       * @param band zu untersuchendes Band
+       * @param histogramSteps Schritte des Histogramms, -1 wenn alle Ausprägungen, 0 für ohne Histogramm
+       * @author Martin Schmitz
+       * @author Andreas Enders
+       */
+      public static GridStatistic determineStatistic(GridCoverage2D gc, int band, int histogramSteps) {
+        return determineStatistic(gc, band, histogramSteps, 0);
+      }
+
+  /**
+   * Liefert statistische Daten ueber ein Grid, ohne ein Histogram zu berechnen.
+   * @param gc ein Raster
+   * @param band zu untersuchendes Band
+   */
+  public static GridStatistic determineStatistic(GridCoverage2D gc, int band) {
+	  return determineStatistic(gc, band, 0);
+  }
+
+  /**
+   * Liefert statistische Daten ueber ein Grid, das in Zonen unterteilt ist.
+   * Die Zonen werden durch einen {@link Float}-Wert identifiziert, damit
+   * auch NoData-Werte in einem Raster einer Zone (naemlich der
+   * Zone {@code Float.NaN}) zugewiesen werden koennen.
+   * @param valueGC Raster ueber das die Statistik erzeugt wird
+   * @param zoneGC Raster, das die Zonen bestimmt
+   */
+  public static GridZoneStatistic<Float> determineZoneStatistic(GridCoverage2D valueGC, GridCoverage2D zoneGC) {
+    return determineZoneStatistic(valueGC, 0, zoneGC, 0);
+  }
+
+  /**
+   * Liefert statistische Daten ueber ein Grid, das in Zonen unterteilt ist.
+   * Die Zonen werden durch einen {@link Float}-Wert identifiziert, damit
+   * auch NoData-Werte in einem Raster einer Zone (naemlich der
+   * Zone {@code Float.NaN}) zugewiesen werden koennen.
+   * @param valueGC Raster ueber das die Statistik erzeugt wird
+   * @param valueBand Band ueber das die Statistik erzeugt wird
+   * @param zoneGC Raster, das die Zonen bestimmt
+   * @param zoneBand Band, das die Zonen bestimmt
+   */
+  public static GridZoneStatistic<Float> determineZoneStatistic(GridCoverage2D valueGC, int valueBand, GridCoverage2D zoneGC, int zoneBand) {
+    final ReadableGridCoverage zoneGrid  = ReadableGridCoverage.create(zoneGC, 0);
+
+    // Performanz: Raster-Ausmasse in lokalen Variablen speichern
+    final float         cellHeight = (float)zoneGrid.getCellHeight();
+    final float         cellWidth  = (float)zoneGrid.getCellHeight();
+    final float         minX       = (float)zoneGrid.getX() + cellWidth/2;
+    final float         maxX       = (float)(zoneGrid.getX() + zoneGrid.getRealWidth());
+    final float         minY       = (float)zoneGrid.getY() + cellHeight/2;
+    final float         maxY       = (float)(zoneGrid.getY() + zoneGrid.getRealHeight());
+
+//    // Solution without WritableGrid
+//    final Envelope2D    env        = zoneGC.getEnvelope2D();
+//    final GridRange     gr         = zoneGC.getGridGeometry().getGridRange();
+//    final float         height     = (float)env.getHeight();
+//    final float         width      = (float)env.getWidth();
+//    final int           heightC    = Math.abs( gr.getLower(1) - gr.getUpper(1) );
+//    final int           widthC     = Math.abs( gr.getLower(0) - gr.getUpper(0) );
+//    final float         cellHeight = heightC != 0 ? height / heightC : 0;
+//    final float         cellWidth  = widthC  != 0 ? width  / widthC  : 0;
+//    final float         minX       = (float)env.getX() + cellWidth/2;
+//    final float         maxX       = (float)(env.getX() + width);
+//    final float         minY       = (float)env.getY() + cellHeight/2;
+//    final float         maxY       = (float)(env.getY() + height);
+
+    final Point2D.Float loc        = new Point2D.Float();
+    // Arrays zum Speichern der Rasterwerte pro Band
+    final float[]  zoneGridVal  = new float[zoneGC.getNumSampleDimensions()];
+    final float[]  valueGridVal = new float[zoneGC.getNumSampleDimensions()];
+    // Variablen zum Speichern des Band-Werts
+    float zoneNo  = 0;
+    float cellVal = 0;
+
+    // Zonen-Statistik berechnen
+    GridZoneStatistic    zoneStat  = new GridZoneStatistic<Float>();
+    for (loc.y=minY; loc.y<maxY; loc.y+=cellHeight)
+      for (loc.x=minX; loc.x<=maxX; loc.x+=cellWidth) {
+        zoneGC.evaluate(loc, zoneGridVal);
+        zoneNo = zoneGridVal[zoneBand];
+        try {
+          valueGC.evaluate(loc, valueGridVal);
+          cellVal = valueGridVal[valueBand];
+          zoneStat.addValueToZoneStatistic(zoneNo, cellVal);
+        } catch (PointOutsideCoverageException err) {
+          // Zonen-Zelle liegt ausserhalb des Wert-Rasters
+          // --> Zelle ignorieren
+        }
+      }
+
+    return zoneStat;
+  }
+
+  /**
+   * Erzeugt einen Standard-Style fuer ein {@link GridCoverage2D}
+   * und setzt den default-Namen: GridUtil.DEFAULT_RASTER_STYLE_NAME
+   * Und eine title : GridUtil.DEFAULT_RASTER_STYLE_TITLE
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @author Stefan Alfons Krüger (DEFAULT_RASTER_STYLE_NAME, DEFAULT_RASTER_STYLE_TITLE)
+   */
+  public static Style createDefaultStyle() {
+    Style defaultStyle = createStyle(null,1.0);
+    defaultStyle.setName(DEFAULT_RASTER_STYLE_NAME);
+    defaultStyle.setTitle(DEFAULT_RASTER_STYLE_TITLE);
+	return( defaultStyle );
+  }
+
+  /**
+   * Erzeugt einen Standard-Style fuer ein {@link GridCoverage2D}
+   * @param colorMap Farb-Palette
+   * @param opacity  Transparenz (1.0 = vollfarbig)
+   * @return ein Standard-Style ( {@code StyleBuilder.createRasterSymbolizer()}) falls
+   *         keine ColorMap angegeben wird
+   */
+  public static Style createStyle(ColorMap colorMap, double opacity) {
+    StyleBuilder builder = StylingUtil.STYLE_BUILDER;
+    RasterSymbolizer symb = colorMap != null ? builder.createRasterSymbolizer(colorMap,opacity) : builder.createRasterSymbolizer();
+    return( builder.createStyle( symb ) );
+  }
+
+  /**
+   * Creates an interpolative style for a {@link GridCoverage2D}. The given colors
+   * are interpolated from the minimum to the maximum raster value.
+   * @param grid     the raster
+   * @param opacity  opacity for the style
+   * @param colors   colors to interpolate with (at least 2)
+   */
+  public static Style createInterpolativeStyle(GridCoverage2D grid, double opacity, Color... colors) {
+    if ( colors.length < 2 )
+      throw new IllegalArgumentException("At least 2 colors must be specified for an interpolative style.");
+    GridStatistic gridStat  = GridUtil.determineStatistic(grid, 0);
+    double   intervalLength = gridStat.maxValue - gridStat.minValue;
+    String[] labels         = new String[colors.length];
+    double[] values         = new double[colors.length];
+    for (int i=0; i<colors.length; i++) {
+      labels[i] = ""; // all categories must have the same label
+      values[i] = gridStat.minValue + intervalLength * i/(colors.length-1.0);
+    }
+    ColorMap colorMap = StylingUtil.STYLE_BUILDER.createColorMap(
+        labels,
+        values,
+        colors,
+        ColorMap.TYPE_RAMP
+    );
+    RasterSymbolizer rasterSymb = StylingUtil.STYLE_BUILDER.createRasterSymbolizer(colorMap, opacity);
+    return StylingUtil.STYLE_BUILDER.createStyle( rasterSymb );
+  }
+
+  /**
+   * Creates a discrete style for a {@link GridCoverage2D}. For every value
+   * in the raster another color is choosen. If the raster contains more values
+   * than colors are given, all surplus values are mapped to the "last" color.
+   * @param grid          the raster
+   * @param opacity       opacity for the style
+   * @param digits        number of digits the grid value classes (and legend) are
+   *                      rounded to (null means no round; >= 0 means digits after comma;
+   *                      < 0 means digits before comma)
+   * @param colorPalette  colors for the values (the smallest value is mapped to index 0, the second
+   *                      smallest to index 1, ...); if no colors are specified {@link #DEFAULT_COLORS}
+   *                      are used
+   */
+  public static Style createDiscreteStyle(GridCoverage2D grid, double opacity, Integer digits, Color... colorPalette) {
+    if ( colorPalette.length == 0 )
+      colorPalette = DEFAULT_COLORS;
+    DecimalFormat decFormat = digits != null ? new DecimalFormat( SwingUtil.getNumberFormatPattern(digits) ) : null;
+    GridStatistic gridStat  = GridUtil.determineStatistic(grid, 0, -1, digits);
+
+    int      valueCount = gridStat.histogramm.size();
+    String[] labels     = new String[valueCount];
+    double[] values     = new double[valueCount];
+    Color[]  colors     = new Color[valueCount];
+    Color    lastColor  = colorPalette[colorPalette.length-1];
+
+    int i = -1;
+    for (String key : gridStat.histogramm.keySet()) {
+      i++;
+      values[i] = Double.parseDouble(key);
+      colors[i] = i < colorPalette.length ? colorPalette[i] : lastColor;
+      if ( decFormat != null )
+        labels[i] = decFormat.format(values[i]);
+      else
+        labels[i] = String.valueOf(values[i]);
+      //labels[i] += " ("+gridStat.histogramm.get(key)+" pixel)";
+    }
+
+    ColorMap tColorMap = StylingUtil.STYLE_BUILDER.createColorMap(
+        labels,
+        values,
+        colors,
+        ColorMap.TYPE_VALUES
+    );
+
+    RasterSymbolizer rasterSymb = StylingUtil.STYLE_BUILDER.createRasterSymbolizer(tColorMap, opacity);
+    return StylingUtil.STYLE_BUILDER.createStyle( rasterSymb );
+  }
+
+  /**
+   * Kopiert ein {@link GridCoverage2D} komplett.
+   * @param source Quell-Raster
+   */
+  public static GridCoverage2D copyGridCoverage(GridCoverage2D source) {
+    //return createGridCoverage(source,source.getEnvelope());
+    return GRID_FAC.create(
+        source.getName(),
+        source.getRenderedImage().copyData(null),
+        source.getEnvelope()
+    );
+  }
+
+  /**
+   * Kopiert ein {@link GridCoverage2D} komplett, dessen Raster-Koordinaten
+   * (obere-linke Ecke) transformiert wird.
+   * @param source Quell-Raster
+   * @param minX   neue Raster-Koordinate fuer die obere linke Ecke
+   * @param minY   neue Raster-Koordinate fuer die obere linke Ecke
+   */
+  public static GridCoverage2D copyGridCoverage(GridCoverage2D source, int minX, int minY) {
+    return GRID_FAC.create(
+                      source.getName(),
+                      source.getRenderedImage().copyData(null).createWritableTranslatedChild(minX, minY),
+                      source.getEnvelope()
+           );
+  }
+
+  /**
+   * Erzeugt ein neues {@link GridCoverage2D} mit neuer {@link SampleDimension}.
+   * @param gc        Raster (mit nur einem Band)
+   * @param sampleDim neue {@link SampleDimension}
+   * @return eine neue {@link GridCoverage2D}-Instanz
+   */
+  public static GridCoverage2D resampleGridCoverage(GridCoverage2D gc, GridSampleDimension sampleDim) {
+    return resampleGridCoverage(gc, new GridSampleDimension[] {sampleDim});
+  }
+
+  /**
+   * Erzeugt ein neues {@link GridCoverage2D} mit neuer {@link SampleDimension}.
+   * @param gc        Raster
+   * @param sampleDim neue {@link SampleDimension}
+   * @return eine neue {@link GridCoverage2D}-Instanz
+   */
+  public static GridCoverage2D resampleGridCoverage(GridCoverage2D gc, GridSampleDimension[] sampleDim) {
+    final Map properties = new HashMap();
+    //  properties.put("GC_NODATA", -9999.0);
+
+    return FactoryFinder.getGridCoverageFactory(null).create(
+        gc.getName(),
+        gc.getRenderedImage(),
+        (GridGeometry2D)gc.getGridGeometry(),
+        sampleDim,
+        null, //new GridCoverage[] { raster },
+        properties
+    );
+  }
+
+  /**
+   * Erzeugt ein {@link GridCoverage2D} aus einem anderen. Das neue Raster
+   * hat maximal die Ausmasse des Quell-Rasters.
+   * @param source Quell-Raster
+   * @param bboxEnv Bounding-Box (beschreibt die den Ausschnitt des Quell-Rasters aus dem
+   *                das neue Raster erzeugt wird)
+   * @return {@code null} falls sich das Quell-Raster und die Bounding-Box nicht
+   *         ueberschneiden.
+   */
+  public static GridCoverage2D createGridCoverage(GridCoverage2D source, Envelope bboxEnv) {
+    Envelope2D sourceEnv = source.getEnvelope2D();
+    // Subset nur bzgl. des Bereichs in dem auch das Raster liegt
+    bboxEnv = GTUtil.intersectEnvelope(sourceEnv,bboxEnv,source.getCoordinateReferenceSystem());
+    if ( bboxEnv.getLength(0) == 0 && bboxEnv.getLength(1) == 0 )
+      return null;
+
+    // Daten des Source-Raster
+    double realMinX     = sourceEnv.getX();
+    double realMinY     = sourceEnv.getY();
+    double realWidth    = sourceEnv.getWidth();
+    double realHeight   = sourceEnv.getHeight();
+    int    rasterMinX   = source.getRenderedImage().getMinX();
+    int    rasterMinY   = source.getRenderedImage().getMinY();
+    int    rasterWidth  = source.getRenderedImage().getWidth();
+    int    rasterHeight = source.getRenderedImage().getHeight();
+    double cellWidth    = realWidth / rasterWidth;
+    double cellHeight   = realHeight / rasterHeight;
+
+    // Envelope in Raster-Koordinaten (bzgl. unterer linker Ecke!)
+    int subsetLX = AbstractWritableGrid.convertRealToRaster(bboxEnv.getLowerCorner().getOrdinate(0),realMinX,realWidth,rasterMinX,rasterWidth);
+    int subsetLY = AbstractWritableGrid.convertRealToRaster(bboxEnv.getLowerCorner().getOrdinate(1),realMinY,realHeight,rasterMinY,rasterHeight);
+    int subsetUX = AbstractWritableGrid.convertRealToRaster(bboxEnv.getUpperCorner().getOrdinate(0),realMinX,realWidth,rasterMinX,rasterWidth);
+    int subsetUY = AbstractWritableGrid.convertRealToRaster(bboxEnv.getUpperCorner().getOrdinate(1),realMinY,realHeight,rasterMinY,rasterHeight);
+    int subsetW  = Math.abs(subsetLX-subsetUX)+1;
+    int subsetH  = Math.abs(subsetLY-subsetUY)+1;
+    // obere linke Ecke des Subsets in Raster-Koordinaten
+    int ulX = Math.min(subsetLX,subsetUX);
+    int ulY = rasterMinY+rasterHeight - Math.max(subsetLY,subsetUY) -1;
+
+    // WritableRaster fuer Subset erstellen
+    Rectangle       rect = new Rectangle(ulX,ulY,subsetW,subsetH);
+    Raster          r    = source.getRenderedImage().getData( rect );
+    WritableRaster  wr   = createWritableRaster(r);
+
+    // Envelope fuer Subset erzeugen (bzgl. untere liner Ecke!)
+    Envelope subsetEnv = new Envelope2D(
+        source.getCoordinateReferenceSystem(),
+        AbstractWritableGrid.convertRasterToReal(subsetLX,rasterMinX,realMinX,cellWidth)-cellWidth/2,
+        AbstractWritableGrid.convertRasterToReal(subsetLY,rasterMinY,realMinY,cellHeight)-cellHeight/2,
+        subsetW * cellWidth,
+        subsetH * cellHeight
+    );
+
+    return new GridCoverageFactory().create("",wr,subsetEnv);
+  }
+
+
+  /**
+   * Erzeugt ein {@link WritableRaster} aus einem {@link Raster}.
+   * @param r Raster
+   * @param minX Start-Index in X-Richtung
+   * @param minY Start-Index in Y-Richtung
+   */
+  public static WritableRaster createWritableRaster(Raster r, int minX, int minY) {
+    return Raster.createWritableRaster(
+        new ComponentSampleModel(r.getDataBuffer().getDataType(),
+                                 r.getWidth(),
+                                 r.getHeight(),
+                                 1,
+                                 r.getWidth(),
+                                 new int[] {0}),
+        r.getDataBuffer(),
+        new Point(minX,minY)
+    );
+  }
+
+  /**
+   * Erzeugt ein {@link WritableRaster} aus einem {@link Raster}.
+   * @param r Raster
+   */
+  public static WritableRaster createWritableRaster(Raster r) {
+    return createWritableRaster(r,0,0);
+  }
+
+  /**
+   * Konvertiert ein Raster-Objekt in eine {@link GridCoverage2D}.
+   * Es werden folgende Objekt-Typen unterstuetzt:
+   * <ul>
+   *   <li>Instanzen von {@link GridCoverage2D}</li>
+   *   <li>Instanzen von {@link WritableRaster} <u>und</u> {@link WritableGrid}</li>
+   *   <li>Instanzen von {@link WritableGridArray}</li>
+   * </ul>
+   * @param obj Object
+   * @exception IllegalArgumentException falls das Objekt nicht konvertiert werden kann
+   */
+  public static GridCoverage2D convertToGridCoverage2D(Object obj) {
+    if ( obj instanceof GridCoverage2D )
+      return (GridCoverage2D)obj;
+
+    if ( obj instanceof WritableRaster  && obj instanceof WritableGrid ) {
+      // GT-GridCoverage aus WritableGridRaster erzeugen
+      WritableRaster wr = (WritableRaster)obj;
+      WritableGrid   wg = (WritableGrid)obj;
+      Envelope2D     ev = new Envelope2D(wg.getCoordinateReferenceSystem(),wg.getX(),wg.getY(),wg.getRealWidth(),wg.getRealHeight());
+      return new GridCoverageFactory().create("",wr,ev);
+    }
+
+    if ( obj instanceof WritableGridArray ) {
+      // GT-GridCoverage aus WritableGridRaster erzeugen
+      WritableGridArray wga = (WritableGridArray)obj;
+      Envelope2D        ev = GTUtil.createEnvelope2D(wga.getEnvelope(),wga.getCoordinateReferenceSystem());
+      WritableRaster    wr = Raster.createWritableRaster(
+          new ComponentSampleModel(wga.getSampleType(),
+                                   wga.getWidth(),
+                                   wga.getHeight(),
+                                   1,
+                                   wga.getWidth(),
+                                   new int[] {0}
+          ),
+          wga.getDataBuffer(),
+          new Point(0,0)
+      );
+      return new GridCoverageFactory().create("",wr,ev);
+    }
+
+    throw new IllegalArgumentException("Object can not be converted to GridCoverage2D: "+(obj==null ? "null" : obj.getClass().getSimpleName()));
+
+  }
+
+
+  /**
+   * Wandelt eine Geo-Referenz in Raster-Koordinaten um.
+   * @param gc   Raster auf dem die Koordinaten definiert sind
+   * @param geoX horizontale Geo-Referenz (Longitude)
+   * @param geoY vertikale Geo-Referenz (Latitude)
+   * @return 2-dimensionalen Array mit dem X-Index in Element 0 und dem Y-Index
+   *         in Element 1
+   */
+  public static int[] convertRealToRaster(GridCoverage2D gc, double geoX, double geoY) {
+    return convertRealToRaster(gc, new double[] { geoX, geoY } );
+  }
+
+  /**
+   * Wandelt eine Geo-Referenz in Raster-Koordinaten um.
+   * @param env      Geo-Referenz des Rasters
+   * @param image    Gesamt-Auspraegung des Rasters
+   * @param geoX     horizontale Geo-Referenz (Longitude)
+   * @param geoY     vertikale Geo-Referenz (Latitude)
+   * @return 2-dimensionalen Array mit dem X-Index in Element 0 und dem Y-Index
+   *         in Element 1
+   */
+  public static int[] convertRealToRaster(Envelope2D env, RenderedImage image, double geoX, double geoY) {
+    return convertRealToRaster(env, image, new double[] { geoX, geoY } );
+  }
+
+  /**
+   * Wandelt eine Geo-Referenz in Raster-Koordinaten um.
+   * @param gc   Raster auf dem die Koordinaten definiert sind
+   * @param geoCoord Geo-Referenz (Longitude,Latitude)
+   * @return 2-dimensionalen Array mit dem X-Index in Element 0 und dem Y-Index
+   *         in Element 1
+   */
+  public static int[] convertRealToRaster(GridCoverage2D gc, double[] geoCoord) {
+    return convertRealToRaster(gc.getEnvelope2D(), gc.getRenderedImage(), geoCoord);
+  }
+
+  /**
+   * Wandelt eine Geo-Referenz in Raster-Koordinaten um.
+   * @param env      Geo-Referenz des Rasters
+   * @param image    Gesamt-Auspraegung des Rasters
+   * @param geoCoord Geo-Referenz (Longitude,Latitude)
+   * @return 2-dimensionalen Array mit dem X-Index in Element 0 und dem Y-Index
+   *         in Element 1
+   */
+  public static int[] convertRealToRaster(Envelope2D env, RenderedImage image, double[] geoCoord) {
+    final double realMinX = env.getX();
+    final double realMinY = env.getY();
+    final double realWidth = env.getWidth();
+    final double realHeight = env.getHeight();
+    final int    rasterMinX = image.getMinX();
+    final int    rasterMinY = image.getMinY();
+    final int    rasterWidth = image.getWidth();
+    final int    rasterHeight = image.getHeight();
+
+    int cellX = AbstractWritableGrid.convertRealToRaster(geoCoord[0],realMinX,realWidth,rasterMinX,rasterWidth);
+    int cellY = AbstractWritableGrid.convertRealToRaster(geoCoord[1],realMinY,realHeight,rasterMinY,rasterHeight);
+    // Ursprung der Raster-Koord. ist OBEN LINKS
+    cellY = rasterMinY+rasterHeight - cellY - 1;
+
+    return new int[] {cellX, cellY};
+  }
+
+
+  /**
+   * Liefert die Zellen eines Rasters (in Raster-Koordinaten), die von einem
+   * {@link LineString} geschnitten werden.
+   * @param gc ein Raster
+   * @param ls Linienzug, auf den getestet wird
+   * @param result Menge, in die die gefunden Zellen eingefuegt werden
+   *               (kann {@code null} sein)
+   * @return leere Menge, falls der Linienzug ausserhalb des Rasters liegt
+   */
+  public static Set<Point> getOverlappingCells(GridCoverage2D gc, LineString ls, Set<Point> result) {
+    if ( result == null )
+      result = new HashSet<Point>();
+
+    Envelope2D gridEnv      = gc.getEnvelope2D();
+    double     realMinX     = gridEnv.getX();
+    double     realMinY     = gridEnv.getY();
+    double     realWidth    = gridEnv.getWidth();
+    double     realHeight   = gridEnv.getHeight();
+    double     realMaxX     = realMinX + realWidth;
+    double     realMaxY     = realMinY + realHeight;
+    int        rasterMinX   = gc.getRenderedImage().getMinX();
+    int        rasterMinY   = gc.getRenderedImage().getMinY();
+    int        rasterWidth  = gc.getRenderedImage().getWidth();
+    int        rasterHeight = gc.getRenderedImage().getHeight();
+    double     cellWidth    = realWidth / rasterWidth;
+    double     cellHeight   = realHeight / rasterHeight;
+
+    Coordinate[]    cellBounds = new Coordinate[] { new Coordinate(), new Coordinate(), new Coordinate(), new Coordinate() };
+    GeometryFactory geomFac    = new GeometryFactory();
+
+    // Rasterzellen ermitteln, in denen die Eckpunkte des LineStrings (BB)
+    // liegen
+    com.vividsolutions.jts.geom.Envelope lsBB = ls.getEnvelopeInternal();
+    double bbMinX = Math.max(realMinX, lsBB.getMinX());
+    double bbMinY = Math.max(realMinY, lsBB.getMinY());
+    double bbMaxX = Math.min(realMaxX, lsBB.getMaxX());
+    double bbMaxY = Math.min(realMaxY, lsBB.getMaxY());
+    int[] lsMinCell = convertRealToRaster(gc,bbMinX,bbMaxY);
+    int[] lsMaxCell = convertRealToRaster(gc,bbMaxX,bbMinY);
+
+    bbMinX = realMinX + lsMinCell[0] * cellWidth;
+    bbMaxX = realMinX + (lsMaxCell[0]+1) * cellWidth;
+    bbMinY = realMinY + (rasterHeight-(lsMaxCell[1]+1)) * cellHeight;
+    bbMaxY = realMinY + (rasterHeight-lsMinCell[1]) * cellHeight;
+
+    // Alle Raster-Zellen in BB des LineString pruefen, ob sie sich mit dem dem
+    // LineString schneiden
+    for (double y = bbMinY; y<=bbMaxY; y+=cellHeight)
+      for (double x = bbMinX; x<=bbMaxX; x+=cellWidth) {
+        cellBounds[0].x = x;
+        cellBounds[0].y = y;
+        cellBounds[1].x = x+cellWidth;
+        cellBounds[1].y = y;
+        cellBounds[2].x = x+cellWidth;
+        cellBounds[2].y = y+cellHeight;
+        cellBounds[3]   = cellBounds[0];
+        if ( ls.intersects(geomFac.createLinearRing(cellBounds)) ) {
+          int[] cell = convertRealToRaster(gc, x, y);
+          result.add( new Point(cell[0], cell[1]) );
+        }
+
+      }
+
+    return result;
+  }
+
+  /**
+   * Markiert die Zellen eines Rasters (in Raster-Koordinaten), die von einer
+   * {@link Geometry} geschnitten werden.
+   * @param gc ein Raster
+   * @param g  Geometry, auf den getestet wird
+   * @param markValue Wert mit dem die Schnitt-Zellen im Ausgabe-Raster markiert werden
+   * @param result Raster, in dem die Schnitt-Zellen markiert werden (kann
+   *              {@code null} sein)
+   */
+  public static WritableRaster getOverlappingCells(GridCoverage2D gc, Geometry g, Number markValue, WritableRaster result) {
+    if ( result == null ) {
+      result = Raster.createWritableRaster(
+                     gc.getRenderedImage().getSampleModel(),
+                     new Point(
+                         gc.getRenderedImage().getMinX(),
+                         gc.getRenderedImage().getMinX()
+                     )
+      );
+    }
+
+    Envelope2D gridEnv      = gc.getEnvelope2D();
+    int        sampleType   = gc.getRenderedImage().getSampleModel().getDataType();
+    int        band         = 0;
+    double     realMinX     = gridEnv.getX();
+    double     realMinY     = gridEnv.getY();
+    double     realWidth    = gridEnv.getWidth();
+    double     realHeight   = gridEnv.getHeight();
+    double     realMaxX     = realMinX + realWidth;
+    double     realMaxY     = realMinY + realHeight;
+    int        rasterMinX   = gc.getRenderedImage().getMinX();
+    int        rasterMinY   = gc.getRenderedImage().getMinY();
+    int        rasterWidth  = gc.getRenderedImage().getWidth();
+    int        rasterHeight = gc.getRenderedImage().getHeight();
+    double     cellWidth    = realWidth / rasterWidth;
+    double     cellHeight   = realHeight / rasterHeight;
+
+
+    // Rasterzellen ermitteln, die in der BoundingBox der Geometrie liegen
+    // -> nur diese kommen in Frage!
+    com.vividsolutions.jts.geom.Envelope lsBB = g.getEnvelopeInternal();
+    double bbMinX = Math.max(realMinX, lsBB.getMinX());
+    double bbMinY = Math.max(realMinY, lsBB.getMinY());
+    double bbMaxX = Math.min(realMaxX, lsBB.getMaxX());
+    double bbMaxY = Math.min(realMaxY, lsBB.getMaxY());
+    int[] lsMinCell = convertRealToRaster(gc,bbMinX,bbMaxY);
+    int[] lsMaxCell = convertRealToRaster(gc,bbMaxX,bbMinY);
+
+    bbMinX = realMinX + lsMinCell[0] * cellWidth;
+    bbMaxX = realMinX + (lsMaxCell[0]+1) * cellWidth;
+    bbMinY = realMinY + (rasterHeight-(lsMaxCell[1]+1)) * cellHeight;
+    bbMaxY = realMinY + (rasterHeight-lsMinCell[1]) * cellHeight;
+
+    // Alle Raster-Zellen in BB pruefen, ob sie sich
+    // tatsaechlich mit der Geometrie schneiden
+    Coordinate[]    cellBounds = new Coordinate[] { new Coordinate(), new Coordinate(), new Coordinate(), new Coordinate() };
+    GeometryFactory geomFac    = new GeometryFactory();
+    for (double y = bbMinY; y<=bbMaxY; y+=cellHeight)
+      for (double x = bbMinX; x<=bbMaxX; x+=cellWidth) {
+        cellBounds[0].x = x;
+        cellBounds[0].y = y;
+        cellBounds[1].x = x+cellWidth;
+        cellBounds[1].y = y;
+        cellBounds[2].x = x+cellWidth;
+        cellBounds[2].y = y+cellHeight;
+        cellBounds[3]   = cellBounds[0];
+        if ( g.intersects(geomFac.createLinearRing(cellBounds)) ) {
+          int[] cell = convertRealToRaster(gc, x, y);
+          switch ( sampleType ) {
+            case DataBuffer.TYPE_BYTE:
+            case DataBuffer.TYPE_SHORT:
+            case DataBuffer.TYPE_USHORT:
+            case DataBuffer.TYPE_INT:
+                        result.setSample(cell[0],cell[1],band,markValue.intValue());
+                        break;
+            case DataBuffer.TYPE_FLOAT:
+                        result.setSample(cell[0],cell[1],band,markValue.floatValue());
+                        break;
+            case DataBuffer.TYPE_DOUBLE:
+                        result.setSample(cell[0],cell[1],band,markValue.doubleValue());
+                        break;
+          }
+        }
+      }
+
+    return result;
+  }
+
+
+  /**
+   * Markiert die Zellen eines Rasters (in Raster-Koordinaten), die von einer
+   * {@link Geometry} geschnitten werden.
+   * @param gc ein Raster
+   * @param g  Geometry, auf den getestet wird
+   * @param markValue Wert mit dem die Schnitt-Zellen im Ausgabe-Raster markiert werden
+   */
+  public static GridCoverage2D getOverlappingCells(GridCoverage2D gc, Geometry g, Number markValue) {
+    WritableRaster result = getOverlappingCells(gc,g,markValue,null);
+    return new GridCoverageFactory().create("",result,gc.getEnvelope());
+  }
+}

Added: trunk/src/schmitzm/geotools/grid/GridZoneStatistic.java
===================================================================
--- trunk/src/schmitzm/geotools/grid/GridZoneStatistic.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/GridZoneStatistic.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,256 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.grid;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Diese Klasse stellt Informationen ueber ein Raster dar, das in Zonen
+ * unterteilt ist.
+ * <ul>
+ *   <li>Anzahl an Zellen pro Zone</li>
+ *   <li>Summe der Zellwerte pro Zone</li>
+ *   <li>Durchschnitt der Zellwerte pro Zone</li>
+ *   <li>Anzahl an Zellen eines bestimmten Werts pro Zone</li>
+ * </ul>
+ * Der Typ, durch den die Zonen identifiziert werden, wird zum Instanzieerungszeitpunkt
+ * durch {@code <Z>} festgelegt.
+ * @see GridUtil#determineZoneStatistic(GridCoverage2D,GridCoverage2D)}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GridZoneStatistic<Z> {
+  /** Speichert Nummern der Zonen. */
+  protected SortedSet<Z>  zones = null;
+  /** Speichert die Anzahl der Zellen pro Zone und Wert. */
+  protected SortedMap<Z, SortedMap<Double,Integer>> detailCellCounts = null;
+  /** Speichert die Anzahl der Zellen pro Zone. */
+  protected SortedMap<Z, Integer> totalCellCounts = null;
+  /** Speichert die Summe der Zellwerte pro Zone. */
+  protected SortedMap<Z, Double>  cellSums = null;
+  /** Speichert den Durchschnitt der Zellwerte pro Zone. */
+  protected SortedMap<Z, Double>  cellAvgs = null;
+
+  /**
+   * Erzeugt eine leere {@code GridZoneStatistic}. Die Werte der Zonen muessen
+   * mittles {@link setZoneStatistic(int,int,double} gesetzt werden.
+   */
+  public GridZoneStatistic() {
+    this( new TreeMap<Z, Integer>(),  new TreeMap<Z, SortedMap<Double,Integer>>(), new TreeMap<Z, Double>(), new TreeMap<Z, Double>());
+  }
+
+  /**
+   * Erzeugt eine {@code GridZoneStatistic}.
+   * @param cellCounts Anzahl an Zellen pro Zone
+   * @param cellSums   Summe der Zellwerte pro Zone
+   */
+  public GridZoneStatistic(SortedMap<Z, Integer> totalCellCounts, SortedMap<Z, SortedMap<Double,Integer>> detailCellCounts, SortedMap<Z, Double> cellSums) {
+    this(totalCellCounts, detailCellCounts, cellSums, new TreeMap<Z, Double>());
+    for (Z zone : getZones()) {
+      int    cellCount = getTotalCellCount(zone);
+      double cellSum   = getCellSum(zone);
+      cellAvgs.put(zone, cellCount != 0 ? cellSum / cellCount : 0);
+    }
+  }
+
+  /**
+   * Erzeugt eine {@code GridZoneStatistic}.
+   * @param cellCounts Anzahl an Zellen pro Zone
+   * @param cellSums   Summe der Zellwerte pro Zone
+   * @param cellAvgs   Durchschnittswert der Zellen pro Zone
+   */
+  public GridZoneStatistic(SortedMap<Z, Integer> totalCellCounts, SortedMap<Z, SortedMap<Double,Integer>> detailCellCounts, SortedMap<Z, Double> cellSums, SortedMap<Z, Double> cellAvgs) {
+    this.cellSums   = cellSums;
+    this.cellAvgs   = cellAvgs;
+    this.detailCellCounts = detailCellCounts;
+    this.totalCellCounts = totalCellCounts;
+    this.zones      = new TreeSet( totalCellCounts.keySet() );
+  }
+
+  /**
+   * Liefert die Anzahl an Zonen.
+   */
+  public int getZoneCount() {
+    return zones.size();
+  }
+
+  /**
+   * Liefert die Nummern der Zonen.
+   */
+  public SortedSet<Z> getZones() {
+    return zones;
+  }
+
+  /**
+   * Prueft, ob es eine bestimmte Zone gibt.
+   * @param zone Nummer der Zone
+   */
+  public boolean containsZone(Z zone) {
+    return zones.contains(zone);
+  }
+
+  /**
+   * Leert die Statistik fuer eine Zone.
+   * @param zone Nummer der Zone
+   */
+  public void clearZoneStatistic(Z zone) {
+    zones.remove(zone);
+    cellAvgs.remove(zone);
+    cellSums.remove(zone);
+    totalCellCounts.remove(zone);
+    detailCellCounts.remove(zone);
+  }
+
+  /**
+   * Leert die Statistik aller Zonen.
+   */
+  public void clearZoneStatistic() {
+    while (!zones.isEmpty())
+      clearZoneStatistic( zones.first() );
+  }
+
+  /**
+   * Fuegt der Statistik einer Zone einen Wert hinzu.
+   * @param zone Nummer der Zone
+   * @param cellValue Zell-Wert
+   */
+  public void addValueToZoneStatistic(Z zone, double cellValue)
+  {
+    // Anzahl der Zellen des bestimmten Werts in Zone erhoehen
+    if ( !detailCellCounts.containsKey(zone) )
+      detailCellCounts.put(zone, new TreeMap<Double,Integer>());
+    SortedMap<Double,Integer> detailCellCount = this.detailCellCounts.get(zone);
+    detailCellCount.put(cellValue, getDetailCellCount(zone,cellValue)+1);
+    // Gesamtanzahl der Zonenwerte erhoehen
+    int cellCount = getTotalCellCount(zone)+1;
+    this.totalCellCounts.put(zone, cellCount);
+    // Gesamtsumme der Zonenwerte erhoehen
+    double cellSum = getCellSum(zone)+cellValue;
+    this.cellSums.put(zone, cellSum);
+    // Durchschnittswert der Zone erneuern
+    this.cellAvgs.put(zone, cellCount != 0 ? cellSum/cellCount : 0 );
+    // ggf. neue Zone in Zonen-Liste aufnehmen
+    this.zones.add(zone);
+  }
+
+  /**
+   * Liefert die Anzahl an Zellen in einer Zone
+   * @param zone Nummer der Zone
+   * @return 0 falls es die Zone nicht gibt
+   */
+  public int getTotalCellCount(Z zone) {
+    return (int)getValueFromZoneMap(zone, totalCellCounts);
+  }
+
+  /**
+   * Liefert die Anzahl an Zellen eines bestimmten Werts in einer Zone.
+   * @param zone Nummer der Zone
+   * @param cellValue Zell-Wert
+   * @return 0 falls es die Zone nicht gibt
+   */
+  public int getDetailCellCount(Z zone, double cellValue) {
+    SortedMap<Double,Integer> cellCounts = detailCellCounts.get(zone);
+    if ( cellCounts == null )
+      return 0;
+    Integer count = cellCounts.get(cellValue);
+    return count == null ? 0 : count.intValue();
+  }
+
+  /**
+   * Liefert die Zellenanzahl differenziert nach Zellwert fuer alle Zonen.
+   */
+  public SortedMap<Z, SortedMap<Double,Integer>> getDetailCellCountMap() {
+    return this.detailCellCounts;
+  }
+
+  /**
+   * Liefert die Zellenanzahl differenziert nach Zellwert fuer eine Zone.
+   * @param zone Nummer der Zone
+   */
+  public SortedMap<Double,Integer> getDetailCellCountMap(Z zone) {
+    return this.detailCellCounts.get(zone);
+  }
+
+  /**
+   * Liefert Anzahl an verschiedenen Zellen fuer eine Zone.
+   * @param zone Nummer der Zone
+   */
+  public int getValueCount(Z zone) {
+    if ( !containsZone(zone) )
+      return 0;
+    return this.detailCellCounts.get(zone).size();
+  }
+
+  /**
+   * Liefert die verschiedenen Werte, die in einer Zone vorkommen.
+   * @param zone Nummer der Zone
+   */
+  public SortedSet<Double> getValues(Z zone) {
+    if ( !containsZone(zone) )
+      return new TreeSet<Double>();
+    return new TreeSet<Double>( this.detailCellCounts.get(zone).keySet() );
+  }
+
+  /**
+   * Liefert die Gesamt-Zellenanzahl pro Zone als Map.
+   */
+  public SortedMap<Z, Integer> getTotalCellCountMap() {
+    return this.totalCellCounts;
+  }
+
+  /**
+   * Liefert die Summe der Zell-Werte in einer Zone
+   * @param zone Nummer der Zone
+   * @return 0 falls es die Zone nicht gibt
+   */
+  public double getCellSum(Z zone) {
+    return getValueFromZoneMap(zone, cellSums);
+  }
+
+  /**
+   * Liefert die Summen der Zellwerte pro Zone als Map.
+   */
+  public SortedMap<Z, Double> getCellSumMap() {
+    return this.cellSums;
+  }
+
+  /**
+   * Liefert den Durchschnitt der Zell-Werte in einer Zone
+   * @param zone Nummer der Zone
+   * @return 0 falls es die Zone nicht gibt
+   */
+  public double getCellAvg(Z zone) {
+    return getValueFromZoneMap(zone, cellAvgs);
+  }
+
+  /**
+   * Liefert die Durchschnittswerte der Zellen pro Zone als Map.
+   */
+  public SortedMap<Z, Double> getCellAvgMap() {
+    return this.cellAvgs;
+  }
+
+  /**
+   * Liefert einen Wert aus einer Zonen-Map.
+   * @param zone Nummer der Zone
+   * @param map eine Map
+   * @return 0, falls es in der Map keinen Eintrag fuer die Zone gibt
+   */
+  private <T extends Number> double getValueFromZoneMap(Z zone, SortedMap<Z, T> map) {
+    Number value = map.get(zone);
+    return value != null ? value.doubleValue() : 0;
+  }
+}

Added: trunk/src/schmitzm/geotools/grid/ReadableGridCoverage.java
===================================================================
--- trunk/src/schmitzm/geotools/grid/ReadableGridCoverage.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/ReadableGridCoverage.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,404 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.grid;
+
+import java.awt.Point;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.DataBuffer;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRenderedImage;
+import java.util.Map;
+import javax.media.jai.PlanarImage;
+import javax.media.jai.RasterFactory;
+
+import org.opengis.coverage.grid.GridCoverage;
+
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.geometry.Envelope2D;
+
+import appl.data.LoadingException;
+
+import schmitzm.data.AbstractReadableGrid;
+import schmitzm.data.AbstractWritableGrid;
+import schmitzm.data.ReadableGrid;
+import schmitzm.data.WritableGrid;
+
+/**
+ * Diese Klasse stellt ein {@linkplain GridCoverage2D GeoTools-GridCoverage} (2D) dar,
+ * welches auf einem {@link Raster} basiert und darauf direkten
+ * Lese- und Schreibzugriff liefert.<br>
+ * Auch wenn {@link Raster} prinzipell mehrere Dimensionen (Baender)
+ * zulaesst, sind die Zugriffsmethoden dieser Klasse auf ein Band beschraenkt.<br>
+ * <br>
+ * <br>Um Instanzen dieser Klasse zu erzeugen, sollten die Factory-Methoden
+ * {@code create(.)} verwendet werden!</b>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ReadableGridCoverage extends GridCoverage2D implements ReadableGrid {
+  /**
+   * Speichert die Datenbasis des Grids.
+   */
+  protected Raster raster = null;
+
+  /**
+   * Speichert das Band des {@link #raster WritableRasters}, welches durch dieses
+   * Grid angesprochen wird.
+   */
+  protected int band   = 0;
+
+  /**
+   * Speichert die Informationen ueber die GeoReferenz des Rasters.
+   */
+  protected Envelope2D envelope = null;
+
+  /**
+   * Ruft den Standard-Konstruktor der von {@link GridCoverage2D} auf.
+   * @param name          Name des Grids
+   * @param image         Image-Daten
+   * @param gridGeometry  Georeferenz und CRS
+   * @param bands         Sample-Dimensions fuer jedes Band (kann {@code null} sein)
+   * @param sources       Quell-Grids (kann {@code null} sein
+   * @param properties    Properties fuer das Grid (kann {@code null} sein)
+   * @param band          Band der Datenbasis, auf das die getter/setter referenziert sind
+   */
+  protected ReadableGridCoverage(CharSequence name, PlanarImage image, GridGeometry2D gridGeometry, GridSampleDimension[] bands, GridCoverage[] sources, Map properties, int band) {
+    super( name, image, gridGeometry, bands, sources, properties );
+    this.raster   = getRenderedImage().getData();
+    this.band     = band;
+    this.envelope = getEnvelope2D();
+  }
+
+  /**
+   * Erzeugt ein neues Grid.
+   * @param gc   Datenbasis fuer das Grid
+   * @param band Band der Datenbasis, auf das die getter/setter referenziert sind
+   */
+  public static ReadableGridCoverage create(GridCoverage2D gc, int band) {
+    return new ReadableGridCoverage(
+      gc.getName().toString(),
+      PlanarImage.wrapRenderedImage(gc.getRenderedImage()),
+      (GridGeometry2D)gc.getGridGeometry(),
+      gc.getSampleDimensions(),
+      null,
+      null,
+      band
+    );
+  }
+
+  /**
+   * Liefert eine direkte Referenz auf die Datenbasis.
+   */
+  public Raster getRaster() {
+    return raster;
+  }
+
+  /**
+   * Liefert die Breite des Rasters (in Zellen).
+   */
+  public int getWidth() {
+    return raster.getWidth();
+  }
+
+  /**
+   * Liefert die Hoehe des Rasters (in Zellen).
+   */
+  public int getHeight() {
+    return raster.getHeight();
+  }
+
+  /**
+   * Liefert den Index der ersten Zelle (Südwest) in X-Richtung.
+   */
+  public int getMinX() {
+    return raster.getMinX();
+  }
+
+  /**
+   * Liefert den Index der ersten Zelle (Südwest) in Y-Richtung.
+   */
+  public int getMinY() {
+    return raster.getMinY();
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealWidth() {
+    return envelope.getLength(0);
+  }
+
+  /**
+   * Liefert die reale Breite des Rasters.
+   */
+  public double getRealHeight() {
+    return envelope.getLength(1);
+  }
+
+  /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealWidth() / getWidth()</code>
+   */
+  public double getCellWidth() {
+    return getRealWidth() / getWidth();
+  }
+
+  /**
+   * Liefert die reale Breite einer Rasterzelle.
+   * @return <code>getRealHeight() / getHeight()</code>
+   */
+  public double getCellHeight() {
+    return getRealHeight() / getHeight();
+  }
+
+  /**
+   * Liefert die X-Koordinate der Georeferenz (Longitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   */
+  public double getX() {
+    return envelope.getX();
+  }
+
+  /**
+   * Liefert die Y-Koordinate der Georeferenz (Latitude) der linken unteren
+   * Ecke des Rasters (Südwest).
+   */
+  public double getY() {
+    return envelope.getY();
+  }
+
+  /**
+   * Liefert die Art der Daten, die im Raster gespeichert werden koennen. Diese
+   * wird durch eine der TYPE-Konstanten in {@link DataBuffer}
+   * repraesentiert.
+   */
+  public int getSampleType() {
+    return raster.getDataBuffer().getDataType();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   * @return je nach {@linkplain #getSampleType() Art} des Rasters einen
+   *         <code>int</code>, <code>float</code> oder <code>double</code>
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public Object getRasterSample(int... cell) {
+    if ( cell.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+//    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+//    // linken oberen Ecke. Im Grid soll die Zaehlung jedoch in der linken unteren
+//    // Ecke beginnen -> Y-Koordinate umrechnen
+//    int x = cell[0];
+//    int y = getMinY() + getHeight()-1 - cell[1];
+    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+    // linken oberen Ecke. Im Grid soll es genauso sein!
+    int x = cell[0];
+    int y = cell[1];
+    switch ( getSampleType() ) {
+      case DataBuffer.TYPE_BYTE:
+      case DataBuffer.TYPE_SHORT:
+      case DataBuffer.TYPE_USHORT:
+      case DataBuffer.TYPE_INT: return raster.getSample(x,y,band);
+      case DataBuffer.TYPE_FLOAT: return raster.getSampleFloat(x,y,band);
+      case DataBuffer.TYPE_DOUBLE: return raster.getSampleDouble(x,y,band);
+    }
+    return raster.getSample(x,y,band);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public short getRasterSampleAsShort(int... cell) {
+    return ((Number)getRasterSample(cell)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public byte getRasterSampleAsByte(int... cell) {
+    return ((Number)getRasterSample(cell)).byteValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public int getRasterSampleAsInt(int... cell) {
+    return ((Number)getRasterSample(cell)).intValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public long getRasterSampleAsLong(int... cell) {
+    return ((Number)getRasterSample(cell)).longValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public float getRasterSampleAsFloat(int... cell) {
+    return ((Number)getRasterSample(cell)).floatValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Raster-Koordinaten.
+   * @param cell 2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *             {@link #getMinX()} und {@link #getMinY()})
+   */
+  public double getRasterSampleAsDouble(int... cell) {
+    return ((Number)getRasterSample(cell)).doubleValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * Liegt der Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen,
+   * wird die naechst groessere Zelle gewaehlt (ausser die Grenze entspricht
+   * dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public Object getGridSample(double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    return getRasterSample(cellX,cellY);
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public short getGridSampleAsShort(double... coord) {
+    return ((Number)getGridSample(coord)).shortValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public byte getGridSampleAsByte(double... coord){
+    return ((Number)getGridSample(coord)).byteValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public int getGridSampleAsInt(double... coord){
+    return ((Number)getGridSample(coord)).intValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public long getGridSampleAsLong(double... coord){
+    return ((Number)getGridSample(coord)).longValue();
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public float getGridSampleAsFloat(double... coord){
+    return ((Number)getGridSample(coord)).floatValue();
+  }
+
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten.
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   */
+  public double getGridSampleAsDouble(double... coord){
+    return ((Number)getGridSample(coord)).doubleValue();
+  }
+
+  /**
+   * Konvertiert eine reale Koordinate in eine Zellennummer. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird
+   * die naechst "groessere" Zellegewaehlt.
+   * Ausnahme bildet der Rand des Rasters. Hier wird die kleinere Zelle
+   * (also die letzte) herangezogen
+   * @param coord Georeferenz-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public int convertRealToRaster(double coord, int dim) {
+    return AbstractReadableGrid.convertRealToRaster(this,coord,dim);
+  }
+
+  /**
+   * Konvertiert eine Zellennummer in reale Koordinate. Dabei wird die
+   * Koordinate der Zellenmitte zurueckgegeben.
+   * @param cell  Rasterzellen-Koordinate
+   * @param dim   Dimension, in der die Umrechnung erfolgen soll
+   * @exception UnsupportedOperationException falls eine ungueltige Dimension
+   *            angegeben wird (nur Werte 0 <= coord < {@link #RASTER_DIM} sind
+   *            zulaessig
+   */
+  public double convertRasterToReal(int cell, int dim) {
+    return AbstractReadableGrid.convertRasterToReal(this,cell,dim);
+  }
+
+  /**
+   * This class does not support late loading!
+   * @see appl.data.LateLoadable#unloadData()
+   * @return false;
+   * @see appl.data.LateLoadable#isLateLoadable()
+   */
+  public boolean isLateLoadable() {
+    return false;
+  }
+
+  /**
+   * Does nothing!
+   * This class does not support late loading!
+   * @see appl.data.LateLoadable#unloadData()
+   * @see appl.data.LateLoadable#loadData()
+   */
+  public void loadData() throws LoadingException {
+  }
+
+  /**
+   * Does nothing!
+   * This class does not support late loading!
+   * @see appl.data.LateLoadable#unloadData()
+   */
+  public void unloadData() {
+  }
+}

Added: trunk/src/schmitzm/geotools/grid/WritableGridCoverage.java
===================================================================
--- trunk/src/schmitzm/geotools/grid/WritableGridCoverage.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/WritableGridCoverage.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,200 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.grid;
+
+import java.awt.Point;
+import java.awt.image.DataBuffer;
+import java.awt.image.WritableRaster;
+import java.util.Map;
+
+import javax.media.jai.PlanarImage;
+import javax.media.jai.RasterFactory;
+
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.geometry.Envelope2D;
+import org.opengis.coverage.grid.GridCoverage;
+
+import schmitzm.data.WritableGrid;
+
+/**
+ * Diese Klasse stellt ein {@linkplain GridCoverage2D GeoTools-GridCoverage} (2D) dar,
+ * welches auf einem {@link WritableRaster} basiert und darauf direkten
+ * Lese- und Schreibzugriff liefert.<br>
+ * Auch wenn {@link WritableRaster} prinzipell mehrere Dimensionen (Baender)
+ * zulaesst, sind die Zugriffsmethoden dieser Klasse auf ein Band beschraenkt.<br>
+ * <br>
+ * <br>Um Instanzen dieser Klasse zu erzeugen, sollten die Factory-Methoden
+ * {@code create(.)} verwendet werden!</b>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class WritableGridCoverage extends ReadableGridCoverage implements WritableGrid {
+  /**
+   * Speichert die Datenbasis des Grids als {@link WritableRaster}.
+   * Entspricht {@link ReadableGridCoverage#raster}.
+   */
+  protected WritableRaster writableRaster = null;
+
+  /**
+   * Ruft den Standard-Konstruktor der von {@link GridCoverage2D} auf.
+   * @param name          Name des Grids
+   * @param image         Image-Daten (muss ein {@link WritableRaster} beinhalten)
+   * @param gridGeometry  Georeferenz und CRS
+   * @param bands         Sample-Dimensions fuer jedes Band (kann {@code null} sein)
+   * @param sources       Quell-Grids (kann {@code null} sein
+   * @param properties    Properties fuer das Grid (kann {@code null} sein)
+   * @param band          Band der Datenbasis, auf das die getter/setter referenziert sind
+   */
+  protected WritableGridCoverage(CharSequence name, PlanarImage image, GridGeometry2D gridGeometry, GridSampleDimension[] bands, GridCoverage[] sources, Map properties, int band) {
+    super( name, image, gridGeometry, bands, sources, properties, band );
+    if ( !(raster instanceof WritableRaster) )
+      throw new UnsupportedOperationException("Image for WritableGridCoverage mus provide a WritableRaster!");
+    this.writableRaster = (WritableRaster)raster;
+  }
+
+  /**
+   * Erzeugt ein neues Grid.
+   * @param gc   Datenbasis fuer das Grid
+   * @param band Band der Datenbasis, auf das die getter/setter referenziert sind
+   */
+  public static WritableGridCoverage create(GridCoverage2D gc, int band) {
+    return new WritableGridCoverage(
+      gc.getName().toString(),
+      PlanarImage.wrapRenderedImage(gc.getRenderedImage()),
+      (GridGeometry2D)gc.getGridGeometry(),
+      gc.getSampleDimensions(),
+      null,
+      null,
+      band
+    );
+  }
+
+  /**
+   * Erzeugt ein neues Grid.
+   * @param name     Name fuer das Grid
+   * @param raster   Datenbasis fuer das Grid
+   * @param band     Band der Datenbasis, auf das referenziert wird
+   * @param envelope GeoReferenz fuer das Raster
+   */
+  public static WritableGridCoverage create(String name, WritableRaster raster, int band, Envelope2D envelope) {
+    return create( new GridCoverageFactory().create(name,raster,envelope), band  );
+  }
+
+  /**
+   * Erzeugt ein neues Grid. Die Spalten sind von 0 bis <code>w-1</code>, die
+   * Zeilen von 0 bis <code>h-1</code> durchnummeriert.
+   * @param name     Name fuer das Grid
+   * @param type     Datentyp, der im Raster gespeichert ist (z.B. {@link DataBuffer#TYPE_INT})
+   * @param w        Breite des Rasters in Zellen
+   * @param h        Hoehe des Rasters in Zellen
+   * @param envelope GeoReferenz fuer das Raster
+   */
+  public static WritableGridCoverage create(String name, int type, int w, int h, Envelope2D envelope) {
+    return create(name,type,w,h,0,0,envelope);
+  }
+
+  /**
+   * Erzeugt ein neues Grid.
+   * @param name     Name fuer das Grid
+   * @param type     Datentyp, der im Raster gespeichert ist (z.B. {@link DataBuffer#TYPE_INT})
+   * @param w        Breite des Rasters in Zellen
+   * @param h        Hoehe des Rasters in Zellen
+   * @param x0       Index, mit dem die linke Spalte des Rasters angesprochen wird
+   * @param y0       Index, mit dem die oberste Zeile des Rasters angesprochen wird
+   * @param envelope GeoReferenz fuer das Raster
+   */
+  public static WritableGridCoverage create(String name, int type, int w, int h, int x0, int y0, Envelope2D envelope) {
+    return create(name,RasterFactory.createBandedRaster(type,w,h,1,new Point(x0,y0)), envelope);
+  }
+
+  /**
+   * Erzeugt ein neues Grid. Es wird automatisch auf Band 0 des WritableRasters
+   * Bezug genommen.
+   * @param name     Name fuer das Grid
+   * @param raster   Datenbasis fuer das Grid
+   * @param envelope GeoReferenz fuer das Raster
+   */
+  public static WritableGridCoverage create(String name, WritableRaster raster, Envelope2D envelope) {
+    return create(name,raster,0,envelope);
+  }
+
+//  /**
+//   * Erzeugt eine neue Instanz, basierend auf dem aktuellen Raster.
+//   * @deprecated Methode dient nur als Test/Notloesung und wird (hoffentlich)
+//   *             bald wieder entfernt werden koennen!
+//   */
+//  public WritableGridCoverage createNew() {
+////    return new WritableGridCoverage(this.getName().toString(),raster,envelope);
+//    return create(this.getName().toString(),raster,envelope);
+//  }
+
+  /**
+   * Liefert eine direkte Referenz auf die Datenbasis.
+   */
+  public WritableRaster getRaster() {
+    return writableRaster;
+  }
+
+  /**
+   * Setzt einen Wert im Raster ueber Raster-Koordinaten.
+   * @param value neuer Wert
+   * @param cell  2D-Raster-Koordinate (Zellenindizes, beginnend bei
+   *              {@link #getMinX()} und {@link #getMinY()})
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public void setRasterSample(Object value, int... cell) {
+    if ( cell.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+//    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+//    // linken oberen Ecke. Im Grid soll die Zaehlung jedoch in der linken unteren
+//    // Ecke beginnen -> Y-Koordinate umrechnen
+//    int x = cell[0];
+//    int y = getMinY() + getHeight()-1 - cell[1];
+    // Bei 'raster' (WritableRaster) beginnen die Raster-Koordinaten in der
+    // linken oberen Ecke. Im Grid soll es genauso sein!
+    int x = cell[0];
+    int y = cell[1];
+
+    switch ( getSampleType() ) {
+      case DataBuffer.TYPE_BYTE:
+      case DataBuffer.TYPE_SHORT:
+      case DataBuffer.TYPE_USHORT:
+      case DataBuffer.TYPE_INT: writableRaster.setSample(x,y,band,((Number)value).intValue());
+                                break;
+      case DataBuffer.TYPE_FLOAT: writableRaster.setSample(x,y,band,((Number)value).floatValue());
+                                  break;
+      case DataBuffer.TYPE_DOUBLE: writableRaster.setSample(x,y,band,((Number)value).doubleValue());
+                                   break;
+    }
+  }
+
+  /**
+   * Liefert einen Wert des Rasters ueber Geo-Koordinaten. Liegt der
+   * Koordinatenwert genau auf der Grenze zwischen zwei Rasterzellen, wird die
+   * naechst groessere Zelle gewaehlt (ausser die Grenze entspricht dem Raster-Rand!).
+   * @param coord  2D-Raster-Koordinate (Lat/Lon-GeoReferenz)
+   * @exception UnsupportedOperationException falls zu {@linkplain #RASTER_DIM wenig}
+   *            Koordinaten angegeben werden
+   */
+  public void setGridSample(Object value, double... coord) {
+    if ( coord.length < RASTER_DIM )
+      throw new UnsupportedOperationException(RASTER_DIM+" Coordinates expected");
+    // Koordinaten umrechnen in Zellen-Index
+    int cellX = convertRealToRaster(coord[0],0);
+    int cellY = convertRealToRaster(coord[1],1);
+    setRasterSample(value,cellX,cellY);
+  }
+}

Added: trunk/src/schmitzm/geotools/grid/package.html
===================================================================
--- trunk/src/schmitzm/geotools/grid/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/grid/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen für die Raster-Verwaltung, die auf der
+	<a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek basieren.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/geotools/gui/CRSSelectionDialog.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/CRSSelectionDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/CRSSelectionDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,455 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+import org.geotools.referencing.CRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.GTUtil;
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.ResourceProvider;
+import schmitzm.swing.ButtonGroup;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.JPanel;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.StatusDialog;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.SwingWorker;
+import schmitzm.swing.event.InputOptionAdapter;
+
+/**
+ * Dieser Dialog stellt verschiedene {@link CoordinateReferenceSystem} (CRS)
+ * zur Auswahl. Neben einer Auswahl von vordefinierten CRS, wird WGS-84, sowie
+ * (optional) ein beliebiges Standard-CRS dargestellt. Zudem kann in einem
+ * Text-Feld die WKT-Spezifikation manuell eingegeben oder editiert werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class CRSSelectionDialog extends JDialog {
+  private   Logger           LOGGER = LangUtil.createLogger(this);
+  private   ResourceProvider RES = GeotoolsGUIUtil.RESOURCE;
+
+  /** Beinhaltet die vordefinierten CRS. Die Map beinhaltet pro Authority eine
+   *  Map mit den CRS. */
+  protected static SortedMap<String,SortedMap<String,CoordinateReferenceSystem>> predefinedCRS = null;
+
+  /** Label in dem die Meldung angezeigt wird. */
+  protected JTextArea  messageLabel = null;
+  /** Radio-Button fuer das WGS84-CRS. */
+  protected JRadioButton wgs84Button = null;
+  /** Radio-Button fuer das Standard-CRS. */
+  protected JRadioButton defaultButton = null;
+  /** Radio-Button fuer ein vordefiniertes CRS. */
+  protected JRadioButton predefinedButton = null;
+  /** Auswahl-Feld fuer die Authority. */
+  protected SelectionInputOption.Combo<String> authoritySelect = null;
+  /** Auswahl-Feld fuer das Authority-CRS. */
+  protected SelectionInputOption.Combo<CoordinateReferenceSystem> crsSelect = null;
+  /** Radio-Button fuer ein benutzerdefiniertes CRS. */
+  protected JRadioButton userButton = null;
+  /** Gruppe der Auswahl-Buttons. */
+  protected ButtonGroup buttonGroup = null;
+  /** Bereich, in dem das CRS als WKT angezeigt wird. */
+  protected JTextArea wktTextArea = null;
+  /** ScrollPane, in dem sich die TextArea fuer die WKT-Definition befinden. */
+  protected JScrollPane wktScrollPane = null;
+
+  /** Die Checkbox "Immer diese Auswahl treffen" des Dialogs. */
+  protected JCheckBox rememberCheckbox = null;
+  /** Der OK-Button des Dialogs. */
+  protected JButton okButton = null;
+  /** Der Abbrechen-Button des Dialogs. */
+  protected JButton cancelButton = null;
+
+  /** Das als "Standard" angebotene CRS.  */
+  protected CoordinateReferenceSystem defaultCRS = null;
+  /** Das ausgewaehlte CRS.  */
+  protected CoordinateReferenceSystem selectedCRS = null;
+
+  /** Panel (mit {@link GridBagLayout}), in dem die Meldung und Radio-Buttons
+   *  angezeigt werden. */
+  protected JPanel contentPane = null;
+  /** Dem Dialog uebergeordnete Komponente. */
+  protected Component parent = null;
+
+  /**
+   * Erzeugt einen neuen Fehler-Dialog. Der Dialog wird relativ zum Parent-Fenster
+   * zentriert.
+   * @param parent         uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param message        einleitende Meldung (kann <code>null</code> sein!)
+   * @param cancelAllowed  wenn {@code false}, kann der Dialog nicht abgebrochen werden
+   * @param rememberOption wenn {@code true} wird eine Checkbox "Immer diese Auswahl treffen"
+   *                       angezeigt
+   * @param defaultCRS     das als Standard angebotene CRS (kann {@code null} sein)
+   */
+  public CRSSelectionDialog(Component parent, String message, boolean cancelAllowed, boolean rememberOption, CoordinateReferenceSystem defaultCRS) {
+    super((Frame)null,true);
+    initPredefinedCRS();
+
+    this.defaultCRS     = defaultCRS;
+    this.parent         = parent;
+
+    // Vorlagen-Dialog erzeugen
+    if ( message != null ) {
+      this.messageLabel = new JTextArea();
+      this.messageLabel.setWrapStyleWord(true);
+      this.messageLabel.setFont( new JLabel().getFont() );
+      this.messageLabel.setBackground( new JLabel("").getBackground() );
+      this.messageLabel.setText(message);
+      this.messageLabel.setEditable(false);
+    }
+    this.wktTextArea      = new JTextArea(10,60);
+    this.wktTextArea.setWrapStyleWord(true);
+    this.wktScrollPane    = new JScrollPane(wktTextArea);
+    this.buttonGroup      = new ButtonGroup();
+    this.wgs84Button      = createRadioButton( RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.button.wgs84") );
+    this.userButton       = createRadioButton( RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.button.userDefined") );
+    this.predefinedButton = createRadioButton( RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.button.predefined") );
+    if ( defaultCRS != null ) {
+      this.defaultButton = createRadioButton( RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.button.default", defaultCRS.getName().toString()) );
+      this.defaultButton.setSelected(true);
+    } else
+      this.wgs84Button.setSelected(true);
+    this.crsSelect        = new SelectionInputOption.Combo<CoordinateReferenceSystem>(null,true);
+    this.authoritySelect  = new SelectionInputOption.Combo<String>(null,true);
+    this.authoritySelect.setSelectionObjects(predefinedCRS.keySet().toArray(), null);
+    this.authoritySelect.setSelectedItem(0);
+    SwingUtil.setPreferredWidth( this.authoritySelect, 100 );
+    SwingUtil.setMinimumWidth( this.authoritySelect, 100 );
+    // Wenn Authority geaendet wird, die CRS-Auswahl aktualisieren
+    this.authoritySelect.addInputOptionListener( new InputOptionAdapter() {
+      public void optionChanged(InputOption option, Object oldValue, Object newValue) {
+        Map<String, CoordinateReferenceSystem> crsMap = predefinedCRS.get((String)newValue);
+        crsSelect.setSelectionObjects(
+            crsMap.values().toArray(new CoordinateReferenceSystem[0]),
+            crsMap.keySet().toArray(new String[0])
+        );
+        // Das erste CRS auswaehlen
+        if ( crsSelect.getSelectedItemCount() > 0 )
+          crsSelect.setSelectedIndex(0);
+      }
+    });
+    // Wenn CRS geaendert wird, das WKT-Feld aktualisieren
+    this.crsSelect.addInputOptionListener( new InputOptionAdapter() {
+      public void optionChanged(InputOption option, Object oldValue, Object newValue) {
+        resetWKT( (CoordinateReferenceSystem)newValue );
+      }
+    });
+    // Wenn Authority vorhanden, die erste vorbelegen
+    if ( authoritySelect.getSelectedItemCount() > 0 )
+      authoritySelect.setSelectedIndex(0);
+
+    // Checkbox erstellen
+    if ( rememberOption )
+      this.rememberCheckbox = new JCheckBox( SwingUtil.RESOURCE.getString("RememberChoice") );
+
+    // Dialog-Button erstellen
+    this.okButton     = new JButton( SwingUtil.RESOURCE.getString("Ok") );
+    this.cancelButton = new JButton( SwingUtil.RESOURCE.getString("Cancel") );
+    this.cancelButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        performButton(cancelButton);
+      }
+    });
+    this.okButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        performButton(okButton);
+      }
+    } );
+
+    // GUI aufbauen
+    this.contentPane = new JPanel();
+    this.contentPane.setLayout( new GridBagLayout() );
+    if ( messageLabel != null )
+      this.contentPane.add(messageLabel, new GridBagConstraints(0,0,3,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(wgs84Button, new GridBagConstraints(0,1,3,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    if ( defaultButton != null )
+      this.contentPane.add(defaultButton, new GridBagConstraints(0,2,3,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(predefinedButton, new GridBagConstraints(0,3,1,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(authoritySelect, new GridBagConstraints(1,3,1,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(crsSelect, new GridBagConstraints(2,3,1,1,1.0,0.0,GridBagConstraints.WEST,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(userButton, new GridBagConstraints(0,4,3,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    this.contentPane.add(wktScrollPane, new GridBagConstraints(0,5,3,1,1.0,1.0,GridBagConstraints.WEST,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    if ( rememberCheckbox != null )
+      this.contentPane.add(rememberCheckbox, new GridBagConstraints(0,6,3,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),0,0));
+
+    // Dialog erzeugen
+    JOptionPane  pane = new JOptionPane(
+      contentPane,
+      JOptionPane.QUESTION_MESSAGE,
+      JOptionPane.DEFAULT_OPTION,
+      null,
+      cancelAllowed ? new Object[] {okButton, cancelButton} : new Object[] { okButton }
+    );
+    JDialog dialog = pane.createDialog(parent,RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.title"));
+
+    // Dialog nach Vorlage initialisieren
+    this.setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
+    this.setTitle( dialog.getTitle() );
+    this.getContentPane().setLayout(new BorderLayout());
+    this.getContentPane().add(dialog.getContentPane(), BorderLayout.CENTER);
+
+    // Sofern vorhanden, den Default-Button als Standard auswaehlen
+    performButton( defaultButton != null ? defaultButton : wgs84Button );
+
+    // Dialog in der Mitte des Parent-Fenster anordnen
+    pack();
+    SwingUtil.setRelativeFramePosition(this,SwingUtil.getParentWindow(parent),0.5,0.5);
+  }
+
+  /**
+   * Initialisiert die zur Verfuegung stehenden CRS.
+   */
+  protected void initPredefinedCRS() {
+    if ( predefinedCRS != null )
+      return;
+
+    // Initalisierung der CRS-Auswahl in einem SwingWorker
+    SwingWorker.Work work = new SwingWorker.Work() {
+      public TreeMap<String, SortedMap<String,CoordinateReferenceSystem>> execute() {
+        TreeMap<String, SortedMap<String,CoordinateReferenceSystem>> authorityCRS = new TreeMap<String,SortedMap<String,CoordinateReferenceSystem>>();
+        for ( String authority : (Set<String>)CRS.getSupportedAuthorities(true) ) {
+          SortedMap<String, CoordinateReferenceSystem> crs = GTUtil.getAvailableCRSByName(authority, false, true);
+          if ( !crs.isEmpty() )
+            authorityCRS.put(authority, crs);
+        }
+        return authorityCRS;
+      }
+      public void performError(Throwable err) {
+        if ( err instanceof ThreadDeath )
+          return;
+        super.performError(err);
+      }
+    };
+    SwingWorker worker = new SwingWorker(
+        work,
+        new StatusDialog(
+            parent,
+            RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.init.crs.title"),
+            RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.init.crs.mess")
+        )
+    );
+    try {
+      worker.start();
+    } catch (Exception err) {
+      // ignore this
+    }
+    predefinedCRS = (TreeMap<String, SortedMap<String,CoordinateReferenceSystem>>)worker.getWorkResult();
+    if ( predefinedCRS == null )
+      predefinedCRS = new TreeMap<String,SortedMap<String,CoordinateReferenceSystem>>();
+  }
+
+  /**
+   * Fuehrt die Aktion eines Buttons aus (Radio- oder Dialog-Button).
+   * @param button der aktivierte Button
+   */
+  protected void performButton(AbstractButton button) {
+    if ( button instanceof JRadioButton ) {
+      // Auswahl der Authority und des CRS nur aktiv, wenn Radio-Button
+      // fuer Authority ausgewaehlt ist
+      this.authoritySelect.setEnabled( button == predefinedButton );
+      this.crsSelect.setEnabled( button == predefinedButton );
+      // Benutzerdefinerte Eingabe nur aktiv, wenn der entsprechende
+      // Radio-Button ausgewaehlt ist
+      this.wktTextArea.setEnabled( button == userButton );
+    }
+
+    // Wenn Auswahl WGS84, Default oder Authority, dann das jeweilige WKT
+    // einblenden
+    if ( button == wgs84Button )
+      resetWKT( GTUtil.WGS84 );
+    if ( button == defaultButton )
+      resetWKT( defaultCRS );
+    if ( button == predefinedButton )
+      resetWKT( (CoordinateReferenceSystem)crsSelect.getValue() );
+    // Wenn Auswahl Benutzerdefiniert, dann direkt auf die WKT springen
+    if ( button == userButton )
+      wktTextArea.grabFocus();
+
+    // Abbrechen --> Dialog ohne CRS-Auswahl beenden
+    if ( button == cancelButton ) {
+      selectedCRS = null;
+      setVisible(false);
+    }
+    // OK --> Rueckgabe-CRS belegen
+    if ( button == okButton ) {
+      try {
+        selectedCRS = null;
+        if ( wgs84Button.isSelected() )
+          selectedCRS = GTUtil.WGS84;
+        if ( defaultButton != null && defaultButton.isSelected() )
+          selectedCRS = CRSSelectionDialog.this.defaultCRS;
+        if ( predefinedButton.isSelected() )
+          selectedCRS = (CoordinateReferenceSystem) crsSelect.getValue();
+        if ( userButton.isSelected() && !"".equals(wktTextArea.getText()) )
+          selectedCRS = CRS.parseWKT(wktTextArea.getText());
+        if ( selectedCRS == null )
+          JOptionPane.showMessageDialog(
+              this,
+              RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.mandatory"),
+              RES.getString("schmitzm.geotools.gui.CRSSelectionDialog.title"),
+              JOptionPane.ERROR_MESSAGE
+          );
+        else
+          setVisible(false);
+      } catch (Exception err) {
+        selectedCRS = null;
+        ExceptionDialog.show(this, err);
+        // Nach Fehler zu der entsprechenden Komponente springen
+        if ( userButton.isSelected() )
+          wktTextArea.grabFocus();
+        else if ( predefinedButton.isSelected() )
+          predefinedButton.grabFocus();
+        else if ( buttonGroup.getSelectedButton() != null )
+          buttonGroup.getSelectedButton().grabFocus();
+      }
+    }
+  }
+
+  /**
+   * Erzeugt einen neuen Radio-Button und fuegt diesen in die {@link #buttonGroup} ein.
+   * @param label Label fuer den Radio-Button
+   */
+  protected JRadioButton createRadioButton(String label) {
+    Action action = new AbstractAction() {
+      public void actionPerformed(ActionEvent e) {
+        performButton( (AbstractButton)e.getSource() );
+      }
+    };
+    action.putValue(Action.SHORT_DESCRIPTION, label);
+    action.putValue(Action.LONG_DESCRIPTION, label);
+    action.putValue(Action.NAME, label);
+    JRadioButton button = new JRadioButton(action);
+    this.buttonGroup.add(button);
+    return button;
+  }
+
+  /**
+   * Belegt das WKT-Feld neu. Jedoch nur, wenn sich der Text aendert,
+   * damit nach Moeglichkeit die Caret-Position erhalten bleibt.
+   * @param crs darzustellendes CRS
+   */
+  protected void resetWKT(CoordinateReferenceSystem crs) {
+    String wkt = "";
+    try {
+      wkt = ( crs != null && crs.toWKT() != null ) ? crs.toWKT().toString() : "";
+    } catch (Exception err) {
+      LOGGER.warn(err.getMessage());
+    }
+
+    // Nur neu setzen, wenn auch Aenderung stattfindet (damit ggf.
+    // die Caret-Position erhalten bleibt)
+    if ( wktTextArea.getText() == null || !wktTextArea.getText().equals(wkt) ) {
+      wktTextArea.setText( wkt );
+      wktTextArea.setCaretPosition(0);
+    }
+  }
+
+  /**
+   * Liefert die angezeigte Meldung.
+   */
+  public String getMessage() {
+    return  messageLabel.getText();
+  }
+
+  /**
+   * Liefert den ausgewaehlen Radio-Button.
+   */
+  public AbstractButton getSelectedButton() {
+    return buttonGroup.getSelectedButton();
+  }
+
+  /**
+   * Prueft, ob der Dialog abgebrochen wurde.
+   */
+  public boolean isCanceled() {
+    return !isVisible() && getSelectedButton() == cancelButton;
+  }
+
+  /**
+   * Prueft, ob der Dialog via OK beendet wurde.
+   */
+  public boolean isConfirmed() {
+    return !isVisible() && getSelectedButton() == okButton;
+  }
+
+  /**
+   * Prueft, ob die "Immer diese Auswahl treffen" Option im Dialog
+   * angewaehlt ist.
+   * @return {@code false} solange der OK-Button noch nicht betaetigt wurde
+   */
+  public boolean isRememberOptionSet() {
+    return isConfirmed() && rememberCheckbox != null && rememberCheckbox.isSelected();
+  }
+
+  /**
+   * Liefert das ausgewaehlte CRS.
+   * @return {@code null} solange der OK-Button noch nicht betaetigt wurde
+   */
+  public CoordinateReferenceSystem getCRS() {
+    return this.selectedCRS;
+  }
+
+  /**
+   * Zeigt einen CRS-Auswahl-Dialog an.
+   * @param parent         uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param message        einleitende Meldung (kann <code>null</code> sein!)
+   * @param cancelAllowed  wenn {@code false}, kann der Dialog nicht abgebrochen werden
+   * @param rememberOption wenn {@code true} wird eine Checkbox "Immer diese Auswahl treffen"
+   *                       angezeigt
+   * @param defaultCRS     das als Standard angebotene CRS (kann {@code null} sein)
+   */
+  public static CoordinateReferenceSystem show(Component parent, String message, boolean cancelAllowed, boolean alwaysOption, CoordinateReferenceSystem defaultCRS) {
+    CRSSelectionDialog dialog = new CRSSelectionDialog(parent,message,cancelAllowed,alwaysOption, defaultCRS);
+    dialog.setVisible(true);
+    return dialog.getCRS();
+  }
+
+  /**
+   * Zeigt einen CRS-Auswahl-Dialog an, der abgebrochen werden kann. Die Eigenschaft
+   * "immer diese Auswahl treffen" kann nicht eingestellt werden.
+   * @param parent         uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param message        einleitende Meldung (kann <code>null</code> sein!)
+   * @param defaultCRS     das als Standard angebotene CRS (kann {@code null} sein)
+   * @see CRS#decode(String, boolean)
+   */
+  public static CoordinateReferenceSystem show(Component parent, String message, CoordinateReferenceSystem defaultCRS) {
+    return show(parent,message,true,false,defaultCRS);
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/ColorMapTable.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/ColorMapTable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/ColorMapTable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,289 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.Color;
+import java.util.Map;
+
+import javax.swing.JColorChooser;
+
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.ColorMapEntry;
+import org.geotools.styling.ColorMapImpl;
+
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.swing.CaptionsChangeable;
+import schmitzm.swing.table.AbstractMutableTableModel;
+import schmitzm.swing.table.ColorRenderer;
+import schmitzm.swing.table.MutableTable;
+
+/**
+ * Diese Klasse stellt eine Tabelle dar, in der eine {@link ColorMap} dargestellt
+ * und veraendert werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class ColorMapTable extends MutableTable implements CaptionsChangeable {
+    /** Key, um den 1. Tabellenkopf-Eintrag "Quantity" in der {@link CaptionsChangeable}-Map anzusprechen.
+     *  @see #resetCaptions(Map)*/
+  public static final String TABLEHEADER_QUANTITY = ColorMapTable.class.getName()+".Header.QUANTITY";
+  /** Key, um den 2. Tabellenkopf-Eintrag "Color" in der {@link CaptionsChangeable}-Map anzusprechen.
+   *  @see #resetCaptions(Map)*/
+  public static final String TABLEHEADER_COLOR = ColorMapTable.class.getName()+".Header.COLOR";
+  /** Key, um den 3. Tabellenkopf-Eintrag "Label" in der {@link CaptionsChangeable}-Map anzusprechen.
+   *  @see #resetCaptions(Map)*/
+  public static final String TABLEHEADER_LABEL = ColorMapTable.class.getName()+".Header.LABEL";
+
+  /**
+   * Erzeugt eine neue Tabelle
+   * @param colorMap darzustellende Farb-Palette
+   */
+  public ColorMapTable(ColorMap colorMap) {
+    super( new ColorMapTableModel(colorMap), MutableTable.ITEM_ADD | MutableTable.ITEM_REMOVE );
+    ((ColorMapTableModel)getModel()).table = this;
+    this.setDefaultRenderer(Color.class, new ColorRenderer(true));
+    this.setCellSelectionEnabled(true);
+  }
+
+  /**
+   * Erzeugt eine neue Tabelle. Die dargestellte Farb-Palette muss anschliessend
+   * noch mit {@link #setColorMap(ColorMap)} gesetzt werden.
+   */
+  public ColorMapTable() {
+    this(null);
+  }
+
+  /**
+   * Ruft {@link #performChange()} auf, auch wenn die CHANGE-Option nicht
+   * aktiviert ist.
+   */
+  public void performDoubleClick() {
+    performChange();
+  }
+
+  /**
+   * Liefert die in der Tabelle dargestelle Farb-Palette.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   */
+  public ColorMap getColorMap() {
+	  //return StylingUtil.clearColorMapLabels(  ((ColorMapTableModel)getModel()).colMap  );
+    return ((ColorMapTableModel)getModel()).colMap;
+  }
+
+  /**
+   * Setzt die in der Tabelle dargestelle Farb-Palette.
+   * @param colorMap darzustellende Farb-Palette
+   */
+  public void setColorMap(ColorMap colorMap) {
+    ((ColorMapTableModel)getModel()).colMap = colorMap;
+    ((ColorMapTableModel)getModel()).fireTableDataChanged();
+  }
+
+  /**
+   * Setzt die Bezeichnungen des Tabellenheaders neu.
+   * @param captionMap Map mit neuen Bezeichnungen fuer Headers
+   */
+  public void resetCaptions(Map<String,Object> captionMap) {
+    String[] header =  ((ColorMapTableModel)getModel()).createColumnNames();
+
+    Object caption = captionMap.get( TABLEHEADER_QUANTITY );
+    if ( caption != null )
+      header[0] = caption.toString();
+    caption = captionMap.get( TABLEHEADER_COLOR );
+    if ( caption != null )
+      header[1] = caption.toString();
+    caption = captionMap.get( TABLEHEADER_LABEL );
+    if ( caption != null )
+      header[2] = caption.toString();
+
+    ((ColorMapTableModel)getModel()).fireTableStructureChanged();
+  }
+
+  /**
+   * Diese Klasse stellt ein Daten-Modell fuer die Farbpaletten-Tabelle
+   * dar.
+   */
+  private static class ColorMapTableModel extends AbstractMutableTableModel {
+    protected MutableTable table   = null;
+    protected ColorMap     colMap   = null;
+    protected Class[]      colClass = new Class[]  {String.class, Color.class, String.class};
+
+    /**
+     * Erzeugt ein neues TableModel.
+     * @param colorMap Farb-Palette
+     */
+    public ColorMapTableModel(ColorMap colorMap) {
+      super();
+      this.colMap = colorMap;
+    }
+
+    /**
+     * Liefert die Spaltennamen der Tabelle.
+     */
+    @Override
+    public String[] createColumnNames() {
+      return new String[] {
+          GeotoolsGUIUtil.RESOURCE.getString(TABLEHEADER_QUANTITY),
+          GeotoolsGUIUtil.RESOURCE.getString(TABLEHEADER_COLOR),
+          GeotoolsGUIUtil.RESOURCE.getString(TABLEHEADER_LABEL)
+      };
+    }
+
+    /**
+     * Fuer Spalte 1 wird ein Farb-Dialog aufgerufen. Spalte 0 (Wert) und 2
+     * werden direkt in der Tabelle geaendert.
+     * @param row Zeilennummer (beginnend bei 0)
+     * @param col Spaltennummer (beginnend bei 0)
+     */
+    public void performChangeData(int row, int col) {
+      if ( col != 1 )
+        return;
+      ColorMapEntry entry = colMap.getColorMapEntry(row);
+      Color color = JColorChooser.showDialog(table,"Choose color",StylingUtil.getColorFromColorMapEntry(entry));
+      if ( color != null )
+        entry.setColor( StylingUtil.STYLE_BUILDER.colorExpression(color) );
+      //changeColorMapEntry( colMap.getColorMapEntry(row) );
+    }
+
+    /**
+     * Loescht einen Eintrag aus der Farb-Palette.
+     * @param row Zeilennummer (beginnend bei 0)
+     */
+    public void performRemoveRow(int row) {
+      ColorMapEntry[] entry = colMap.getColorMapEntries();
+      colMap = new ColorMapImpl();
+      for (int i=0; i<entry.length; i++)
+        if ( i != row )
+          colMap.addColorMapEntry(entry[i]);
+    }
+
+    /**
+     * Ruft einen Dialog auf, in dem ein neuer Wert und eine neue Farbe
+     * angegeben werden kann.
+     */
+    public void performAddRow() {
+      colMap.addColorMapEntry( StylingUtil.createColorMapEntry("",0.0,Color.WHITE,1.0) );
+    }
+
+/*
+    private void changeColorMapEntry(ColorMapEntry oldEntry) {
+      // Default-Wert fuer den Dialog ist zunaechst der alte Eintrag
+      ColorMapEntry defaultEntry = oldEntry;
+      while (true) {
+        // Dialog anzeigen um Eintrag zu aendern/erzeugen
+        ColorMapEntry newEntry = null;
+        try {
+          newEntry = showColorMapEntryDialog(defaultEntry);
+        } catch (Exception err) {
+          ExceptionDialog.show(table,err,"Error","Color map entry incorrect!");
+          // nochmal versuchen (WHILE-Schleife fortfuehren)
+          continue;
+        }
+        // wenn Dialog abgebrochen wurde oder der Eintrag nicht
+        // geaendert wurde, wird nichts gemacht
+        if (newEntry == null || newEntry.equals(oldEntry))
+          break;
+
+        // Eintrag einfuegen
+        if ( oldEntry == null )
+          // neuen Eintrag am Ende einfuegen
+          colMap.addColorMapEntry( newEntry );
+        else {
+          // alte gegen neue Category ersetzen
+          oldEntry.setColor( newEntry.getColor() );
+          oldEntry.setQuantity( newEntry.getQuantity() );
+        }
+        this.fireTableDataChanged();
+        break;
+      }
+    }
+
+    private ColorMapEntry showColorMapEntryDialog(ColorMapEntry entry) throws Exception {
+      Double defValue = (entry != null) ? StylingUtil.getQuantityFromColorMapEntry(entry) : 0.0;
+      Color  defColor = (entry != null) ? StylingUtil.getColorFromColorMapEntry(entry) : null;
+
+      Object[] obj = MultipleOptionPane.showMultipleInputDialog(
+          table,
+          "New quantity and color",
+          new InputOption[] {
+            new ManualInputOption.Double("Quantity",true,defValue),
+            new ColorInputOption("Color",true,defColor)
+          }
+      );
+      // Dialog abgebrochen
+      if ( obj == null )
+        return null;
+      // Neuen Eintrag erzeugen
+      return StylingUtil.createColorMapEntry("",(Double)obj[0],(Color)obj[1],1.0);
+    }
+*/
+    /**
+     * Liefert die Anzahl an Tabellenzeilen.
+     */
+    public int getRowCount() {
+      if ( colMap == null )
+        return 0;
+      return colMap.getColorMapEntries().length;
+    }
+
+    /**
+     * Liefert den Typ einer Spalte.
+     * @param col Spaltennummer (beginnend mit 0)
+     */
+    public Class getColumnClass(int col) {
+      return colClass[col];
+    }
+
+    /**
+     * Spezifiert, ob eine Zelle editierbar ist.
+     * @param row Zeilennummer (beginnend bei 0)
+     * @param col Spaltennummer (beginnend bei 0)
+     * @return <code>true</code> fuer Spalte 0 und 2, sonst <code>false</code>
+     */
+    public boolean isCellEditable(int row, int col) {
+      return col == 0 || col == 2;
+    }
+
+    /**
+     * Liefert einen Wert der Tabelle
+     * @param row Zeilennummer (beginnend bei 0)
+     * @param col Spaltennummer (beginnend bei 0)
+     */
+    public Object getValueAt(int row, int col) {
+      switch( col ) {
+        case 0: return StylingUtil.getQuantityFromColorMapEntry( colMap.getColorMapEntry(row) );
+        case 1: return StylingUtil.getColorFromColorMapEntry( colMap.getColorMapEntry(row) );
+        case 2: return colMap.getColorMapEntry(row).getLabel();
+      }
+      return null;
+    }
+
+    /**
+     * Setzt einen Wert der Tabelle. Diese Methode macht nichts fuer Werte in
+     * Spalte 1.
+     * @param obj neuer Wert fuer die Zelle
+     * @param row Zeilennummer (beginnend bei 0)
+     * @param col Spaltennummer (beginnend bei 0)
+     */
+    public void setValueAt(Object obj, int row, int col) {
+      try {
+        switch( col ) {
+          case 0: colMap.getColorMapEntry(row).setQuantity( StylingUtil.STYLE_BUILDER.literalExpression(Double.parseDouble((String)obj)) );
+                  break;
+          case 2: colMap.getColorMapEntry(row).setLabel( (String)obj );
+                  break;
+        }
+      } catch (Exception err) {
+      }
+    }
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureCollectionFilterPanel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureCollectionFilterPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureCollectionFilterPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,292 @@
+package schmitzm.geotools.gui;
+
+import java.awt.Insets;
+import java.awt.GridBagConstraints;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.ListSelectionModel;
+import javax.swing.JTextField;
+import java.util.Map;
+
+import schmitzm.swing.SwingUtil;
+import schmitzm.geotools.feature.AttributeTypeFilter;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.styling.Style;
+
+// fuer Doku
+import org.opengis.filter.Filter;
+import schmitzm.geotools.feature.FeatureOperationTree;
+import schmitzm.geotools.feature.FeatureOperationTreeParser;
+import schmitzm.geotools.feature.FeatureOperationTreeFilter;
+
+/**
+ * Diese Klasse stellt ein Panel zur Vefuegung, mit der ein
+ * {@link FeatureOperationTreeFilter} in Form einer arithmetischen
+ * (und boolschen) Formel erstellt werden kann. Neben den Komponenten zur
+ * Definition/Eingabe des Filters enthaelt das Panel einen Vorschau-Bereich,
+ * in dem eine {@link FeatureCollection} angezeigt wird, auf der der Filter
+ * angewandt wird.
+ * @see FeatureOperationTree
+ * @see FeatureOperationTreeParser
+ * @see FeatureOperationTreeFilter
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureCollectionFilterPanel extends FeatureFilterPanel {
+  /** Konstante fuer die Layout-Constraints des Vorschau-Bereich.
+   *  @see #layoutConstraints
+   *  @see #previewPanel */
+  public static final String PREVIEW_PANEL = FeatureCollectionFilterPanel.class.getName()+"filterPreviewPanel";
+  /** Konstante fuer die Layout-Constraints des Testen-Button.
+   *  @see #layoutConstraints
+   *  @see #resetCaptions(Map)
+   *  @see #testButton */
+  public static final String TEST_BUTTON = FeatureCollectionFilterPanel.class.getName()+".TestButton";
+  /** Konstante fuer die Layout-Constraints des Testen-Button.
+   *  @see #layoutConstraints
+   *  @see #testButton */
+  public static final String TESTRESULT_LABEL = FeatureCollectionFilterPanel.class.getName()+".TestResultLabel";
+
+  /** Panel fuer Filter-Vorschau. */
+  protected FeatureCollectionPane previewPanel = null;
+  /** Button zum Testen der Formel */
+  protected JButton testButton = null;
+  /** Label mit Ergebnis des Formel-Tests */
+  protected JLabel  testResult = null;
+
+  /** Speichert, ob im Vorschau-Panel eine grafische Anzeige erscheinen soll */
+  private boolean geomPrev = false;
+  /** Speichert, die FeatureCollection */
+  private FeatureCollection fc = null;
+
+  /**
+   * Erzeugt ein neues Panel.
+   * @param fc definiert die zur Verfuegung gestellten Feature-Attribute
+   * @param geomPrev bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   * @see FeatureCollection#getSchema()
+   */
+  public FeatureCollectionFilterPanel(FeatureCollection fc, boolean geomPrev) {
+    this( fc, geomPrev, true );
+  }
+
+  /**
+   * Erzeugt ein neues Panel
+   * @param fc definiert die zur Verfuegung gestellten Feature-Attribute
+   * @param initGUI Flag, ob {@link #initGUI()} am Ende des Konstruktor
+   *        aufgerufen werden soll (wenn {@code false} muss die explizit
+   *        durch die Unterklasse erfolgen!)
+   */
+  protected FeatureCollectionFilterPanel(FeatureCollection fc, boolean geomPrev, boolean initGUI) {
+    super(fc.getSchema(), false);
+    this.geomPrev = geomPrev;
+
+    // Layout-Anordnung fuer GUI
+    layoutConstraints.get( ATTRIBUTE_TABLE ).gridwidth     = 3;
+    layoutConstraints.get( ATTRIBUTE_TABLE ).weighty       = 0.3;
+    layoutConstraints.get( RULE_TEXTFIELD ).gridwidth      = 2;
+    layoutConstraints.get( OPERATOR_COMBOBOX ).gridx       = 2;
+    layoutConstraints.put( TEST_BUTTON,      new GridBagConstraints(0,4,1,1,  0,  0.0,GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5,10,10,10),0,0) );
+    layoutConstraints.put( TESTRESULT_LABEL, new GridBagConstraints(1,4,2,1,1.0,  0.0,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5,10,10,10),0,0) );
+    layoutConstraints.put( PREVIEW_PANEL,    new GridBagConstraints(0,5,3,1,  0,  0.7,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5,10,10,10),0,0) );
+
+    if ( initGUI )
+      initGUI();
+
+    setFeatureCollection(fc);
+  }
+
+  /**
+   * Initalisiert die GUI des Fensters. Der "Start-Button" der durch die
+   * Oberklasse definiert wird, wird dabei aus dem Panel entfernt!
+   */
+  protected void initGUI() {
+    super.initGUI();
+
+    // Button zum Testen des Filters
+    testButton = new JButton( GeotoolsGUIUtil.RESOURCE.getString(TEST_BUTTON) );
+    testButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        performRuleTest();
+      }
+    });
+    // Label fuer Fehler-Meldungen
+    testResult = new JLabel("");
+
+    previewPanel = new FeatureCollectionPane( null, geomPrev ) {
+      // In der Tabelle sollen einzelne Werte selektiert werden koennen
+      // und diese bei einem Doppelklick in die Formel uebernommen werden
+      protected void initGUI(boolean geomPreview) {
+        super.initGUI(geomPreview);
+        // nur einzelne Zellen duerfen selektiert werden
+        featuresTable.setColumnSelectionAllowed( true );
+        featuresTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+        // beim Klick auf eine Tabellenzelle, soll der Wert in die Formel
+        // uebernommen werden
+        featuresTable.addMouseListener(new MouseAdapter() {
+          public void mouseClicked(MouseEvent e) {
+            if (e.getButton() == MouseEvent.BUTTON1 &&
+                e.getClickCount() == 2)
+              insertValueInRule( featuresTable.getValueAt(
+                featuresTable.getSelectedRow(),
+                featuresTable.getSelectedColumn())
+              );
+          }
+        });
+      }
+
+      // Im Vorschau-Panel sollen nicht die in der Tabelle selektieren
+      // Features angezeigt werden, sondern immer ALLE Features der Tabelle (also
+      // das Resultat des Filters)
+      public void performListSelection() {
+      }
+    };
+    // Geometry-Attribute sollen nicht angezeigt werden
+    previewPanel.setAttributeFilter( AttributeTypeFilter.NO_GEOMETRY );
+
+    SwingUtil.setPreferredHeight( previewPanel, 50 );
+
+    // zusaetzliche Komponenten einfuegen
+    add( testButton,   layoutConstraints.get(TEST_BUTTON) );
+    add( testResult,   layoutConstraints.get(TESTRESULT_LABEL) );
+    add( previewPanel, layoutConstraints.get(PREVIEW_PANEL) );
+  }
+
+  /**
+   * Setzt den Filter, der die dargestellten Attribute bestimmt.
+   * @param attrFilter Filter
+   */
+  public void setAttributeFilter(AttributeTypeFilter attrFilter) {
+    super.setAttributeFilter(attrFilter);
+    // Filter auch auf die in der Vorschau dargestellten
+    // Attribut-Spalten anwenden
+    previewPanel.setAttributeFilter(attrFilter);
+  }
+
+  /**
+   * Setzt die Labels des Panels neu.
+   * @param captionMap Map
+   * @see FeatureFilterPanel#resetCaptions(Map)
+   */
+  public void resetCaptions(Map<String,Object> captionMap) {
+    super.resetCaptions(captionMap);
+    SwingUtil.resetCaption( testButton, captionMap.get(TEST_BUTTON) );
+  }
+
+  /**
+   * Fuegt an der Curserposition einen Wert in die Formel ein.
+   * @param value Object
+   */
+  private void insertValueInRule(Object value) {
+    if ( value == null )
+      value = "";
+    
+    String ruleValue = value.toString();
+    // Nur numerische Werte direkt in Formel einfuegen.
+    // Andere Werte als String in Anfuehrungsstrichen gekapselt.
+    if ( !(value instanceof Number) )
+      ruleValue = "\""+ruleValue+"\"";
+    // Wert in Formel einfuegen
+    performOperatorInsert(ruleValue, (JTextField)this.rule.getInputComponent());
+    // Fokus zurueck auf Formel-Feld
+    rule.grabFocus();
+  }
+
+  /**
+   * Setzt die aktuell im Dialog eingegebene Formel und aktualisiert die
+   * Vorschau.
+   * @param rule Formel als String
+   */
+  public void setRule(String rule) {
+    super.setRule(rule);
+    performRuleTest();
+  }
+
+
+  /**
+   * Wird ausgefuehrt, wenn der Testen-Button gedrueckt wird.
+   * Erzeugt einen Filter aus der angegebenen Formel, wertet diese
+   * auf der {@link FeatureCollection} aus und zeigt das Resultat im
+   * {@linkplain #previewPanel Vorschau-Bereich} an.
+   * <br>
+   * Tritt ein Fehler auf, wird die entsprechende Meldung im Label
+   * {@link #testResult} (neben dem Button) angezeigt.
+   *
+   */
+  protected void performRuleTest() {
+    try {
+      FeatureCollection subCollection = filterFeatureCollection();
+      previewPanel.setFeatureCollection( subCollection );
+      testResult.setText("");
+    } catch (Exception err) {
+      previewPanel.setFeatureCollection( null );
+      testResult.setText( err.getMessage() );
+    }
+    rule.grabFocus();
+  }
+
+  /**
+   * Setzt die {@link FeatureCollection}, die (gefiltert) in der Vorschau angezeigt
+   * wird.
+   * @param fc eine {@link FeatureCollection}, die zum Feature-Type (Schema) des
+   *        Formel-Panels passt
+   * @exception IllegalArgumentException falls die FeatureCollection nicht zum
+   *            Schema des Panels passt
+   * @see FeatureCollection#getSchema()
+   */
+  public void setFeatureCollection(FeatureCollection fc) {
+    if ( getFeatureType() == null )
+      setFeatureType( fc.getSchema() );
+    if ( !fc.getSchema().equals( getFeatureType() ) )
+      throw new IllegalArgumentException("FeatureCollection does not fit to schema of the panel!");
+    this.fc = fc;
+    if ( previewPanel != null )
+      previewPanel.setFeatureCollection( fc );
+  }
+
+  /**
+   * Liefert die {@link FeatureCollection}, auf die (gefiltert) im Vorschau-Panel
+   * angezeigt wird.
+   */
+  public FeatureCollection getFeatureCollection() {
+    return fc;
+  }
+
+  /**
+   * Setzt den Style, in dem die Features in der Vorschau dargestellt werden.
+   * @param style Style fuer die Features
+   */
+  public void setFeatureStyle( Style style ) {
+    previewPanel.setFeatureStyle( style );
+  }
+
+  /**
+   * Liefert den Style, in dem die Features in der Vorschau dargestellt werden.
+   */
+  public Style getFeatureStyle() {
+    return previewPanel.getFeatureStyle();
+  }
+
+  /**
+   * Wendet die im Panel eingetragene Filter-Formel auf die {@link FeatureCollection}
+   * des Vorschau-Fensters an und liefert die entsprechende Sub-Collection.
+   * @see FeatureCollection#subCollection(Filter)
+   */
+  public FeatureCollection filterFeatureCollection( ) {
+    return getFeatureCollection().subCollection( createFilter() );
+  }
+
+  /**
+   * Wendet die im Panel eingetragene Filter-Formel auf eine {@link FeatureCollection}
+   * and und liefert die entsprechende Sub-Collection.
+   * @see FeatureCollection#subCollection(Filter)
+   */
+  public FeatureCollection filterFeatureCollection( FeatureCollection fc ) {
+    return fc.subCollection( createFilter() );
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureCollectionFrame.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureCollectionFrame.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureCollectionFrame.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,89 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import javax.swing.JFrame;
+
+import org.geotools.feature.FeatureCollection;
+
+import schmitzm.geotools.gui.FeatureCollectionPane;
+
+/**
+ * Dieses Fenster stellt eine {@link FeatureCollection} als Tabelle dar.
+ * Optional wird links neben der Tabelle eine grafische Vorschau der in
+ * der Tabelle selektierten Features angezeigt.
+ * @see FeatureCollectionPane
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureCollectionFrame extends JFrame {
+  /** Enthaelt die Tabelle (und den Preview-Bereich). */
+  protected FeatureCollectionPane featuresPane = null;
+
+  /**
+   * Erzeugt ein neues Fenster inklusive Preview-Bereich fuer die in der
+   * Tabelle selektieren Features.
+   */
+  public FeatureCollectionFrame() {
+    this(null, true);
+  }
+
+  /**
+   * Erzeugt ein neues Fenster.
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  public FeatureCollectionFrame(boolean geomPreview) {
+    this(null, geomPreview);
+  }
+
+  /**
+   * Erzeugt ein neues Fenster inklusive Preview-Bereich fuer die in der
+   * Tabelle selektieren Features.
+   * @param fc angezeigte Features
+   */
+  public FeatureCollectionFrame(FeatureCollection fc) {
+    this(fc, true);
+  }
+
+  /**
+   * Erzeugt ein neues Fenster.
+   * @param fc angezeigte Features
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  public FeatureCollectionFrame(FeatureCollection fc, boolean geomPreview) {
+    super();
+    this.setLayout( new BorderLayout() );
+    this.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE );
+    this.featuresPane = new FeatureCollectionPane(fc,geomPreview);
+    this.getContentPane().add( featuresPane );
+    this.pack();
+  }
+
+  /**
+   * Setzt die anzuzeigende {@link FeatureCollection}.
+   * @param fc Features
+   */
+  public void setFeatureCollection(FeatureCollection fc) {
+    featuresPane.setFeatureCollection(fc);
+  }
+
+  /**
+   * Liefert die angezeigte {@link FeatureCollection}.
+   */
+  public FeatureCollectionPane getFeatureCollectionPane() {
+    return this.featuresPane;
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureCollectionPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureCollectionPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureCollectionPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,461 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Insets;
+import java.awt.Dimension;
+import java.awt.event.MouseEvent;
+import javax.swing.ListSelectionModel;
+import javax.swing.JTable;
+import javax.swing.JSplitPane;
+import javax.swing.JScrollPane;
+import javax.swing.BorderFactory;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.geotools.map.MapLayer;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureCollections;
+
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.JPanel;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.feature.FeatureUtil;
+import org.geotools.styling.Style;
+
+import adagios.swing.MultiSplitPane;
+
+// fuer Doku
+import javax.swing.table.TableModel;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.gui.swing.table.FeatureTableModel;
+import javax.swing.event.TableModelListener;
+import javax.swing.event.TableModelEvent;
+import org.geotools.feature.AttributeType;
+import java.util.Vector;
+import schmitzm.geotools.feature.AttributeTypeFilter;
+
+/**
+ * Diese Komponente stellt eine Tabelle dar, in der die Attribute einer
+ * {@link FeatureCollection} dargestellt werden. Optional werden links neben der
+ * Tabelle die in der Tabelle ausgewaehlten Features grafisch dargestellt.<br>
+ * <br>
+ * <b>Bemerkung:</b><br>
+ * Als {@code TableModel} fuer die Feature-Tabelle verwendet diese Klasse
+ * ein eigenes internes {@link TableModel}, welches effizienter arbeitet,
+ * als {@link org.geotools.gui.swing.table.FeatureTableModel org.geotools.gui.swing.table.FeatureTableModel}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureCollectionPane extends JPanel {
+  /** Tabelle in der die Features angezeigt werden. */
+  protected JTable featuresTable = null;
+  /** Tabellen-Modell der Feature-Tabelle. */
+  protected FeatureCollectionTableModel featuresTableModel = null;
+  /** Preview-Bereich fuer die in der Tabelle selektierten Features. */
+  protected JMapPane   mapPane = null;
+  /** Style, in dem die Features in der Karte dargestellt werden */
+  protected Style featureStyle = null;
+
+  /**
+   * Erzeugt einen neue Komponente mit Preview-Bereich.
+   */
+  public FeatureCollectionPane() {
+    this(false);
+  }
+
+  /**
+   * Erzeugt einen neue Komponente mit Preview-Bereich.
+   * @param fc angezeigte Features
+   */
+  public FeatureCollectionPane(FeatureCollection fc) {
+    this(fc,true);
+  }
+
+  /**
+   * Erzeugt einen neue Komponente.
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  public FeatureCollectionPane(boolean geomPreview) {
+    this(null,geomPreview);
+  }
+
+  /**
+   * Erzeugt einen neue Komponente.
+   * @param fc          angezeigte Features
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  public FeatureCollectionPane(FeatureCollection fc, boolean geomPreview) {
+    this(fc,null,geomPreview);
+  }
+
+  /**
+   * Erzeugt einen neue Komponente.
+   * @param fc          angezeigte Features
+   * @param style       Style, in dem die Features in der Karte dargestellt werden
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  public FeatureCollectionPane(FeatureCollection fc, Style style, boolean geomPreview) {
+    super();
+    this.featureStyle = style;
+
+    initGUI( geomPreview );
+
+    JScrollPane featuresTableScrollPane = new JScrollPane(
+      featuresTable,
+      JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+      JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
+    );
+
+    // Komponenten dem Pane hinzufuegen
+    if ( mapPane != null ) {
+      MultiSplitPane splitPane = new MultiSplitPane(2,JSplitPane.HORIZONTAL_SPLIT);
+      splitPane.setInnerBorder(null);
+      splitPane.setContainer(0,mapPane);
+      splitPane.setContainer(1,featuresTableScrollPane);
+      splitPane.setResizeWeigth( new double[] {0.2,0.8} );
+      add(splitPane);
+    } else {
+      add(featuresTableScrollPane);
+    }
+    setFeatureCollection( fc );
+  }
+
+  /**
+   * Initalisiert die GUI.
+   * @param geomPreview bestimmt, ob ein Preview-Bereich angezeigt wird ({@code true})
+   *                    oder nicht ({@code false})
+   */
+  protected void initGUI(boolean geomPreview ) {
+    setLayout( new BorderLayout() );
+    // MapPane fuer Preview der in der Tabelle selektieren Features
+    if ( geomPreview ) {
+      this.mapPane = new JMapPane() {
+        // Bei Links-Klick auf das gesamte Layer zoomen
+        public void mouseClicked(MouseEvent e) {
+          if (getState() == JMapPane.RESET && e.getButton() == e.BUTTON1) {
+            MapLayer layer = getTopLayer();
+            if (layer != null)
+              try {
+                setMapArea(layer.getFeatureSource().getBounds());
+                refresh();
+              } catch (Exception err) {
+              }
+          } else {
+            super.mouseClicked(e);
+          }
+        }
+      };
+      mapPane.setState(JMapPane.RESET);
+      mapPane.setWindowSelectionState(JMapPane.NONE);
+      mapPane.setHighlight(false);
+      mapPane.setMinimumSize(new Dimension(50, 50));
+      SwingUtil.setPreferredWidth(mapPane,100);
+      mapPane.setBorder(BorderFactory.createLoweredBevelBorder());
+    }
+
+    // Tabelle fuer FeatureCollection
+    featuresTableModel = new FeatureCollectionTableModel();
+    featuresTable      = new JTable(featuresTableModel);
+    featuresTable.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
+    featuresTable.setColumnSelectionAllowed(false);
+    featuresTable.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
+    // Wenn Auswahl in Tabelle sich aendert, Vorschau anpassen
+    featuresTable.getSelectionModel().addListSelectionListener( new ListSelectionListener() {
+      public void valueChanged(ListSelectionEvent e) {
+        performListSelection();
+      }
+    } );
+    // Wenn neue FeatureCollection gesetzt wird, alle Features
+    // in Vorschau anzeigen
+    featuresTableModel.addTableModelListener( new TableModelListener() {
+      public void tableChanged(TableModelEvent e) {
+        showFeaturesInMap( getFeatureCollection() );
+      }
+    } );
+  }
+
+  /**
+   * Liefert den Filter, der die dargestellten Attribut-Spalten bestimmt.
+   */
+  public AttributeTypeFilter getAttributeFilter() {
+    return featuresTableModel.getAttributeFilter();
+  }
+
+  /**
+   * Setzt den Filter, der die dargestellten Attribut-Spalten bestimmt.
+   * @param attrFilter Filter
+   */
+  public void setAttributeFilter(AttributeTypeFilter attrFilter) {
+    featuresTableModel.setAttributeFilter(attrFilter);
+  }
+
+  /**
+   * Prueft, ob der Geometry-Preview angezeigt wird.
+   */
+  public boolean isGeometryPreviewVisible() {
+    return mapPane.isVisible();
+  }
+
+  /**
+   * Setzt die angezeigte {@link FeatureCollection}
+   * @param fc anzuzeigende Features
+   */
+  public void setFeatureCollection(FeatureCollection fc) {
+    featuresTableModel.setFeatureCollection(fc);
+  }
+
+  /**
+   * Liefert die angezeigten Features.
+   */
+  public FeatureCollection getFeatureCollection() {
+    return featuresTableModel.getFeatureCollection();
+  }
+
+  /**
+   * Setzt den Style, in dem die Features in der Karte dargestellt werden.
+   * @param style Style fuer die Features
+   */
+  public void setFeatureStyle( Style style ) {
+    this.featureStyle = style;
+  }
+
+  /**
+   * Liefert den Style, in dem die Features in der Karte dargestellt werden.
+   */
+  public Style getFeatureStyle() {
+    return this.featureStyle;
+  }
+
+  /**
+   * Liefert die in der Tabelle selektierten Features.
+   */
+  public FeatureCollection getSelectedFeatures() {
+    return featuresTableModel.getFeaturesAsCollection( featuresTable.getSelectedRows() );
+  }
+
+  /**
+   * Zeigt die aktuell in der Tabelle selektierten Features im Preview-Bereich an.
+   */
+  protected void performListSelection() {
+    FeatureCollection subCollection = getSelectedFeatures();
+    // Wenn keine Auswahl getaetigt ist, alle Features anzeigen
+    if (subCollection == null || subCollection.isEmpty() )
+      subCollection = getFeatureCollection();
+    showFeaturesInMap( subCollection );
+  }
+
+  /**
+   * Zeigt einen {@link FeatureCollection} in der Karte an. Diese Funktion
+   * hat keinen Einfluss auf die in der Tabelle angezeigten Features!!
+   * @param fc eine {@link FeatureCollection}
+   */
+  protected void showFeaturesInMap(FeatureCollection fc) {
+    if ( mapPane == null )
+      return;
+    mapPane.getContext().clearLayerList();
+    if ( fc != null && fc.size() > 0 && fc.getSchema().getDefaultGeometry() != null ) {
+      if ( getFeatureStyle() == null )
+        setFeatureStyle( FeatureUtil.createDefaultStyle(fc) );
+      mapPane.getContext().addLayer(fc, getFeatureStyle() );
+      mapPane.setMapArea(fc.getBounds());
+    }
+    mapPane.refresh();
+  }
+
+
+  /**
+   * Tabellen-Modell fuer eine FeatureCollection. Aus Effizienzgruenden
+   * (um wahlfreien Zugriff zu erreichen) werden die Features beim Aufruf von
+   * {@link #setFeatureCollection(FeatureCollection)} in ein Array kopiert.
+   * Veraenderungen an der zugrunde liegenden {@link FeatureCollection} werden
+   * somit nicht automatisch in das Tabellen-Modell uebernommen, sondern es ist
+   * ein expliziter Aufruf von {@link #reorganize()} notwendig.<br>
+   * Bis zu diesem Punkt arbeitet diese Tabellen-Modell identisch zum
+   * {@code FeatureTableModel} von Geotools. Im Gegensatz zu
+   * {@link org.geotools.gui.swing.table.FeatureTableModel org.geotools.gui.swing.table.FeatureTableModel}.
+   * werden die Tabellennamen und die Anzahl an Spalten jedoch nicht bei jedem Zugriff
+   * neu aus dem ersten Feature ermittelt, sondern global gespeichert. Dies ist
+   * erffizienter, da das haeufige Oeffnen eines {@link FeatureIterator FeatureIterators}
+   * vermieden wird.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected static class FeatureCollectionTableModel extends AbstractTableModel {
+      /** Holds the feature table that will be represented by this model. */
+      protected FeatureCollection featureTable = null;
+      /** Array mit den Daten von {@link #featureTable}. Wird nur beim Aufruf von
+       *  {@link #reorganize} befuellt. */
+      protected Feature[] featureArray;
+      /** Spaltennamen. Wird nur beim Aufruf von {@link #reorganize} befuellt. */
+      protected String[] colNames = null;
+      /** Bestimmt die angezeigten Features */
+      protected AttributeTypeFilter attrFilter = AttributeTypeFilter.ALL;
+
+      /** Speichert die angezeigten Attribut-Typen. */
+      protected Vector<AttributeType> attrTypes = new Vector<AttributeType>();
+      /** Speichert den Attribut-Index fuer jede Spalte (wichtig, wenn nicht
+       *  alle Spalten angezeigt werden!) */
+      protected int[] attrIdxForCol = null;
+
+      /**
+       * Erzeugt ein neues (leeres) Tabellen-Modell.
+       */
+      public FeatureCollectionTableModel() {
+        this(null);
+      }
+
+      /**
+       * Erzeugt ein neues Tabellen-Modell.
+       * @param features Daten fuer die Tabelle
+       */
+      public FeatureCollectionTableModel(final FeatureCollection features) {
+        super();
+        setFeatureCollection(features);
+      }
+
+      /**
+       * Baut die interne Datenbasis (Array) des Tabellen-Modells neu auf.
+       * Muss aufgerufen werden, damit nachtraegliche Aenderungen an der
+       * zugrunde liegenden {@link FeatureCollection} in das Tabellenmodell
+       * uebernommen werden.
+       */
+      public void reorganize() {
+        featureArray = FeatureUtil.featuresToArray(featureTable);
+        if ( featureArray == null || featureArray.length == 0 )
+          colNames = new String[0];
+        else {
+          // Struktur der Tabelle von erstem Feature uebernehmen
+          FeatureType ft = featureArray[0].getFeatureType();
+          // Pruefen, welche Attribute angezeigt werden
+          attrTypes.clear();
+          for (int i=0; i<ft.getAttributeCount(); i++) {
+            AttributeType type = ft.getAttributeType(i);
+            if ( attrFilter == null || attrFilter.accept( type, i ) )
+              attrTypes.add( type );
+          }
+          // Namen und Attribut-Indizes der angezeigten Spalten ermitteln
+          colNames      = new String[ attrTypes.size() ];
+          attrIdxForCol = new int[ attrTypes.size() ];
+          for (int i=0; i<colNames.length; i++) {
+            int idx = ft.find( attrTypes.elementAt(i) );
+            attrIdxForCol[i] = idx;
+            colNames[i]      = ft.getAttributeType(idx).getName();
+          }
+        }
+        fireTableStructureChanged();
+      }
+
+      /**
+       * Setzt die FeatureCollection fuer das Tabellen-Modell.
+       * Ruft {@link #reorganize} auf.<br>
+       * Nachtraegliche Aenderungen an der {@link FeatureCollection} werden
+       * erst nach erneutem {@link #reorganize()} in das Tabellen-Modell uebernommen!
+       * @param features Daten-Basis fuer die Tabelle
+       */
+      public void setFeatureCollection(final FeatureCollection features) {
+          featureTable = features;
+          reorganize();
+      }
+
+      /**
+       * Liefert die FeatureCollection fuer das Tabellen-Modell.
+       * Nachtraegliche Aenderungen an der {@link FeatureCollection} werden
+       * erst nach erneutem {@link #reorganize()} in das Tabellen-Modell uebernommen!
+       */
+      public FeatureCollection getFeatureCollection() {
+          return featureTable;
+      }
+
+      /**
+       * Liefert den Filter, der die dargestellten Attribut-Spalten bestimmt.
+       */
+      public AttributeTypeFilter getAttributeFilter() {
+        return attrFilter;
+      }
+
+      /**
+       * Setzt den Filter, der die dargestellten Attribut-Spalten bestimmt.
+       * @param attrFilter Filter
+       */
+      public void setAttributeFilter(AttributeTypeFilter attrFilter) {
+        this.attrFilter = attrFilter;
+        // Filter neu anwenden
+        reorganize();
+      }
+
+      /**
+       * Liefert die Anzahl an Spalten der Tabelle. Entspricht der Anzahl an
+       * Attributen der Features.
+       */
+      public int getColumnCount() {
+        return colNames == null ? 0 : colNames.length;
+      }
+
+      /**
+       * Liefert einen Spaltennamen der Tabelle. Entspricht den Attributnamen
+       * des (ersten) Features der Collection.
+       * @param col Spalten-Index, beginnend bei 0
+       */
+      public String getColumnName(int col) {
+        return colNames == null || col >= colNames.length ? "" : colNames[col];
+      }
+
+      /**
+       * Liefert die Anzahl an Zeilen der Tabelle. Entspricht der Anzahl an
+       * Features in der Collection.
+       */
+      public int getRowCount() {
+        return featureTable == null ? 0 : featureTable.size();
+      }
+
+      /**
+       * Liefert eine Zellen-Wert.
+       * @param row Zeilennummer (Feature), beginnend bei 0
+       * @param col Spaltennummer (Attribut), beginnen bei 0
+       */
+      public Object getValueAt(final int row, final int col) {
+          if (featureArray == null)
+              featureArray = FeatureUtil.featuresToArray(featureTable);
+          return featureArray[row].getAttribute( attrIdxForCol[col] );
+      }
+
+      /**
+       * Liefert Features der Tabelle als Array.
+       * @param idx Indizes der FeatureCollection
+       */
+      public Feature[] getFeaturesAsArray(int[] idx) {
+        Feature[] features = new Feature[ idx == null ? 0 : idx.length ];
+        for (int i=0; i<features.length; i++)
+          features[i] = featureArray[ idx[i] ];
+        return features;
+      }
+
+      /**
+       * Liefert Features der Tabelle als Collection.
+       * @param idx Indizes der FeatureCollection
+       */
+      public FeatureCollection getFeaturesAsCollection(int[] idx) {
+        FeatureCollection featureCol = FeatureCollections.newCollection();
+        for (int i=0; idx != null && i<idx.length; i++)
+          featureCol.add( featureArray[ idx[i] ] );
+        return featureCol;
+      }
+ }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureFilterPanel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureFilterPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureFilterPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,218 @@
+package schmitzm.geotools.gui;
+
+import java.awt.Insets;
+import java.awt.GridBagConstraints;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Map;
+
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JLabel;
+import javax.swing.ListSelectionModel;
+import javax.swing.JTextField;
+
+import schmitzm.swing.SwingUtil;
+
+import org.geotools.filter.Filter;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.type.GeometricAttributeType;
+import org.geotools.data.DefaultFeatureResults;
+
+import schmitzm.swing.OperationTreePanel;
+import schmitzm.geotools.feature.FeatureTypeTableModel;
+import schmitzm.geotools.feature.FeatureOperationTreeFilter;
+import schmitzm.geotools.feature.FeatureTypeTableModel;
+import schmitzm.geotools.feature.AttributeTypeFilter;
+
+// fuer Doku
+import schmitzm.geotools.feature.FeatureOperationTree;
+import schmitzm.geotools.feature.FeatureOperationTreeParser;
+
+/**
+ * Diese Klasse stellt ein Panel zur Vefuegung, mit der ein
+ * {@link FeatureOperationTreeFilter} in Form einer arithmetischen
+ * (und boolschen) Formel erstellt werden kann.
+ * @see FeatureOperationTree
+ * @see FeatureOperationTreeParser
+ * @see FeatureOperationTreeFilter
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureFilterPanel extends OperationTreePanel {
+  /** Konstante fuer das Label zur Tabelle der Feature-Attribute.
+   *  @see #layoutConstraints
+   *  @see #resetCaptions(Map) */
+  public static final String ATTRIBUTE_LABEL = FeatureFilterPanel.class.getName()+".FeatureAttributeLabel";
+  /** Konstante fuer die Tabelle der Feature-Attribute.
+   *  @see #layoutConstraints */
+  public static final String ATTRIBUTE_TABLE = FeatureFilterPanel.class.getName()+".FeatureAttributeTable";
+
+  /** Label ueber der Tabelle mit den Feature-Attributen. */
+  protected JLabel                attributeLabel = null;
+  /** Tabelle in der die Feature-Attribute angegeben werden. */
+  protected JTable                attributeTable = null;
+  /** Tabellen-Modell, das den Inhalt der Feature-Attribut-Tabelle bestimmt */
+  protected FeatureTypeTableModel attributeTableModel = null;
+  private   JScrollPane           attributeScrollPane = null;
+
+  /**
+   * Erzeugt ein neues Panel.
+   * @param ftype definiert die zur Verfuegung gestellten Feature-Attribute
+   * @see FeatureCollection#getSchema()
+   */
+  public FeatureFilterPanel(FeatureType ftype) {
+    this(ftype,true);
+  }
+
+  /**
+   * Erzeugt ein neues Panel.
+   * @param fc definiert die zur Verfuegung gestellten Feature-Attribute
+   * @see FeatureCollection#getSchema()
+   */
+  public FeatureFilterPanel(FeatureCollection fc) {
+    this( fc.getSchema() );
+  }
+
+  /**
+   * Erzeugt ein neues Panel
+   * @param ftype definiert die zur Verfuegung gestellten Feature-Attribute
+   * @param initGUI Flag, ob {@link #initGUI()} am Ende des Konstruktor
+   *        aufgerufen werden soll (wenn {@code false} muss die explizit
+   *        durch die Unterklasse erfolgen!)
+   */
+  protected FeatureFilterPanel(FeatureType ftype, boolean initGUI) {
+    super(false);
+
+    // Zusaetzliche Operatoren und Konstanten
+    // avOperators.add(...);    avOperatorsDesc.put(..., ...);
+
+    // Layout-Anordnung fuer GUI
+    layoutConstraints.put( ATTRIBUTE_LABEL,   new GridBagConstraints(0,0,2,1,  0,  0,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5,10,0,10),0,0) );
+    layoutConstraints.put( ATTRIBUTE_TABLE,   new GridBagConstraints(0,1,2,1,1.0,1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0,10,5,10),0,0) );
+    layoutConstraints.put( RULE_TEXTFIELD,    new GridBagConstraints(0,2,1,1,1.0,  0,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,10,5,10),0,0) );
+    layoutConstraints.put( OPERATOR_COMBOBOX, new GridBagConstraints(1,2,1,1,0.0,  0,GridBagConstraints.SOUTHEAST, GridBagConstraints.HORIZONTAL, new Insets(0,10,5,10),2,2) );
+    layoutConstraints.put( START_BUTTON,      new GridBagConstraints(0,3,1,1,0.0,  0,GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,10,5,10),0,0) );
+
+    if ( initGUI ) {
+      initGUI();
+      setFeatureType( ftype );
+    }
+  }
+
+  /**
+   * Initalisiert die GUI des Fensters. Der "Start-Button" der durch die
+   * Oberklasse definiert wird, wird dabei aus dem Panel entfernt!
+   */
+  protected void initGUI() {
+    super.initGUI();
+
+    // Input-Raster-Tabelle
+    attributeTableModel = new FeatureTypeTableModel();
+    attributeTableModel.setAttributeFilter( AttributeTypeFilter.NO_GEOMETRY );
+    attributeTable = new JTable( attributeTableModel );
+    attributeTable.setTableHeader(null);
+    attributeTable.setShowGrid(false);
+    attributeTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+    attributeScrollPane = new JScrollPane( attributeTable );
+    SwingUtil.setPreferredHeight( attributeScrollPane, 50 );
+
+    // Bei Doppelklick auf Tabelle wird der Attribut-Name in die Formel
+    // eingefuegt
+    attributeTable.addMouseListener( new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+        // Nur auf Doppelklick reagieren
+        if ( e.getButton() != MouseEvent.BUTTON1 ||
+             e.getClickCount() != 2 )
+          return;
+        // Selektiertes Attribut ermitteln
+        String attrName = (String)attributeTableModel.getValueAt(
+            attributeTable.getSelectedRow(),
+            0
+        );
+        // Attribut-Namen in Formel einfuegen
+        performOperatorInsert("$"+attrName, (JTextField)rule.getInputComponent());
+        // Fokus zurueck auf Formel-Feld
+        rule.grabFocus();
+      }
+    });
+
+    // kein "startButton"
+    remove( this.startButton );
+    // zusaetzliche Komponenten einfuegen
+    this.attributeLabel = new JLabel( GeotoolsGUIUtil.RESOURCE.getString("Attributes")+":");
+    add( attributeLabel, layoutConstraints.get(ATTRIBUTE_LABEL) );
+    add( attributeScrollPane, layoutConstraints.get(ATTRIBUTE_TABLE) );
+  }
+
+  /**
+   * Setzt die Labels des Panels neu.
+   * @param captionMap Map
+   * @see OperationTreePanel#resetCaptions(Map)
+   */
+  public void resetCaptions(Map<String,Object> captionMap) {
+    super.resetCaptions(captionMap);
+    SwingUtil.resetCaption( attributeLabel, captionMap.get(ATTRIBUTE_LABEL) );
+  }
+
+  /**
+   * Setzt den {@link FeatureType}, dessen Attribute fuer die Filter-Formel
+   * angeboten werden.
+   * @param ftype ein {@link FeatureType}
+   */
+  public void setFeatureType(FeatureType ftype) {
+    attributeTableModel.setFeatureType( ftype );
+  }
+
+  /**
+   * Liefert den {@link FeatureType}, dessen Attribute fuer die Filter-Formel
+   * angeboten werden.
+   */
+  public FeatureType getFeatureType() {
+    return attributeTableModel.getFeatureType();
+  }
+
+  /**
+   * Liefert den Filter, der die dargestellten Attribute bestimmt.
+   */
+  public AttributeTypeFilter getAttributeFilter() {
+    return attributeTableModel.getAttributeFilter();
+  }
+
+  /**
+   * Setzt den Filter, der die dargestellten Attribute bestimmt.
+   * @param attrFilter Filter
+   * @see FeatureTypeTableModel#setAttributeFilter(AttributeTypeFilter)
+   */
+  public void setAttributeFilter(AttributeTypeFilter attrFilter) {
+    attributeTableModel.setAttributeFilter(attrFilter);
+  }
+
+  /**
+   * Liefert die im Panel eingetragene Formel.
+   */
+  public String getRule() {
+    return rule.getValue();
+  }
+
+  /**
+   * Setzt die im Panel eingegebene Formel.
+   * @param ruleString Formel als String
+   */
+  public void setRule(String ruleString) {
+    rule.setValue(ruleString);
+  }
+
+  /**
+   * Liefert einen {@link Filter} zu der im Panel eingetragene Formel.
+   * @return TRUE-Regel, falls im Panel keine Formel eingetragen ist.
+   */
+  public Filter createFilter() {
+    String rule = getRule();
+    if ( rule == null || rule.trim().equals("") )
+      rule = "1";
+    return new FeatureOperationTreeFilter( rule );
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureInputOption.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,120 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.gui;
+
+import java.awt.Dimension;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureTypeBuilder;
+
+import schmitzm.geotools.feature.FeatureTableModel;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.MultipleOptionPane;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.event.InputOptionAdapter;
+import schmitzm.swing.event.InputOptionListener;
+import schmitzm.swing.table.ComponentRenderer;
+import schmitzm.swing.table.MutableTable;
+
+/**
+ * {@linkplain InputOption Eingabe-Option} zur Definition eines {@link Feature} im
+ * {@link MultipleOptionPane}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class FeatureInputOption extends InputOption<Feature> {
+  /** Beinhaltet die Tabelle, in der die Attribute eingegeben
+   *  werden. */
+  protected JTable inpTable;
+  /** Beinhaltet das Tabellen-Modell, in dem die Attribute verwaltet
+   *  werden. */
+  protected FeatureTableModel inpTableModel;
+
+  /**
+   * Erzeugt einen neue Eingabe-Option.
+   * @param label Ueberschrift
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   * @param feature initalial dargestelltes {@link Feature}
+   */
+  public FeatureInputOption(String label, boolean inputNeeded, Feature feature) {
+    this(label, inputNeeded, feature != null ? feature.getFeatureType() : null);
+    setValue(feature);
+  }
+
+  /**
+   * Erzeugt einen neue Eingabe-Option.
+   * @param label Ueberschrift
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   * @param fType bestimmt die dargestellten Attribute (kann {@code null} sein)
+   */
+  public FeatureInputOption(String label, boolean inputNeeded, FeatureType fType) {
+    super(label, inputNeeded, true);
+    inpTableModel.setFeature(fType);
+  }
+
+  /**
+   * Erzeugt eine neue Instanz der Eingabe-Komponente. z.B. ein Text-Eingabefeld
+   * oder eine Combo-Box.
+   */
+  protected JScrollPane createInputComponent() {
+    this.inpTableModel = new FeatureTableModel();
+    this.inpTable      = new JTable( inpTableModel );
+    this.inpTable.setColumnSelectionAllowed(false);
+    this.inpTable.setRowSelectionAllowed(false);
+    SwingUtil.setPreferredHeight(this,200);
+    SwingUtil.setPreferredWidth(this,200);
+    return new JScrollPane(inpTable);
+  }
+
+  /**
+   * Liefert das aktuell in der Tabelle definierte {@link Feature}.
+   */
+  protected Feature performGetValue() {
+    // Workaround: Editieren der zuletzt editierten Zelle
+    //             beenden, damit Wert in Feature uebernommen
+    //             wird
+    inpTable.editCellAt(-1, -1);
+    return inpTableModel.getFeature();
+  }
+  /**
+   * Initalisiert die Tabelle mit einem neuen {@link Feature}.
+   * @param newValue neues {@link Feature}
+   * @return immer {@code true}
+   */
+  protected boolean performSetValue(Feature newValue) {
+    inpTableModel.setFeature(newValue);
+    return true;
+  }
+
+  /**
+   * Prueft, ob ein Feature definiert ist.
+   */
+  protected boolean performIsInputEmpty() {
+    return inpTableModel.getFeature() == null;
+  }
+
+  /**
+   * Prueft, ob die aktuelle Eingabe leer ist. Liefert immer {@code true}.
+   */
+  protected boolean performIsInputValid() {
+    return true;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureLayerFilterDialog.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureLayerFilterDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureLayerFilterDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,227 @@
+package schmitzm.geotools.gui;
+
+import java.awt.event.ActionEvent;
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.awt.Frame;
+import java.awt.FlowLayout;
+import java.awt.Window;
+
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JButton;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.map.MapLayer;
+import org.geotools.map.event.MapLayerEvent;
+
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.CaptionsChangeable;
+import schmitzm.geotools.map.event.MapLayerAdapter;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import org.geotools.filter.Filter;
+
+
+/**
+ * Diese Klasse stellt einen Dialog dar, in dem eine {@link FeatureCollection}
+ * ueber eine Formel gefiltert werden kann. Beim Anwenden des Filters wird
+ * ein {@link FeatureSelectedEvent} ausgeloest, auf das z.B. mit dem
+ * Einfuegen eines neuen Layers reagiert werden kann.
+ * @see FeatureCollectionFilterPanel
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureLayerFilterDialog extends JDialog implements CaptionsChangeable {
+  /** Key, um den Titel des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String DIALOG_TITLE = FeatureLayerFilterDialog.class.getName()+".TITLE";
+  /** Key, um den OK-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String OK_BUTTON = FeatureLayerFilterDialog.class.getName()+".OK";
+  /** Key, um den ABBRECHEN-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CANCEL_BUTTON = FeatureLayerFilterDialog.class.getName()+".CANCEL";
+  /** Key, um den ÃœBERNEHMEN-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String APPLY_BUTTON = FeatureLayerFilterDialog.class.getName()+".APPLY";
+
+  /** Panel in dem der Filter definiert und getestet werden kann. */
+  protected FeatureCollectionFilterPanel filterPanel = null;
+  /** Der OK-Button. */
+  protected JButton okButton = null;
+  /** Der ABBRECHEN-Button. */
+  protected JButton cancelButton = null;
+  /** Der ANWENDEN-Button. */
+  protected JButton applyButton = null;
+
+  private String   frameTitle = GeotoolsGUIUtil.RESOURCE.getString(DIALOG_TITLE);
+  private JMapPane mapPane = null;
+  private MapLayer layer = null;
+
+  /**
+   * Erzeugt einen neuen Dialog.
+   * @param parent Uebergeordnetes Fenster (kann {@code null} sein)
+   * @param mapPane MapPane fuer welches {@link FeatureSelectedEvent} ausgeloest
+   *                werden
+   * @param mapLayer MapLayer, aus dem die FeatureCollection stammt, auf der der
+   *                 Filter definiert wird
+   * @exception IOException falls beim Ermitteln der {@link FeatureCollection} aus
+   *            dem Layer ein Fehler auftritt
+   *           
+   * CHANGE BY SK: Takes {@link Window} instead of {@link Frame}
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public FeatureLayerFilterDialog(Window parent, JMapPane mapPane, MapLayer mapLayer) throws IOException {
+    this(parent, mapPane, mapLayer, true);
+  }
+
+
+  /**
+   * Erzeugt einen neuen Dialog.
+   * @param parent Uebergeordnetes Fenster (kann {@code null} sein)
+   * @param mapPane MapPane fuer welches {@link FeatureSelectedEvent} ausgeloest
+   *                werden
+   * @param mapLayer MapLayer, aus dem die FeatureCollection stammt, auf der der
+   *                 Filter definiert wird
+   * @param initGUI bestimmt, ob {@link #initGUI()} aufgerufen werden soll. Wenn
+   *                {@code false}, muss der Konstruktor der Unterklasse dafuer
+   *                sorgen, dass {@link #initGUI()} aufgerufen wird
+   * @exception IOException falls beim Ermitteln der {@link FeatureCollection} aus
+   *            dem Layer ein Fehler auftritt
+   *            
+   * CHANGE BY SK: Takes {@link Window} instead of {@link Frame}     
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>       
+   */
+  protected FeatureLayerFilterDialog(Window parent, JMapPane mapPane, MapLayer mapLayer, boolean initGUI) throws IOException {
+    super(parent);
+    setModal(true);
+    this.setTitle( frameTitle + " ["+mapLayer.getTitle()+"]" );
+    this.setLayout( new BorderLayout() );
+    this.setSize(380,200);
+    this.mapPane = mapPane;
+    this.layer   = mapLayer;
+    if ( initGUI ) {
+      initGUI();
+      pack();
+    }
+  }
+
+
+
+  /**
+   * Initalisiert die GUI.
+   */
+  protected void initGUI() throws IOException {
+    filterPanel = new FeatureCollectionFilterPanel( layer.getFeatureSource().getFeatures(), true );
+    okButton = new JButton( SwingUtil.RESOURCE.getString("Ok") );
+    cancelButton = new JButton( SwingUtil.RESOURCE.getString("Cancel") );
+    applyButton = new JButton( SwingUtil.RESOURCE.getString("Apply") );
+    add( filterPanel, BorderLayout.CENTER);
+    JPanel buttonPanel = new JPanel( new FlowLayout(FlowLayout.CENTER, 10, 5) );
+    buttonPanel.add( okButton, BorderLayout.CENTER );
+    buttonPanel.add( cancelButton, BorderLayout.CENTER );
+    buttonPanel.add( applyButton, BorderLayout.CENTER );
+    add( buttonPanel, BorderLayout.SOUTH );
+
+    // Aktionen der Filter-Fenster-Button
+    ActionListener action = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // Ok- und Apply-Button > Filter anwenden und Event erzeugen
+        if ( e.getSource() == okButton || e.getSource() == applyButton ) {
+          // FeatureCollection filtern
+          try {
+            FeatureCollection fc = filterPanel.filterFeatureCollection();
+            FeatureSelectedEvent fse = new FeatureSelectedEvent(getMapPane() , layer, fc.getBounds(), fc, FeatureLayerFilterDialog.this);
+            getMapPane().fireMapPaneEvent( fse );
+          } catch ( Exception err ) {
+            ExceptionDialog.show(filterPanel,err);
+            // Fenster nicht schliessen, bei OK!!
+            return;
+          }
+        }
+        // Ok- und Cancel-Button > Dialog schliessen
+        if ( e.getSource() == okButton || e.getSource() == cancelButton ) {
+          setVisible( false );
+        }
+      }
+    };
+    okButton.addActionListener( action );
+    cancelButton.addActionListener( action );
+    applyButton.addActionListener( action );
+
+    // Wenn sich die die Bezeichnung des Layers aendert, soll auch der
+    // Titel des Filter-Fensters geaendert werden
+    layer.addMapLayerListener( new MapLayerAdapter() {
+      public void layerChanged(MapLayerEvent e) {
+        // Bezeichnung der Checkbox aendern
+        setTitle(frameTitle + " ["+layer.getTitle()+"]");
+      }
+    });
+  }
+
+  /**
+   * Liefert das MapPane, fuer das {@link FeatureSelectedEvent FeatureSelectedEvents}
+   * ausgeloest werden.
+   */
+  public JMapPane getMapPane() {
+    return mapPane;
+  }
+
+  /**
+   * Liefert das Layer, aus dem die {@link FeatureCollection} stammt auf der
+   * der Filter definiert wird.
+   */
+  public MapLayer getMapLayer() {
+    return layer;
+  }
+
+  /**
+   * Liefert das Panel, in dem der Filter definiert und die Vorschau angezeigt
+   * wird.
+   */
+  public FeatureCollectionFilterPanel getFeatureCollectionFilterPanel() {
+    return filterPanel;
+  }
+
+  /**
+   * Liefert die aktuell im Dialog eingegebene Formel.
+   */
+  public String getFilterRule() {
+    return filterPanel.getRule();
+  }
+
+  /**
+   * Setzt die im Dialog eingegebene Formel und aktualisiert entsprechend die
+   * Vorschau
+   * @param rule Formel als String
+   */
+  public void setFilterRule(String rule) {
+    filterPanel.setRule(rule);
+  }
+
+  /**
+   * Liefert den Filter der aktuell im Dialog eingegebenen Formel.
+   */
+  public Filter getFilter() {
+    return filterPanel.createFilter();
+  }
+
+  /**
+   * Belegt die Labels und Buttons im Dialog neu
+   * @param captionMap Map in der die neuen Labels hinterlegt sind.
+   * @see FeatureLayerFilterDialog
+   */
+  public void resetCaptions(Map<String,Object> captionMap) {
+    SwingUtil.resetCaption ( okButton, captionMap.get(OK_BUTTON) );
+    SwingUtil.resetCaption ( applyButton, captionMap.get(APPLY_BUTTON) );
+    SwingUtil.resetCaption ( cancelButton, captionMap.get(CANCEL_BUTTON) );
+    Object caption = captionMap.get( DIALOG_TITLE );
+    if ( caption != null ) {
+      frameTitle = caption.toString();
+      // Damit Layer-Bezeichnung in Titel uebernommen wird, ein MapLayerEvent
+      // ausloesen
+      layer.setTitle( layer.getTitle() );
+    }
+    filterPanel.resetCaptions( captionMap );
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/FeatureTypeInputOption.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/FeatureTypeInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/FeatureTypeInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,116 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.gui;
+
+import java.awt.Dimension;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureTypeBuilder;
+
+import schmitzm.geotools.feature.FeatureTypeBuilderTableModel;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.MultipleOptionPane;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.table.ComponentRenderer;
+import schmitzm.swing.table.MutableTable;
+
+/**
+ * {@linkplain InputOption Eingabe-Option} zur Definition eines {@link FeatureType} im
+ * {@link MultipleOptionPane}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class FeatureTypeInputOption extends InputOption<FeatureType> {
+  /** Beinhaltet die Tabelle, in der die Attribute eingegeben
+   *  werden. */
+  protected MutableTable inpTable;
+  /** Beinhaltet das Tabellen-Modell, in dem die Attribute verwaltet
+   *  werden. */
+  protected FeatureTypeBuilderTableModel inpTableModel;
+
+  /**
+   * Erzeugt einen neue Eingabe-Option.
+   * @param label Ueberschrift
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   */
+  public FeatureTypeInputOption(String label, boolean inputNeeded) {
+    this(label, inputNeeded, null);
+  }
+
+
+  /**
+   * Erzeugt einen neue Eingabe-Option.
+   * @param label Ueberschrift
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   * @param fType initalial dargestellter {@link FeatureType} (kann {@code null} sein)
+   */
+  public FeatureTypeInputOption(String label, boolean inputNeeded, FeatureType fType) {
+    super(label, inputNeeded, true);
+    if (fType != null)
+      setValue(fType);
+  }
+
+  /**
+   * Erzeugt eine neue Instanz der Eingabe-Komponente. z.B. ein Text-Eingabefeld
+   * oder eine Combo-Box.
+   */
+  protected JScrollPane createInputComponent() {
+    this.inpTableModel = new FeatureTypeBuilderTableModel();
+    this.inpTable      = new MutableTable( inpTableModel, MutableTable.ITEM_ADD | MutableTable.ITEM_REMOVE );
+    this.inpTable.setColumnSelectionAllowed(false);
+    this.inpTable.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
+    SwingUtil.setPreferredHeight(this,200);
+    SwingUtil.setPreferredWidth(this,400);
+    return new JScrollPane(inpTable);
+  }
+
+  /**
+   * Liefert den aktuellen in der Tabelle definierten {@link FeatureType}.
+   */
+  protected FeatureType performGetValue() {
+    return inpTableModel.createFeatureType();
+  }
+  /**
+   * Initalisiert die Tabelle mit einem neuen {@link FeatureType}.
+   * @param newValue neuer {@link FeatureType}
+   * @return immer {@code true}
+   */
+  protected boolean performSetValue(FeatureType newValue) {
+    inpTableModel.setFeatureType(newValue);
+    return true;
+  }
+
+  /**
+   * Prueft, ob ein Attribut definiert ist.
+   */
+  protected boolean performIsInputEmpty() {
+    return inpTable.getRowCount() == 0;
+  }
+
+  /**
+   * Prueft, ob die aktuelle Eingabe leer ist. Wirft eine Exception
+   * wenn {@link FeatureTypeBuilderTableModel#createFeatureType()}
+   * eine Exception wirft.
+   * @return immer {@code true}
+   */
+  protected boolean performIsInputValid() {
+    inpTableModel.createFeatureType();
+    return true;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/GTResource.20080424
===================================================================
--- trunk/src/schmitzm/geotools/gui/GTResource.20080424	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GTResource.20080424	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,93 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.util.ResourceBundle;
+import schmitzm.lang.HashtableResourceBundle;
+
+// nur fuer Doku
+import java.util.Locale;
+
+/**
+ * Dieses Standard-ResourceBundle stellt folgende Objekte fuer Geotools-Komponenten
+ * zur Verfuegung.
+ * <table align=center border=2 cellpadding=5><code>
+ * <tr><th>Key</th><th>Ressource</th></tr>
+ * <tr><td><code>Attributes</code></td><td>"Attributes"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.FeatureCollectionFilterPanel.testButton</code></td><td>"Test filter"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE</code></td><td>"Feature-Filter"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.INFO</code></td><td>"Info"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN</code></td><td>"Zoom in"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT</code></td><td>"Zoom out"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP</code></td><td>"Select from top layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL</code></td><td>"Select from all layers"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP</code></td><td>"Move layer UP"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN</code></td><td>"Move layer DOWN"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO</code></td><td>"Zoom to layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER</code></td><td>"Filter layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR</code></td><td>"Recolor layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE</code></td><td>"Remove layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL</code></td><td>"Show all layers"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL</code></td><td>"Hide all layers"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL</code></td><td>"Invert all layers"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE</code></td><td>"Customize..."</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE</code></td><td>"Color map"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE</code></td><td>"New color map"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION</code></td><td>"Enter a name for the new color map..."</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY</code></td><td>"Quantity"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.COLOR</code></td><td>"Color"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.LABEL</code></td><td>"Label"</td></tr>
+ * </table>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GTResource extends HashtableResourceBundle {
+  /**
+   * Enthaelt eine Referenz auf die Ressourcen in den Default-Locale
+   * @see Locale#setDefault(Locale)
+   */
+  public static final ResourceBundle DEFAULT = ResourceBundle.getBundle(GTResource.class.getName());
+
+  /**
+   * Liefert die (Key/Wert)-Paerchen. Die Keys (erste Dimension) muessen aus Strings
+   * bestehen.
+   */
+  public Object[][] getContents() {
+    return new Object[][] {
+        {"Attributes","Attributes"},
+        {"schmitzm.geotools.gui.FeatureCollectionFilterPanel.TestButton","Test filter"},
+        {"schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE","Feature-Filter"},
+        {"schmitzm.geotools.gui.MapActionControlPane.INFO","Info"},
+        {"schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN","Zoom in"},
+        {"schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT","Zoom out"},
+        {"schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP","Select from top layer"},
+        {"schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL","Select from all layers"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP","Move layer UP"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN","Move layer DOWN"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO","Zoom to layer"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER","Filter layer..."},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR","Recolor layer"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE","Remove layer"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL","Show all layers"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL","Hide all layers"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL","Invert all layers"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE","Customize..."},
+        {"schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE","Color map"},
+        {"schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE","New color map"},
+        {"schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION","Enter a name for the new color map..."},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY","Quantity"},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.COLOR","Color"},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.LABEL","Label"}
+    };
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/GTResource_de.20080424
===================================================================
--- trunk/src/schmitzm/geotools/gui/GTResource_de.20080424	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GTResource_de.20080424	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,85 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.util.ResourceBundle;
+import schmitzm.lang.HashtableResourceBundle;
+
+// nur fuer Doku
+import java.util.Locale;
+
+/**
+ * Dieses Standard-ResourceBundle stellt folgende Objekte fuer Swing-Komponenten
+ * zur Verfuegung.
+ * <table align=center border=2 cellpadding=5><code>
+ * <tr><th>Key</th><th>Ressource</th></tr>
+ * <tr><td><code>Attributes</code></td><td>"Attribute"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.FeatureCollectionFilterPanel.testButton</code></td><td>"Filter testen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE</code></td><td>"Feature-Filter"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN</code></td><td>"Heran zoomen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT</code></td><td>"Heraus zoomen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP</code></td><td>"Auswählen aus oberstem Layer"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL</code></td><td>"Auswählen aus allen Layern"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP</code></td><td>"Nach OBEN schieben"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN</code></td><td>"Nach UNTEN schieben"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO</code></td><td>"Zu Layer zoomen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER</code></td><td>"Layer filtern..."</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR</code></td><td>"Färbung ändern"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE</code></td><td>"Layer entfernen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL</code></td><td>"Alle layer anzeigen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL</code></td><td>"Alle layer verbergen"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL</code></td><td>"Alle layer invertieren"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE</code></td><td>"Anpassen..."</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE</code></td><td>"Farbpalette"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE</code></td><td>"Farbpalette speichern"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION</code></td><td>"Name für neue Farbpalette..."</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY</code></td><td>"Raster-Wert"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.COLOR</code></td><td>"Farbe"</td></tr>
+ * <tr><td><code>schmitzm.geotools.gui.ColorMapTable.Header.LABEL</code></td><td>"Label"</td></tr>
+ * </table>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GTResource_de extends GTResource {
+  /**
+   * Liefert die (Key/Wert)-Paerchen. Die Keys (erste Dimension) muessen aus Strings
+   * bestehen.
+   */
+  public Object[][] getContents() {
+    return new Object[][] {
+        {"Attributes","Attribute"},
+        {"schmitzm.geotools.gui.FeatureCollectionFilterPanel.TestButton","Filter testen"},
+        {"schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE","Feature-Filter"},
+        {"schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN","Heran zoomen"},
+        {"schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT","Heraus zoomen"},
+        {"schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP","Auswählen aus oberstem Layer"},
+        {"schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL","Auswählen aus allen Layern"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP","Nach OBEN schieben"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN","Nach UNTEN schieben"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO","Zu Layer zoomen"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER","Layer filtern..."},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR","Färbung ändern"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE","Layer entfernen"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL","Alle Layer anzeigen"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL","Alle Layer verbergen"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL","Alle Layer invertieren"},
+        {"schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE","Anpassen..."},
+        {"schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE","Farbpalette"},
+        {"schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE","Farbpalette speichern"},
+        {"schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION","Name für neue Farbpalette..."},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY","Raster-Wert"},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.COLOR","Farbe"},
+        {"schmitzm.geotools.gui.ColorMapTable.Header.LABEL","Label"}
+    };
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/GeoMapPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/GeoMapPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GeoMapPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,251 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.io.File;
+import java.util.HashMap;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.map.MapContext;
+
+import schmitzm.swing.JPanel;
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.io.GeoImportUtil;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.map.event.ScaleChangedEvent;
+
+/**
+ * Das {@code GeoMapPane} erweitert das {@link JMapPane} um einen Massstab-Balken,
+ * sowie ein horizontales und vertikales Koordinaten-Raster (Grid), in dem
+ * die Geo-Referenz des angezeigten Karten-Bereichs in Welt-Koordinaten (Grad, Minuten)
+ * dargestellt ist.
+ * @see ScalePane
+ * @see GridPanel
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class GeoMapPane extends JPanel {
+
+  /** Konstante fuer das vertikale Koordinaten-Grid.
+  *  @see #layoutConstraints */
+  public static final String DLC_VGRID = "vertGrid";
+  /** Konstante fuer das horizontale Koordinaten-Grid.
+  *  @see #layoutConstraints */
+  public static final String DLC_HGRID = "horGrid";
+  /** Konstante fuer die Karte.
+  *  @see #layoutConstraints */
+  public static final String DLC_MAP   = "mapPane";
+  /** Konstante fuer den Massstab-Balken.
+   *  @see #layoutConstraints */
+  public static final String DLC_SCALE = "scalePane";
+  /** Werte fuer die grafische Anordnung der Layout-Komponenten.
+   *  Ueber die Konstanten {@link #DLC_MAP}, {@link #DLC_VGRID}, {@link #DLC_HGRID}
+   *  und {@link #DLC_SCALE} koennen die Constraints (am Anfang von {@link #guiInit()})
+   *  veraendert oder erweitert werden. */
+  protected HashMap<String,GridBagConstraints> layoutConstraints = new HashMap<String,GridBagConstraints>();
+
+  /** Karten-Bereich des {@code GeoMapPane}. */
+  protected JMapPane mapPane = null;
+  /** Massstab-Balken */
+  protected ScalePane scalePane = null;
+  /** Vertikale Koordinaten-Leiste (Grid). Zeigt die Latitude-Koordinate in
+   *  Grad und Minutenm an. */
+  protected GridPanel vertGrid = null;
+  /** Horizontale Koordinaten-Leiste (Grid). Zeigt die Longitude-Koordinate in
+   *  Grad und Minutenm an. */
+  protected GridPanel horGrid = null;
+
+  /** A flag indicating if dispose() was already called. If true, then further use of this {@link GeoMapPane} is undefined.  */
+  private boolean disposed = false;
+
+  /**
+   * Erzeugt ein neues {@code GeoMapPane}.
+   */
+  public GeoMapPane() {
+    this( null, null, null, null );
+  }
+
+  /**
+   * Erzeugt ein neues {@code GeoMapPane}. Dieser Konstruktor bietet die
+   * Moeglichkeit, alternative Implementierungen der einzelnen Komponenten
+   * darzustellen. <b>Die uebergebenen Parameter koennen {@code null} sein!
+   * In diesem Fall wird die entsprechende Standard-Implementierung verwendet.</b>
+   * @param mapPane   Karten-Bereich
+   * @param vGrid     vertikales Koordinaten-Raster
+   * @param hGrid     horizontales Koordinaten-Raster
+   * @param scalePane Massstab-Balken
+   */
+  public GeoMapPane(JMapPane mapPane, GridPanel vGrid, GridPanel hGrid, ScalePane scalePane) {
+    super();
+
+    if ( vGrid != null && !vGrid.isVertical() )
+      throw new IllegalArgumentException("GridPanel for vertical grid must be of type GridPanel.VERTICAL!!");
+    if ( hGrid != null && !hGrid.isHorizontal() )
+      throw new IllegalArgumentException("GridPanel for horizontal grid must be of type GridPanel.HORIZONTAL!!");
+
+    // Karte
+    this.mapPane =  ( mapPane   != null ) ? mapPane : new JMapPane();
+    // Koordinaten-Leisten
+    this.vertGrid = ( vGrid != null ) ? vGrid : new GridPanel( GridPanel.VERTICAL, this.mapPane );
+    this.horGrid  = ( hGrid != null ) ? hGrid : new GridPanel( GridPanel.HORIZONTAL, this.mapPane );
+    // Massstab-Balken
+    this.scalePane = ( scalePane != null ) ? scalePane : new ScalePane();
+
+    // Standard-Werte fuer GUI-Anordnung setzen
+    layoutConstraints.put(DLC_MAP,   new GridBagConstraints(1,0,1,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    layoutConstraints.put(DLC_VGRID, new GridBagConstraints(0,0,1,1,0.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    layoutConstraints.put(DLC_HGRID, new GridBagConstraints(1,1,1,1,1.0,0.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    layoutConstraints.put(DLC_SCALE, new GridBagConstraints(1,2,1,1,0.0,0.0,GridBagConstraints.WEST,  GridBagConstraints.BOTH,new Insets(2,5,2,5),0,0));
+    // GUI initialisieren
+    guiInit();
+  }
+
+  /**
+   * Wird vom Konstruktor aufgerufen und initialisiert die grafische Darstellung/Anordnung
+   * der einzelnen GUI-Komponenten in einem {@link GridBagLayout}. Die Constraints
+   * fuer die Anordnung der Komponenten sind in {@link #layoutConstraints}
+   * hinterlegt und koennen zu beginn dieser Methode ueberschrieben oder erweitert
+   * werden.
+   */
+  protected void guiInit() {
+    this.setLayout( new GridBagLayout() );
+
+    // Karten-Darestellung initialisieren
+    this.mapPane.setBorder(BorderFactory.createLoweredBevelBorder());
+    this.mapPane.setPreferredSize( new Dimension(200, mapPane.getPreferredSize().height) );
+
+    // MapListener that listens to Scale and MapArea changes
+    this.mapPane.addMapPaneListener(new JMapPaneListener() {
+      public void performMapPaneEvent(JMapPaneEvent e) {
+        if (e instanceof ScaleChangedEvent) {
+          ScaleChangedEvent sce = (ScaleChangedEvent) e;
+          scalePane.setScale(sce.getNewScale());
+        }
+        if (e instanceof MapAreaChangedEvent) {
+          vertGrid.repaint();
+          horGrid.repaint();
+        }
+      }
+    });
+
+    // Komponenten in Layout anordnen
+    this.add(vertGrid,  layoutConstraints.get(DLC_VGRID));
+    this.add(horGrid,   layoutConstraints.get(DLC_HGRID));
+    this.add(scalePane, layoutConstraints.get(DLC_SCALE));
+    // WICHTIG: mapPane als LETZTES einfuegen, damit es beim Re-Paint ALS ERSTES
+    //          aktualisiert wird und die Aenderungen (Area, Scale) im
+    //          Update des Massstab-Balken und der der Koordinaten-Leiste
+    //          bereits zur Verfuegung stehen!
+    this.add(mapPane,   layoutConstraints.get(DLC_MAP));
+
+//    // Hintergrundfarbe aller enthaltenen Komponenten auf Weiss setzen
+//    setBackground(BGCOLOR, true);
+  }
+
+  /**
+   * Aktualisiert die Karten-Darstellung.
+   * @see JMapPane#refresh()
+   */
+  public void refreshMap() {
+    mapPane.refresh();
+  }
+
+  /**
+   * Nach dem {@code super}-Aufruf, wird der Massstab neu gesetzt (und somit
+   * neu angezeigt), falls sich der Massstab der Karte geaendert hat.
+   * Dies ist ein Workaround, damit der Massstab auch beim allerersten anzeigen
+   * korrekt angezeigt wird (ohne {@link ScaleChangedEvent}).
+   */
+  public void paint(Graphics g) {
+    super.paint(g);
+    if ( scalePane.getScaleInMeters() != mapPane.getScale() && mapPane.getScale() > 0 )
+      scalePane.setScale( mapPane.getScale() );
+  }
+
+  /**
+   * Liefert das {@link JMapPane}, in dem die Karten dargestellt werden.
+   */
+  public final JMapPane getMapPane() {
+    return mapPane;
+  }
+
+  /**
+   * Liefert den {@link MapContext} der die einzelnen Karten-Layer verwaltet.
+   */
+  public final MapContext getMapContext() {
+    return mapPane.getContext();
+  }
+
+  /**
+   * Setzt die Hintergrundfarbe des kompletten {@link GeoMapPane}.
+   * @param color Hintergrundfarbe
+   */
+  public void setBackground(Color color) {
+    setBackground(color,true);
+  }
+
+  /**
+   * @return If dispose() has been called. If <code>true<(code>, further use of this {@link GeoMapPane} is undefined
+   *
+   *  @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public boolean isDisposed() {
+	return disposed ;
+  }
+
+  /**
+   * Should be called when the {@link GeoMapPane} is not needed no more to help the GarbageCollector
+   *
+   *  @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void dispose() {
+    if (isDisposed())
+      return;
+    mapPane.dispose();
+    removeAll();
+    disposed = true;
+  }
+
+//  /**
+//   * Trying to reproduce a bug #37
+//   *
+//   *  for explanation and screenshots see the xulu trac
+//   *
+//   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+//   */
+//  public static void main(String[] args) throws Exception {
+//	JFrame f = new JFrame();
+//	f.pack();
+//	f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
+//	GeoMapPane gmp = new GeoMapPane();
+////	FeatureCollection shape = GeoImportUtil.readFeaturesFromShapeFile( new File("/home/stefan/Desktop/AtlasData/benin_karte_UTM31/admin_boundary_benin_utm.shp") );
+//        FeatureCollection shape = GeoImportUtil.readFeaturesFromShapeFile( new File("F:\\Arbeit\\Impetus - ZFL\\Daten\\Neuer Ordner\\admin_boundary_benin_utm.shp") );
+//        gmp.getMapContext().addLayer( shape, FeatureUtil.createDefaultStyle(shape) );
+//	f.add( gmp);
+//	gmp.getMapPane().setPreferredSize( new Dimension(400,400));
+//	gmp.refreshMap();
+//	f.pack();
+//	f.setVisible(true);
+//  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/GeoPositionLabel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/GeoPositionLabel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GeoPositionLabel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,217 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import javax.swing.JLabel;
+import javax.swing.event.MouseInputListener;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.awt.geom.AffineTransform;
+import java.text.DecimalFormat;
+
+import schmitzm.geotools.gui.JMapPane;
+
+import org.geotools.gui.swing.event.GeoMouseEvent;
+
+// nur fuer Doku
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+/**
+ * Diese Klasse stellt ein {@link JLabel} dar, in dem (2dimensionale) Geo-Koordinaten
+ * angezeigt werden. Dabei werden die Koordinaten auf eine bestimmte Anzahl
+ * an Nachkommastellen gerundet.<br>
+ * Die Klasse fungiert als {@link MouseListener} und {@link MouseMotionListener} und
+ * kann so direkt an ein {@link JMapPane} gekoppelt werden. Die Koordinaten-Darstellung
+ * im Label aktualisiert sich somit automatisch, sobald sich die Maus ueber die
+ * Karte bewegt. Wird ein Kartenbereich selektiert (gedrueckte linke Maustaste),
+ * werden neben der aktuellen Position auch die Koordinaten des Selektionsstart
+ * angezeigt.<br>
+ * <b>Bemerke:</b><br>
+ * Eine Instanz des <code>GeoPositionLabel</code> muss dem {@link JMapPane} sowohl
+ * als {@linkplain JMapPane#addMouseListener(MouseListener) MouseListener}, als
+ * auch als {@linkplain JMapPane#addMouseMotionListener(MouseMotionListener) MouseMotionListener}
+ * hinzugefuegt werden. Ansonsten bekommt es nicht alle notwendigen Informationen
+ * mit!
+ * @see #setFractionDigits(int)
+ * @see #getFractionDigits()
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeoPositionLabel extends JLabel implements MouseInputListener {
+
+  // Faktor mit dem die Koordinaten vor dem Runden multipliziert und nach
+  // dem Runden dividiert werden
+  // --> bestimmt die dargestellten Nachkommastellen
+  private double fracFactor = 0.0;
+
+  /** Speichert das Format fuer die dargstellten Koordinaten */
+  protected final DecimalFormat decForm = new DecimalFormat();
+
+  /** Speichert die Koordinaten, die in dem Moment aktuell sind, wenn die linke
+   *  Maustaste gedruckt wird. Und zwar solange, bis die Taste wieder losgelassen
+   *  wird. Danach wieder <code>null</code>. */
+  protected Point2D selStartCoord = null;
+
+  /**
+   * Erzeugt ein neues Label. Die Koordinaten werden ohne Nachkommastellen
+   * dargestellt.
+   */
+  public GeoPositionLabel() {
+    this(0);
+  }
+
+  /**
+   * Erzeugt ein neues Label.
+   * @param fracDigits Anzahl an Nachkommastellen, auf die die Koordinaten gerundet
+   *                   werden
+   */
+  public GeoPositionLabel(final int fracDigits) {
+    super(" ");
+    // eine Stelle vor dem Komma soll immer dargestellt werden
+    this.decForm.setMinimumIntegerDigits(1);
+    // Nachkommastellen setzen
+    this.setFractionDigits(fracDigits);
+  }
+
+  /**
+   * Stellt die Koordinaten im Label dar, wenn es sich bei dem Event um
+   * ein {@link GeoMouseEvent} handelt  oder das Event von einem {@link JMapPane}
+   * ausgeloest wurde.<br>
+   * Wird von {@link #mousePressed(MouseEvent)}, {@link #mouseMoved(MouseEvent)}
+   * und {@link #mouseDragged(MouseEvent)} aufgerufen.
+   * @see #createGeoPositionString(Point2D,Point2D)
+   */
+  protected void displayCoordinates(final MouseEvent e) {
+    if ( e==null || !(e instanceof GeoMouseEvent) && !(e.getSource() instanceof JMapPane) )
+      return;
+
+    // aktuelle Geo-Position ermitteln und runden
+    final Point2D actCoord = JMapPane.getMapCoordinatesFromEvent(e);
+    if ( actCoord == null )
+      return;
+    final double actLat = Math.round(actCoord.getX() * fracFactor) / fracFactor;
+    final double actLon = Math.round(actCoord.getY() * fracFactor) / fracFactor;
+    // Position des Selektion-Startpunkts runden
+    double startLat = 0.0;
+    double startLon = 0.0;
+    if (selStartCoord != null) {
+      startLat = Math.round(selStartCoord.getX() * fracFactor) / fracFactor;
+      startLon = Math.round(selStartCoord.getY() * fracFactor) / fracFactor;
+    }
+    // Label setzen
+    setText(createGeoPositionString(
+        new Point2D.Double(actLat, actLon),
+        selStartCoord == null ? null : new Point2D.Double(startLat, startLon)
+    ));
+  }
+
+  /**
+   * Erzeugt den String, in dem die Koordinaten dargestellt werden. Unterklassen
+   * koennen diese Methode ueberschreiben, um die Darstellung zu aendern.
+   * Die uebergebenen Koordinaten sind bereits entsprechend der gesetzten
+   * Nachkommastellen gerundet!
+   * @param actCoord aktuelle Geoposition des Mauszeigers
+   * @param selStartCoord Geoposition des Selektion-Startpunkt (<code>null</code>
+   *        falls die aktuell kein Bereich selektiert wird)
+   */
+  protected String createGeoPositionString(final Point2D actCoord, final Point2D selStartCoord) {
+    String coordText = "";
+    // wenn die linke Maustaste gedrueckt ist (also ein Bereich
+    // selektiert wird), sind in selStartCoord die Startkoordinaten
+    // gespeichert
+    if ( selStartCoord != null )
+      coordText = "("+decForm.format(selStartCoord.getX())+" / "+decForm.format(selStartCoord.getY())+") - ";
+    coordText = coordText + "("+decForm.format(actCoord.getX())+" / "+decForm.format(actCoord.getY())+")";
+
+    return coordText;
+  }
+
+  /**
+   * Setzt die Anzahl an Nachkommastellen, die fuer die Koordinaten
+   * dargestellt werden
+   * @param fracDigits Anzahl an Nachkommastellen
+   */
+  public void setFractionDigits(final int fracDigits) {
+    this.fracFactor = Math.pow(10.0,fracDigits);
+    this.decForm.setMinimumFractionDigits(fracDigits);
+    this.decForm.setMaximumFractionDigits(fracDigits);
+  }
+
+  /**
+   * Liefert die Anzahl an Nachkommastellen, die fuer die Koordinaten
+   * dargestellt werden
+   */
+  public int getFractionDigits() {
+    return (int)Math.log10(this.fracFactor);
+  }
+
+  /**
+   * Wird aufgerufen, wenn der Maus-Button gedrueckt wird.
+   * Handelt es sich bei dem Event um ein {@link GeoMouseEvent} oder das Event
+   * von einem {@link JMapPane} ausgeloest wurde, werden die
+   * aktuellen Koordinaten in {@link #selStartCoord} gespeichert.
+   */
+  public void mousePressed(final MouseEvent e) {
+    final Point2D p = JMapPane.getMapCoordinatesFromEvent(e);
+    if ( p != null && e.getButton() == MouseEvent.BUTTON1) {
+      selStartCoord = p;
+      displayCoordinates(e);
+    }
+  }
+
+  /**
+   * Wird aufgerufen, wenn der Maus-Button wieder losgelassen wird.
+   * Setzt {@link #selStartCoord} auf <code>null</code>.
+   */
+  public void mouseReleased(final MouseEvent e) {
+    selStartCoord = null;
+  }
+
+  /**
+   * Macht nichts.
+   */
+  public void mouseClicked(final MouseEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, sobald die Maus bewegt wird.
+   * Stellt die Koordinaten im Label dar, wenn es sich bei dem Event um ein
+   * {@link GeoMouseEvent} handelt oder das Event von einem {@link JMapPane}
+   * ausgeloest wurde.
+   */
+  public void mouseMoved(final MouseEvent e) {
+    displayCoordinates(e);
+  }
+
+  /**
+   * Wird aufgerufen, sobald die Maus bei gedrueckter Taste bewegt wird.
+   * Stellt die Koordinaten im Label dar, wenn es sich bei dem Event um ein
+   * {@link GeoMouseEvent} handelt oder das Event von einem {@link JMapPane}
+   * ausgeloest wurde.
+   */
+  public void mouseDragged(final MouseEvent e) {
+    displayCoordinates(e);
+  }
+
+  /**
+   * Macht nichts.
+   */
+  public void mouseEntered(final MouseEvent e) {
+  }
+
+  /**
+   * Macht nichts.
+   */
+  public void mouseExited(final MouseEvent e) {
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/GeotoolsGUIUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/GeotoolsGUIUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GeotoolsGUIUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,227 @@
+package schmitzm.geotools.gui;
+
+import java.awt.Component;
+import java.io.File;
+import java.net.URL;
+import java.util.Locale;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileNameExtensionFilter;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.feature.FeatureCollection;
+
+import schmitzm.geotools.io.GeoExportUtil;
+import schmitzm.io.IOUtil;
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.ResourceProvider;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.SwingUtil;
+
+/**
+ * Diese Klasse enthaelt statische Hilfsmethoden im Bereich Geotools GUI.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeotoolsGUIUtil {
+  private static Logger LOGGER = Logger.getLogger( GeotoolsGUIUtil.class.getName() );
+
+  /** {@link ResourceProvider}, der die Lokalisation fuer GUI-Komponenten
+   *  des Package {@code schmitzm.geotools.gui} zur Verfuegung stellt. Diese sind
+   *  in properties-Datein unter {@code schmitzm.geotools.gui.resource.locales}
+   *  hinterlegt. */
+  public static ResourceProvider RESOURCE = new ResourceProvider( LangUtil.extendPackagePath(GeotoolsGUIUtil.class,"resource.locales.GTResourceBundle"), Locale.ENGLISH );
+
+  /** {@link FileNameExtensionFilter} fuer ArcInfoAscii-Raster. */
+  public static final FileNameExtensionFilter FILTER_RASTER_ASCII = new FileNameExtensionFilter("ASCII-Raster (*.asc)","asc");
+  /** {@link FileNameExtensionFilter} fuer GeoTiff-Raster. */
+  public static final FileNameExtensionFilter FILTER_RASTER_GEOTIFF = new FileNameExtensionFilter("GeoTiff (*.tif)","tif");
+  /** {@link FileNameExtensionFilter} fuer Shape-Files. */
+  public static final FileNameExtensionFilter FILTER_FEATURE_SHAPE = new FileNameExtensionFilter("Shape-File (*.shp)","shp");
+  
+  /**
+   * Zeigt einen Dialog zum Auswaehlen einer Datei an.
+   * @param parent dem Dialog uebergeordnete GUI-Komponente
+   * @param dialogType {@code JFileChooser.SAVE_DIALOG} oder {@code JFileChooser.OPEN_DIALOG}
+   * @param title Titel, der im Dialog angezeigt wird (kann {@code null} sein)
+   * @param initialDir Verzeichnis, das angezeigt wird (kann {@code null} sein)
+   * @param filter (optionale) Filter, die im Dialog angewaehlt werden koennen
+   * @return den FileChooser, in dem die Datei ausgewaehlt ist oder {@code null}
+   *         falls der Dialog abgebrochen wurde
+   */
+  private static JFileChooser chooseFile(Component parent, int dialogType, String title, File initialDir, FileFilter... filter) {
+    final JFileChooser fileChooser = new JFileChooser(initialDir);
+    fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    fileChooser.setMultiSelectionEnabled( false );
+    for (FileFilter f : filter)
+      fileChooser.addChoosableFileFilter(f);
+    if ( filter.length > 0 ) {
+      fileChooser.setFileFilter(filter[0]);
+      fileChooser.setAcceptAllFileFilterUsed(false);
+    }
+    if ( title != null )
+      fileChooser.setDialogTitle(title);
+    int retValue = 0;
+    if ( dialogType == JFileChooser.SAVE_DIALOG )
+      retValue = fileChooser.showSaveDialog(parent);
+    else if ( dialogType == JFileChooser.OPEN_DIALOG )
+      retValue = fileChooser.showOpenDialog(parent);
+    else
+      throw new IllegalArgumentException("Unsupported dialog type: "+dialogType);
+
+    if ( retValue != JFileChooser.APPROVE_OPTION )
+      return null;
+
+    return fileChooser;
+  }
+
+  /**
+   * Zeigt einen Dialog zum Auswaehlen einer Export-Datei an. Die
+   * Datei-Erweiterung wird entsprechend des ausgewaehlten Filters
+   * automatisch angehaengt. Existiert die Datei bereits, wird zum Bestaetigen#
+   * des Ueberschreibens aufgefordert.
+   * @param parent dem Dialog uebergeordnete GUI-Komponente
+   * @param dialogType {@code JFileChooser.SAVE_DIALOG} oder {@code JFileChooser.OPEN_DIALOG}
+   * @param title Titel, der im Dialog angezeigt wird (kann {@code null} sein)
+   * @param initialDir Verzeichnis, das angezeigt wird (kann {@code null} sein)
+   * @param filter (optionale) Filter, die im Dialog angewaehlt werden koennen
+   * @return den FileChooser, in dem die Datei ausgewaehlt ist oder {@code null}
+   *         falls der Dialog abgebrochen wurde
+   */
+  private static JFileChooser chooseExportFile(Component parent, String title, File initialDir, FileNameExtensionFilter... filter) {
+    JFileChooser expFileChooser = null;
+    do {
+      // Export-Datei auswaehlen
+      expFileChooser = chooseFile(
+        parent,
+        JFileChooser.SAVE_DIALOG,
+        title,
+        initialDir,
+        filter
+      );
+      if ( expFileChooser == null )
+        return null;
+      // Verzeichnis merken
+      initialDir = expFileChooser.getCurrentDirectory();
+
+      // Ueberschreiben bestaetigen
+      File   expFile    = expFileChooser.getSelectedFile();
+      String expFileExt = ((FileNameExtensionFilter)expFileChooser.getFileFilter()).getExtensions()[0];
+      expFile = IOUtil.appendFileExt(expFile, expFileExt);
+      expFileChooser.setSelectedFile(expFile);
+
+      if ( !expFile.exists() || approveFileOverwrite(parent,expFile.getName()) )
+        break;
+    } while ( true );
+
+    return expFileChooser;
+
+  }
+  /**
+   * Zeigt einen Dialog an, in dem das Ueberschreiben einer Datei bestaetigt
+   * werden muss.
+   * @param parent Uebergeordnete GUI-Komponente fuer die Meldung
+   * @param filename Bezeichnung der Datei
+   * @return {@code true}, falls das Ueberschreiben mit OK bestaetigt wurde
+   */
+  public static boolean approveFileOverwrite(Component parent, String filename) {
+    int retValue = JOptionPane.showConfirmDialog(
+      parent,
+      filename,
+      SwingUtil.RESOURCE.getString("Overwrite"),
+      JOptionPane.OK_CANCEL_OPTION,
+      JOptionPane.QUESTION_MESSAGE
+    );
+    return retValue == JOptionPane.OK_OPTION;
+  }
+
+  /**
+   * Exportiert eine {@link FeatureCollection} in ein Shape-File. Dabei
+   * wird ein Anwender-Dialog geoeffnet, um die Ziel-Datei auszuwaehlen.
+   * Tritt ein Fehler beim Export auf, wird dieser in einem Dialog angezeigt.
+   * @param parent dem Dialog uebergeordnete GUI-Komponente (kann {@code null} sein)
+   * @param fc zu exportierende {@link FeatureCollection}
+   * @param desc Beschreibung der FeatureCollection, die im Dialog-Titel angezeigt
+   *             wird (kann {@code null} sein)
+   * @return die Export-Datei, oder {@code null} falls der Export abgebrochen
+   *         oder aufgrund eines Fehlers nicht durchgefuehrt wurde
+   */
+  public static URL exportFeatureCollection(Component parent, FeatureCollection fc, String desc) {
+    String title = RESOURCE.getString("schmitzm.geotools.gui.GeotoolsGUIUtil.SaveFeature");
+    if ( desc != null && !desc.trim().equals("") )
+      title += " [" + desc.trim() + "]";
+
+    // Export-Datei auswaehlen
+    JFileChooser destFileChooser = chooseExportFile(
+      parent,
+      title,
+      null,
+      FILTER_FEATURE_SHAPE
+    );
+    if ( destFileChooser == null )
+      return null;
+    File destFile = destFileChooser.getSelectedFile();
+
+    // FeatureCollection exportieren, je nach angewaehltem Format
+    try {
+      FileFilter fileFilter = destFileChooser.getFileFilter();
+      if ( fileFilter == FILTER_FEATURE_SHAPE )
+        GeoExportUtil.writeFeaturesToShapeFile(fc, destFile);
+      else
+        throw new UnsupportedOperationException("Chosen file format not supported: "+fileFilter.getDescription());
+      return destFile.toURI().toURL();
+    } catch (Exception err) {
+      ExceptionDialog.show(parent,err);
+      return null;
+    }
+  }
+
+  /**
+   * Exportiert ein {@link GridCoverage2D} in eine Raster-Datei. Dabei
+   * wird ein Anwender-Dialog geoeffnet, um die Ziel-Datei und den Datei-Typ
+   * auszuwaehlen.
+   * Tritt ein Fehler beim Export auf, wird dieser in einem Dialog angezeigt.
+   * @param parent dem Dialog uebergeordnete GUI-Komponente (kann {@code null} sein)
+   * @param gc zu exportierendes Raster
+   * @param desc Beschreibung des Rasters, die im Dialog-Titel angezeigt
+   *             wird (kann {@code null} sein)
+   * @return die Export-Datei, oder {@code null} falls der Export abgebrochen
+   *         oder aufgrund eines Fehlers nicht durchgefuehrt wurde
+   */
+  public static URL exportGridCoverage2D(Component parent, GridCoverage2D gc, String desc) {
+    String title = RESOURCE.getString("schmitzm.geotools.gui.GeotoolsGUIUtil.SaveRaster");
+    if ( desc != null && !desc.trim().equals("") )
+      title += " [" + desc.trim() + "]";
+    
+    // Export-Datei auswaehlen
+    JFileChooser destFileChooser = chooseExportFile(
+      parent,
+      title,
+      null,
+      FILTER_RASTER_ASCII,
+      FILTER_RASTER_GEOTIFF
+    );
+    if ( destFileChooser == null )
+      return null;
+    File destFile = destFileChooser.getSelectedFile();
+
+    // Raster exportieren, je nach angewaehltem Format
+    try {
+      FileFilter fileFilter = destFileChooser.getFileFilter();
+      if ( fileFilter == FILTER_RASTER_ASCII )
+        GeoExportUtil.writeGridToArcInfoASCII(gc, destFile);
+      else if ( fileFilter == FILTER_RASTER_GEOTIFF )
+        GeoExportUtil.writeGridToGeoTiff(gc, destFile);
+      else
+        throw new UnsupportedOperationException("Chosen file format not supported: "+fileFilter.getDescription());
+      return destFile.toURI().toURL();
+    } catch (Exception err) {
+      ExceptionDialog.show(parent,err);
+      return null;
+    }
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/GridPanel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/GridPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/GridPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,370 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import javax.swing.JLabel;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Color;
+import java.text.DecimalFormat;
+import java.util.Locale;
+
+import org.geotools.measure.CoordinateFormat;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.geometry.DirectPosition2D;
+import org.geotools.geometry.GeneralDirectPosition;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.operation.transform.GeocentricTranslation;
+import org.geotools.referencing.operation.transform.GeocentricTransform;
+import org.geotools.referencing.operation.projection.PointOutsideEnvelopeException;
+
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.grid.GridUtil;
+import schmitzm.swing.JPanel;
+import schmitzm.swing.SwingUtil;
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.ResourceProvider;
+
+import org.apache.log4j.Logger;
+import java.awt.geom.AffineTransform;
+
+/**
+ * Diese Klasse stellt eine horizontale oder vertikale Koordinaten-Leiste (Grid)
+ * dar, die an ein {@link JMapPane} gekoppelt ist.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GridPanel extends JPanel {
+  private final Logger LOGGER = LangUtil.createLogger(this);
+  private static final ResourceProvider RES = GeotoolsGUIUtil.RESOURCE;
+
+  private static final DecimalFormat numFormat = new DecimalFormat("###,###,##0");
+
+  /** Laenge der Grid-Abschnitts-Linien.  */
+  private static final int GRID_LINE_SIZE = 10;
+  /** Container-Breite bei vertikalem Grid. */
+  public static final int VERT_WIDTH     = 50;
+  /** Container-Hoehe bei horizontalem Grid. */
+  public static final int HOR_HEIGHT     = 20;
+
+  /** Flag fuer ein horizontales Grid. */
+  public static final int HORIZONTAL = 1;
+  /** Flag fuer ein vertikales Grid. */
+  public static final int VERTICAL = 2;
+
+  /** Speichert die Orientierung des Grid (Horizontal oder Vertikal). */
+  private int orientation = 0;
+
+  /** Karte, an der die Koordinaten-Leiste ausgerichtet wird. */
+  protected JMapPane mapPane = null;
+
+  /** Transformation von Karten-CRS zu CRS des Koordinaten-Rasters */
+  protected MathTransform mapToGrid = null;
+  /** Transformation von CRS des Koordinaten-Rasters zu Karten-CRS*/
+  protected MathTransform gridToMap = null;
+  /** Speichert das CRS, fuer das die Transformationen erstellt wurden. */
+  private CoordinateReferenceSystem transformedMapCRS = null;
+
+  /**
+   * Erzeugt eine Koordinaten-Leiste fuer ein {@link JMapPane}.
+   * @param orientation Orientierung ({@link #HORIZONTAL} oder {@link #VERTICAL})
+   * @param mapPane     Karte an der die Koordinaten-Leiste ausgerichtet wird
+   */
+  public GridPanel(int orientation, JMapPane mapPane) {
+    super();
+
+    this.orientation = orientation;
+    this.mapPane     = mapPane;
+    if ( isVertical() )
+      SwingUtil.setPreferredWidth(this,VERT_WIDTH);
+    else
+      SwingUtil.setPreferredHeight(this,HOR_HEIGHT);
+  }
+
+  /**
+   * Liefert die Orientierung der Koordinaten-Leiste.
+   * @see #HORIZONTAL
+   * @see #VERTICAL
+   */
+  public int getOrientation() {
+    return orientation;
+  }
+
+  /**
+   * Prueft, ob es sich um eine horizontale Koordinaten-Leiste handelt.
+   */
+  public boolean isHorizontal() {
+    return this.orientation == HORIZONTAL;
+  }
+
+  /**
+   * Prueft, ob es sich um eine vertikale Koordinaten-Leiste handelt.
+   */
+  public boolean isVertical() {
+    return this.orientation == VERTICAL;
+  }
+
+  /**
+   * Zeichnet die Koordinaten-Leiste.
+   * @param g Graphics
+   */
+  public void paint(Graphics g) {
+    super.paint(g);
+    if ( getParent() == null || mapPane.getMapArea() == null )
+      return;
+
+    CoordinateReferenceSystem mapCRS = null;   // (aktuelles) CRS des MapPane
+    CoordinateReferenceSystem gridCRS = null;  // CRS fuer die Grad/Min/Sek-Anzeige
+//    try {
+//      final CoordinateReferenceSystem UTM   = CRS.decode("EPSG:32631"); // utm zone 31n
+//      final CoordinateReferenceSystem WGS84 = CRS.decode("EPSG:4326"); // lat/lon (WGS84)
+////      final CoordinateReferenceSystem WGS84 = org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;// lat/lon
+////      final CoordinateReferenceSystem NAD83 = CRS.decode("EPSG:100002"); // lat/lon
+////      mapCRS  = mapPane.getContext().getCoordinateReferenceSystem();
+//      mapCRS = UTM;
+//      gridCRS = WGS84;
+//    } catch (Exception err) {
+//      return;
+//    }
+
+
+    mapCRS  = mapPane.getContext().getCoordinateReferenceSystem();
+    gridCRS = org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;// lat/lon
+//    LOGGER.debug("mapCRS: "+mapCRS);
+//    LOGGER.debug("gridCRS: "+gridCRS);
+
+    double     minX   = mapPane.getMapArea().getMinX();
+    double     minY   = mapPane.getMapArea().getMinY();
+    double     maxX   = mapPane.getMapArea().getMaxX();
+    double     maxY   = mapPane.getMapArea().getMaxY();
+
+// commented out, because it was unused
+//    double     width  = mapPane.getMapArea().getWidth();
+//    double     height = mapPane.getMapArea().getHeight();
+
+    // Minimum/Maximum des Grids in MapPane-CRS (MCRS)
+    double[] mapMin_MCRS = new double[] {minX,minY};
+    double[] mapMax_MCRS = isVertical() ? new double[] {minX,maxY} : new double[] {maxX,minY};
+    // Minimum/Maximum des Grids in Grid-CRS (GCRS)
+    double[] mapMin_GCRS = new double[2];
+    double[] mapMax_GCRS = new double[2];
+
+    try {
+      try {
+        // Der Effizienz halber, die Transformation nur neu ermitteln
+        // wenn sich das Karten-CRS geaendert hat
+        if ( mapToGrid == null || gridToMap == null || transformedMapCRS == null || !transformedMapCRS.equals(mapCRS) ) {
+          mapToGrid = CRS.findMathTransform(mapCRS, gridCRS);
+          gridToMap = CRS.findMathTransform(gridCRS, mapCRS);
+          // CRS merken, fuer das Transformation erstellt wurde
+          transformedMapCRS = mapCRS;
+        }
+      } catch (FactoryException  err) {
+        LOGGER.warn("CRS-Transformation to WGS84 failed: "+err.getMessage());
+        mapToGrid = CRS.findMathTransform(mapCRS, gridCRS, true);
+        gridToMap = CRS.findMathTransform(gridCRS, mapCRS, true);
+        LOGGER.warn("CRS-Transformation without datum shift");
+        // CRS merken, fuer das Transformation erstellt wurde
+        transformedMapCRS = mapCRS;
+      }
+      mapToGrid.transform( mapMin_MCRS, 0, mapMin_GCRS, 0, 1  );
+      mapToGrid.transform( mapMax_MCRS, 0, mapMax_GCRS, 0, 1  );
+    } catch (PointOutsideEnvelopeException err) {
+      return;
+    } catch (Exception err) {
+      LOGGER.error("CRS-Transformation to WGS84 failed: "+err.getMessage());
+      LOGGER.debug("CRS-Transformation to WGS84 failed: "+err.getMessage(),err);
+    }
+
+//   // Ergebnis von Transform: {Latitude,Longitude}
+//   int idx = isVertical() ? 0 : 1;
+   // SEHR SEHR MERKWUERDIG: Mal liefert Transform {Latitude,Longitude}, und
+   //                        manchmal {Longitude,Latitude}!!
+   // WORKAROUND (klappt natuerlich nicht mit allen CRS!):
+   //       Wenn  X_MCRS > Y_MCRS, dann muss auch X_GCRS > Y_GCRS
+  int idx = determineCompatibleArrayIndex(mapMin_MCRS,mapMin_GCRS,mapMax_MCRS,mapMax_GCRS);
+//   LOGGER.debug( isVertical() ? "V" : "H" );
+//   LOGGER.debug( "Min MCRS: "+mapMin_MCRS[0]+", "+mapMin_MCRS[1]+"         GCRS:"+mapMin_GCRS[0]+", "+mapMin_GCRS[1]);
+//   LOGGER.debug( "Max MCRS: "+mapMax_MCRS[0]+", "+mapMax_MCRS[1]+"         GCRS:"+mapMax_GCRS[0]+", "+mapMax_GCRS[1]);
+//   LOGGER.debug( "  --> "+idx+"\n");
+
+   double mapMin = mapMin_GCRS[idx];
+   double mapMax = mapMax_GCRS[idx];
+   // Maximale Groesse des Grid in Pixel
+   int maxSize = isHorizontal() ? getParent().getWidth() : getParent().getHeight();
+
+   // Passende Grid-Unterteilung in Grad
+   double gridDist = determineGridDistance(mapMin,mapMax,maxSize);
+   // Leiste zeichnen
+   if ( isHorizontal() )
+     g.drawLine(0,0,maxSize,0);
+   else
+     g.drawLine(VERT_WIDTH-1,0,VERT_WIDTH-1,maxSize);
+
+   // Abschnitte zeichnen
+   double   firstGrid = Math.ceil(mapMin/gridDist)*gridDist; // 1. Abschnitt in Lat bzw. Lon
+   double[] grid_GCRS = mapMin_GCRS.clone(); // Ausgangspunkt
+   double[] grid_MCRS = new double[2];
+   AffineTransform mapToWin = mapPane.getTransform();
+   for (double gridPos=firstGrid; gridPos<=mapMax; gridPos+=gridDist) {
+     grid_GCRS[idx] = gridPos;
+     // In Map-CRS und dann in Fenster-Koordinaten transformieren
+     double[] grid_WINDOW = new double[2];
+     try {
+       gridToMap.transform(grid_GCRS, 0, grid_MCRS, 0, 1);
+       mapToWin.inverseTransform(grid_MCRS,0,grid_WINDOW,0,1);
+//     } catch (Exception err) {
+     } catch (Throwable err) {
+       LOGGER.warn("Error ignored during paint of coordinate grid: "+err);
+       continue;
+     }
+     // Grid-Abschnitt zeichnen
+     int x1 = isVertical() ? VERT_WIDTH-GRID_LINE_SIZE : (int)grid_WINDOW[0];
+     int y1 = isVertical() ? (int)grid_WINDOW[1] : 0;
+     int x2 = isVertical() ? x1+GRID_LINE_SIZE : x1;
+     int y2 = isVertical() ? y1 : y1+GRID_LINE_SIZE;
+     g.drawLine(x1,y1,x2,y2);
+
+     // Koordinate anzeigen
+     double coord = grid_GCRS[idx];
+     int degrees = (int)coord;
+     int minutes = (int)Math.round((coord-degrees)*60 );
+     // Aufgrund des Minuten-Systems kann schlecht im Vorhinein gerundet
+     // werden. Deshalb wird im Nachhinein ein etwaiger Rundungsfehler
+     // ausgeglichen
+     if ( Math.abs(minutes) >= 60 ) {
+       degrees += Math.signum(minutes);
+       minutes -= Math.signum(minutes) * 60;
+     }
+     String coordStr = Math.abs(degrees)+"°"+Math.abs(minutes);
+     if ( degrees != 0 || minutes != 0 ) {
+       if (isVertical())
+         coordStr += RES.getString( (coord < 0) ? "schmitzm.geotools.gui.GeotoolsGUIUtil.SOUTH.Abb" : "schmitzm.geotools.gui.GeotoolsGUIUtil.NORTH.Abb" );
+       else
+         coordStr += RES.getString( (coord < 0) ? "schmitzm.geotools.gui.GeotoolsGUIUtil.WEST.Abb" : "schmitzm.geotools.gui.GeotoolsGUIUtil.EAST.Abb" );
+     }
+     x1 = isVertical() ?    5 : x1 + 5;
+     y1 = isVertical() ? y1-2 : y1 + 12;
+     g.drawString( coordStr, x1, y1 );
+   }
+
+/////////////   Test und Debugging /////////////////
+//    System.out.println("2 UTM lat="+minY+" lon="+minX+"       --> Degree lat="+dest2[0]+" lon="+dest2[1]);
+
+//    org.geotools.referencing.operation.DefaultMathTransformFactory.main(new String[] {});
+//    if ( debug == 0) {
+//      schmitzm.lang.WorkingThread w = new schmitzm.lang.WorkingThread() {
+//        java.util.Set codes = null;
+//        public void performInit() {
+//          codes = CRS.getSupportedCodes("EPSG");
+//        }
+//        public void performWork() {
+//          for (Object code : codes) {
+//            String crsCode = ((String)code).startsWith("EPSG") ? (String)code : "EPSG:"+((String)code);
+//            try {
+//              CoordinateReferenceSystem crs = CRS.decode(crsCode);
+//              System.out.println(code+"\t"+crsCode+"\t"+crs.getName());
+//            } catch (Exception e) {
+//              System.out.println(code+"\t"+crsCode+"\tError");
+//              break;
+//            }
+//          }
+//        }
+//        public void performDispose() {
+//        }
+//      };
+//      debug = 1;
+//      w.start();
+//    }
+  }
+
+  /**
+   * Bestimmt eine "passende" Unterteilung fuer das Grid.
+   */
+  private static double determineGridDistance(double mapMin_LL, double mapMax_LL, int panelSize) {
+    // Differenz zwischen Min/Max-Koordinaten = darzustellender Bereich in Grad
+    double diff = Math.abs( mapMin_LL - mapMax_LL );
+    // Distanz zwischen zwei Unterteilungen in Grad
+    double dist = 0.0;
+    if ( diff < 10/60.0 )      // < 10 Min. -->  1 Min.-Aufteilung
+      dist = 1/60.0;
+    else if ( diff < 30/60.0 ) // < 30 Min. -->  5 Min.-Aufteilung
+      dist = 5/60.0;
+    else if ( diff <  1 )      // <  1?     --> 10 Min.-Aufteilung
+      dist = 10/60.0;
+    else if ( diff <  3 )      // <  3?     --> 15 Min.-Aufteilung
+      dist = 15/60.0;
+    else if ( diff <  5 )      // <  5?     --> 30 Min.-Aufteilung
+      dist = 30/60.0;
+    else if ( diff < 11 )      // < 11?     -->  1?-Aufteilung
+      dist = 1;
+    else                       // sonst     -->  2?-Aufteilung
+      dist = 2;
+
+    // Solange der Abstand zwischen 2 Unterteilungen weniger als 40 Pixel
+    // betraegt --> Unterteilung verdoppeln
+    while ( diff != 0 && dist != 0 && panelSize / (diff / dist) < 40 )
+      dist *=2;
+
+    return dist;
+  }
+
+//  /**
+//   * Diese Methode stellt einen Workaround fuer einen sehr sehr merkwuerdigen
+//   * {@link MathTransform}-Effekt dar. Mal liefert Transform die Koordinaten
+//   * als {Latitude,Longitude}-Array, und manchmal als {Longitude,Latitude}!
+//   * Diese Methode versucht zu ermitteln, in welchem Array-Index die
+//   * benoetigte Koordinate (je nachdem ob es sich um ein horizontales oder
+//   * vertikales Grid handelt) tatsaechlich befindet.
+//   * @param coord_SCRS Koordinate (in Quell-CRS)
+//   * @param coord_DCRS transformierte Koordinate (in Ziel-CRS)
+//   */
+//  private int determineCompatibleArrayIndex(double[] coord_SCRS, double[] coord_DCRS) {
+//    // SEHR SEHR MERKWUERDIG: Mal liefert Transform {Latitude,Longitude}, und
+//    //                        manchmal {Longitude,Latitude}!!
+//    // WORKAROUND (klappt natuerlich nicht mit allen CRS!):
+//    //       Wenn  X_sourceCRS > Y_sourceCRS, dann muss auch X_destCRS > Y_destCRS
+//    int idx = 0;
+//    if (coord_SCRS[0] > coord_SCRS[1] && coord_DCRS[0] > coord_DCRS[1] ||
+//        coord_SCRS[0] < coord_SCRS[1] && coord_DCRS[0] < coord_DCRS[1] ||
+//        coord_SCRS[0] == coord_SCRS[1] && coord_DCRS[0] == coord_DCRS[1])
+//      idx = isHorizontal() ? 0 : 1;
+//    else
+//      idx = isHorizontal() ? 1 : 0;
+//    return idx;
+//  }
+
+  /**
+   * Diese Methode stellt einen Workaround fuer einen sehr sehr merkwuerdigen
+   * {@link MathTransform}-Effekt dar. Mal liefert Transform die Koordinaten
+   * als {Latitude,Longitude}-Array, und manchmal als {Longitude,Latitude}!
+   * Diese Methode versucht zu ermitteln, in welchem Array-Index die
+   * benoetigte Koordinate (je nachdem ob es sich um ein horizontales oder
+   * vertikales Grid handelt) tatsaechlich befindet.
+   */
+  private int determineCompatibleArrayIndex(double[] mapMin_MCRS, double[] mapMin_GCRS,double[] mapMax_MCRS, double[] mapMax_GCRS) {
+    // SEHR SEHR MERKWUERDIG: Mal liefert Transform {Latitude,Longitude}, und
+    //                        manchmal {Longitude,Latitude}!!
+    // WORKAROUND (klappt natuerlich nicht mit allen CRS!):
+    //       Wenn  X_sourceCRS > Y_sourceCRS, dann muss auch X_destCRS > Y_destCRS
+
+
+    double diff0_GCRS = mapMax_GCRS[0] - mapMin_GCRS[0];
+    double diff1_GCRS = mapMax_GCRS[1] - mapMin_GCRS[1];
+
+    return diff0_GCRS > diff1_GCRS ? 0 : 1;
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/JEditorPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/JEditorPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/JEditorPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,932 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.DefaultFeatureCollection;
+import org.geotools.feature.DefaultFeatureCollections;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.FeatureTypeBuilder;
+import org.geotools.feature.GeometryAttributeType;
+import org.geotools.feature.SchemaException;
+import org.geotools.feature.type.GeometricAttributeType;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.DefaultMapLayer;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.styling.Style;
+
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.feature.FeatureUtil.GeometryForm;
+import schmitzm.geotools.map.event.FeatureModifiedEvent;
+import schmitzm.geotools.map.event.LayerEditCanceledEvent;
+import schmitzm.geotools.map.event.LayerEditFinishedEvent;
+import schmitzm.geotools.map.event.LayerEditStartedEvent;
+import schmitzm.geotools.map.event.MapContextSynchronizer;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.MultipleOptionPane;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.LinearRing;
+import com.vividsolutions.jts.geom.MultiPoint;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+
+/**
+ * The {@code GeoEditorPane} extends the {@link JMapPane} with functionalities
+ * to create new vector layers by successively click points via mouse.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class JEditorPane extends JMapPane {
+  /** Modes, the editor can be perform.
+   *  @see JEditorPane#startEditing(EditorMode, String, Style) */
+  public static enum EditorMode {
+    /** Create a new point layer. */
+    New_Point,
+    /** Create a new linestring layer. */
+    New_Line,
+    /** Create a new polygon layer. */
+    New_Polygon
+  }
+
+  /** Attribute name used for the geometry of new layers. */
+  public static final String GEOMETRY_ATTR = "the_geom";
+
+  /** Default-Style for point layers (blue dots). */
+  public static final Style DEFAULT_POINT_STYLE = FeatureUtil.createPointStyle(Color.BLUE);
+  /** Default-Style for line layers (blue lines). */
+  public static final Style DEFAULT_LINE_STYLE = FeatureUtil.createLineStyle(Color.BLUE,2);
+  /** Default-Style for polygon layers (orange with black borders). */
+  public static final Style DEFAULT_POLYGON_STYLE = FeatureUtil.createPolygonStyle(Color.ORANGE, Color.BLUE, 2);
+
+  /** If the edited layer is empty, a dummy feature is inserted, because
+   *  the StreamingRenderer can not handle empty FeatureCollections yet. */
+  private Feature DUMMY_EDITOR_FEATURE = null;
+  private Feature DUMMY_LINE_FEATURE = null;
+  private Feature DUMMY_POINT_FEATURE = null;
+
+  /** The map context of the displayed layer. */
+  protected MapContext mapContext = null;
+
+  /** Contains the additional attributes for new FeatureCollections. */
+  protected FeatureType additionalAttr = null;
+  /** Contains the {@link InputOption} to specify the additional
+   *  attribute values.  */
+  protected FeatureInputOption attrInputOption = null;
+
+  /** The map context where the edited layers are displayed. This
+   *  context is displayed on top of the other layers. */
+  protected MapContext editorMapContext = null;
+  /** The renderer the edited layers are rendered with. */
+  protected GTRenderer editorRenderer = null;
+  /** Holds the {@link Style styles} to display edited layers. */
+  protected Map<GeometryForm,Style> editorStyles = null;
+  /** Holds the operation the editor currently performs. */
+  protected EditorMode editorMode = null;
+
+  /** Holds the {@link FeatureType} of the edited layer. */
+  protected FeatureType editorFeatureType = null;
+  /** Holds the kind of geometry of the edited layer. */
+  protected GeometryForm editorGeometryForm = null;
+  /** Holds the {@link FeatureCollection} of the edited layer. */
+  protected DefaultFeatureCollection editorFeatureCollection = null;
+  /** Holds the edited layer. */
+  protected MapLayer editorLayer = null;
+
+  /** Holds the {@link FeatureType} of the new segment (incomplete Feature)
+   *  displayed as line. */
+  protected FeatureType segmLineFeatureType = null;
+  /** Holds the {@link FeatureCollection} which holds the new segment
+   *  (incomplete Feature) displayed as line. */
+  protected DefaultFeatureCollection segmLineFeatureCollection = null;
+  /** Holds the layer, the new segment (incomplete Feature) is displayed in
+   *  as line. */
+  protected MapLayer segmLineLayer = null;
+
+  /** Holds the {@link FeatureType} of the new segment (incomplete Feature)
+   *  displayed as points. */
+  protected FeatureType segmPointFeatureType = null;
+  /** Holds the {@link FeatureCollection} which holds the new segment
+   *  (incomplete Feature) displayed as points. */
+  protected DefaultFeatureCollection segmPointFeatureCollection = null;
+  /** Holds the layer, the new segment (incomplete Feature) is displayed in
+   *  as points. */
+  protected MapLayer segmPointLayer = null;
+
+
+  /** Holds the points of the edited segment (incomplete Feature) which
+   *  can be undone. */
+  protected Stack<Coordinate> segmUndoPoints = new Stack<Coordinate>();
+  /** Holds the undone points of the edited segment (incomplete Feature) which
+   *  can be redone. */
+  protected Stack<Coordinate> segmRedoPoints = new Stack<Coordinate>();
+  /** Holds the points of former segments (complete Features) which
+   *  can be undone. */
+  protected Stack<Stack<Coordinate>> globalUndoPoints = new Stack<Stack<Coordinate>>();
+  /** Holds the undone points of former segments (complete Features) which
+   *  can be redone. */
+  protected Stack<Stack<Coordinate>> globalRedoPoints = new Stack<Stack<Coordinate>>();
+  /** Holds the former segments (complete Features) which can be undone. */
+  protected Stack<Feature> globalUndoFeatures = new Stack<Feature>();
+  /** Holds the undone segments (complete Features) which can be redone. */
+  protected Stack<Feature> globalRedoFeatures = new Stack<Feature>();
+
+  /**
+   * Creates a new {@code GeoEditorPane}.
+   */
+  public JEditorPane() {
+    this( new BorderLayout(),
+          true,
+          new StreamingRenderer(),
+          new DefaultMapContext(DefaultGeographicCRS.WGS84)
+    );
+  }
+
+  /**
+   * Creates a new {@code GeoEditorPane}. This constructor provides the possibility
+   * to use alternative implementations of the internal components.
+   * <b>The specified parameter all can be {@code null}! In this case
+   * the respective default component is used.</b>
+   */
+  public JEditorPane(LayoutManager layout, boolean isDoubleBuffered, GTRenderer renderer, MapContext context) {
+    super(layout,isDoubleBuffered,renderer,context);
+    this.mapContext = getContext();
+    // initalize the editor styles
+    this.editorStyles = new HashMap<GeometryForm, Style>();
+    for (GeometryForm geomForm : GeometryForm.values())
+      this.setEditorStyle(geomForm, null);
+
+    // no standard action on left mouse button, instead: adding points
+    setState( NONE );
+    setWindowSelectionState( ZOOM_IN );
+    // special map context for the editor layers, so the
+    // editor layers are not shown in "normal" layer list
+    this.editorRenderer   = new StreamingRenderer();
+    this.editorMapContext = new DefaultMapContext( mapContext.getCoordinateReferenceSystem() );
+    // listen to CRS/Area changes on the "normal" Context to
+    // synchronize the editor context
+    this.mapContext.addMapBoundsListener( new MapContextSynchronizer(editorMapContext) );
+
+    // create the input option to specify additional attribute values
+    attrInputOption = new FeatureInputOption(null,true,(Feature)null);
+  }
+
+  /**
+   * After the actions of the super method, this method paints the
+   * special editor layers in {@link #editorMapContext}.
+   */
+  protected void paintComponent(Graphics g) {
+    resetEditorLayerVisibility();
+    super.paintComponent(g);
+    Rectangle r = getBounds();
+    Rectangle dr = new Rectangle(r.width, r.height);
+    editorRenderer.setContext(editorMapContext);
+    editorRenderer.paint((Graphics2D) g, dr, mapArea);
+  }
+
+  /**
+   * Ignores the actions of the super class for left-clicks.
+   * Instead this method reacts according to the current
+   * editor mode.
+   */
+  public void mouseClicked(MouseEvent e) {
+    Point2D geoCoord = getTransform().transform(e.getPoint(), null);
+    // Zunaechst Modus auf NULL pruefen, da ansonsten
+    // NullPointerException in switch-Statement
+    if ( editorMode == null )
+      return;
+
+    switch( editorMode ) {
+      case New_Point:
+      case New_Line:
+      case New_Polygon: addSegment( new Coordinate(geoCoord.getX(), geoCoord.getY()) );
+                        break;
+    }
+  }
+
+  //**********************************************************************
+  //***** General public methods
+  //**********************************************************************
+
+  /**
+   * Checks, whether a layer is edited.
+   */
+  public boolean isEditorEnabled() {
+    return editorMode != null;
+  }
+
+  /**
+   * Sets the style for edited layers.
+   * @param geomForm kind of layers the style is set for
+   * @param style a Style (if {@code null} a default style is set, so
+   *              {@link #getEditorStyle(GeometryForm)} never returns null)
+   */
+  public void setEditorStyle(GeometryForm geomForm, Style style) {
+    // If no style is specified, set a default style
+    if ( style == null )
+      switch ( geomForm ) {
+        case POINT:   style = DEFAULT_POINT_STYLE; break;
+        case LINE:    style = DEFAULT_LINE_STYLE; break;
+        case POLYGON: style = DEFAULT_POLYGON_STYLE; break;
+      }
+
+    // Set the style
+    editorStyles.put(geomForm, style);
+
+    // Apply the style also to the current edited layer
+    if ( editorLayer != null && FeatureUtil.getGeometryForm(editorLayer) == geomForm )
+      editorLayer.setStyle(style);
+    if ( segmLineLayer != null && FeatureUtil.getGeometryForm(segmLineLayer) == geomForm )
+      segmLineLayer.setStyle(style);
+    if ( segmPointLayer != null && FeatureUtil.getGeometryForm(segmPointLayer) == geomForm )
+      segmPointLayer.setStyle(style);
+  }
+
+  /**
+   * Returns the style for edited layers.
+   * @param geomForm type of layers the style is returned for
+   */
+  public Style getEditorStyle(GeometryForm geomForm) {
+    return this.editorStyles.get(geomForm);
+  }
+
+  /**
+   * Returns the style for the current edited layer.
+   */
+  public Style getEditorStyle() {
+    if ( editorGeometryForm == null )
+      return null;
+    return this.editorStyles.get(editorGeometryForm);
+  }
+
+  /**
+   * Sets the additional attributes for new FeatureCollections (besides
+   * the geometric attribute, which is added automatically).
+   * @param fType defines the attributes (If {@code null}, new FeatureCollections
+   *              only contain the geometric attribute)
+   */
+  public void setAdditionalAttributes(FeatureType fType) {
+    this.additionalAttr = fType;
+  }
+
+  /**
+   * Returns the additional attributes for new FeatureCollections (besides
+   * the geometric attribute, which is added automatically).
+   */
+  public FeatureType getAdditionalAttributes() {
+    return this.additionalAttr;
+  }
+
+  /**
+   * Returns the current editor mode.
+   */
+  public EditorMode getEditorMode() {
+    return this.editorMode;
+  }
+
+  /**
+   * Returns the edited layer.
+   */
+  public MapLayer getEditorLayer() {
+    return this.editorLayer;
+  }
+
+  /**
+   * Returns the {@link FeatureCollection} of the edited layer.
+   */
+  public FeatureCollection getEditorFeatureCollection() {
+    return this.editorFeatureCollection;
+  }
+
+  //**********************************************************************
+  //*****  Public Methods controlling the editor operations
+  //**********************************************************************
+
+  /**
+   * Starts a new layer.
+   * @param mode type of layer
+   * @param layerTitle Title for the new layer
+   * @exception UnsupportedOperationException if no map area is defined yet
+   */
+  public void startEditing(EditorMode mode, String layerTitle) {
+    // to create a new layer, first an existing layer must be
+    // displayed, so that a CRS and geo-position is available
+    // for the new layer
+    if ( getMapArea() == null )
+      throw new UnsupportedOperationException( GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorPane.Err.MissingMap") );
+    // cancel active editings
+    cancelEditing();
+    editorMode = mode;
+
+    // Create new Layers
+    initEditorFeatureCollection(layerTitle);
+    initSegmentFeatureCollection();
+    editorMapContext.addLayer(0,editorLayer);
+    fireMapPaneEvent( new LayerEditStartedEvent(this,editorLayer,this) );
+  }
+
+  /**
+   * Called when the mouse is clicked on map during creating a new layer.
+   * @param coord World coordinates of click position
+   * @param refresh indicates whether the visualisation will be refreshed
+   * @param aClearRedo indicates whether the REDO-Stack is cleared (normally {@code true},
+   *                   but {@code false} during REDO operation!)
+   */
+  protected void addSegment(Coordinate coord, boolean refresh, boolean clearRedo) {
+    if ( editorMode != EditorMode.New_Point &&
+         editorMode != EditorMode.New_Line &&
+         editorMode != EditorMode.New_Polygon )
+      return;
+
+    // store the new point for undoing
+    segmUndoPoints.push(coord);
+    // delete all former redo possibilities
+    if ( clearRedo )
+      segmRedoPoints.clear();
+    // recreate the segment FeatureCollection to show the extended line
+    generateSegmentFeatureCollection();
+
+    // finish the feature automatically
+    // - when editing points, because there are no segments for point layer,
+    //   because every point is a new feature
+    // - when editing polygons and reaching the start point
+    if ( editorGeometryForm == GeometryForm.POINT ||
+         editorGeometryForm == GeometryForm.POLYGON && segmUndoPoints.size() > 1 && segmUndoPoints.lastElement().equals(segmUndoPoints.firstElement()) )
+      finishFeature(refresh, clearRedo); // refresh is done by finishFeature()
+    else
+      if ( refresh )
+        refresh();
+  }
+
+  /**
+   * Called when the mouse is clicked on map during creating a new layer.
+   * @param coord World coordinates of click position
+   */
+  protected void addSegment(Coordinate coord) {
+    addSegment(coord, true, true);
+  }
+
+  /**
+   * Finishes the editing of the current segment (Feature).
+   * If no editing operation is currently in progress, this method does nothing.
+   * @param refresh indicates whether the visualisation will be refreshed
+   * @param aClearRedo indicates whether the REDO-Stack is cleared (normally {@code true},
+   *                   but {@code false} during REDO operation!)
+   * @exception UnsupportedOperationException if a line or polygon feature can not
+   *            be finished because of less specified points
+   */
+  protected void finishFeature(boolean refresh, boolean clearRedo) {
+    if ( editorMode == null )
+      return;
+
+    if ( editorGeometryForm == GeometryForm.LINE && segmUndoPoints.size() < 2 )
+      throw new UnsupportedOperationException( GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorPane.Err.Line.LessPoints") );
+    if ( editorGeometryForm == GeometryForm.POLYGON && segmUndoPoints.size() < 3 )
+      throw new UnsupportedOperationException( GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorPane.Err.Polygon.LessPoints") );
+
+    // close a polygon automatically
+    if ( editorGeometryForm == GeometryForm.POLYGON &&
+        !segmUndoPoints.lastElement().equals(segmUndoPoints.firstElement()) ) {
+      addSegment( segmUndoPoints.firstElement(), refresh, clearRedo );
+      return; // !!!! finishFeature(.) is called again by addSegment(..) !!!!
+    }
+    // create a new Feature from the segment points
+    Feature feature = createFeature();
+    if ( feature == null )
+      return;
+    fireMapPaneEvent( new FeatureModifiedEvent(this,editorLayer,feature,this) );
+
+    // update the global undo
+    globalUndoPoints.push( segmUndoPoints );
+    globalUndoFeatures.push( feature );
+    editorFeatureCollection.add( feature );
+
+    // after finishing a Feature, a new segment is started
+    segmUndoPoints = new Stack<Coordinate>();
+    if ( clearRedo )
+      segmRedoPoints = globalRedoPoints.isEmpty() ? new Stack<Coordinate>() : globalRedoPoints.pop();
+    initSegmentFeatureCollection();
+    // refresh the display
+    if ( refresh )
+      refresh();
+  }
+
+  /**
+   * Finishes the editing of the current segment (Feature).
+   * If no editing operation is currently in progress, this method does nothing.
+   */
+  public void finishFeature() {
+    finishFeature(true, true);
+  }
+
+  /**
+   * Finishes the current editing operation. After that no more operations
+   * on this layer can be performed.
+   * As long as no other editing operation is started, the edited (new) layer is
+   * available by {@link #getEditorLayer()} and {@link #getEditorFeatureCollection()}.
+   * If no editing operation is currently in progress, this method does nothing.
+   */
+  public void finishEditing() {
+    if ( editorMode == null )
+      return;
+
+    // if current segment is not closed, finish it automatically
+    if ( !segmUndoPoints.isEmpty() )
+      finishFeature();
+    initUndoRedo();
+
+    // move the edited layer to the "real" MapContext
+    editorMapContext.removeLayer(editorLayer);
+    if ( mapContext.indexOf(editorLayer) < 0 ) {
+      mapContext.addLayer(editorLayer);
+      editorLayer.setVisible(true);
+    }
+    fireMapPaneEvent( new LayerEditFinishedEvent(this,editorLayer,this) );
+
+    // initialize the editing variables
+    editorMode = null;
+    editorLayer = null;
+    editorFeatureCollection = null;
+    editorGeometryForm = null;
+
+    // refresh the display
+    refresh();
+  }
+
+  /**
+   * Cancels the current edititing operation. The edited layer is removed.
+   */
+  public void cancelEditing() {
+    if ( editorLayer != null )
+      this.editorMapContext.removeLayer( editorLayer );
+    if ( editorFeatureCollection != null ) {
+      if ( mapContext.indexOf(editorLayer) < 0 ) {
+        // Layer was a new one, so clear it completly
+        editorFeatureCollection.clear();
+        initUndoRedo();
+        fireMapPaneEvent( new LayerEditCanceledEvent(this,null,this) );
+      } else {
+        // Layer was an existing one, so undo all editing operations
+        undoAll();
+        initUndoRedo();
+        fireMapPaneEvent( new LayerEditFinishedEvent(this,editorLayer,this) );
+      }
+    }
+    if ( segmLineFeatureCollection != null )
+      segmLineFeatureCollection.clear();
+    if ( segmPointFeatureCollection != null )
+      segmPointFeatureCollection.clear();
+
+    // initialize the editing variables
+    editorMode = null;
+    editorLayer = null;
+    editorFeatureCollection = null;
+    editorGeometryForm = null;
+
+    // refresh the display
+    refresh();
+  }
+
+  /**
+   * Makes previously made editing actions undone.
+   * @param count count of operations made undone
+   */
+  public void undoEditing(int count) {
+    for (int i=0; i<count; i++)
+    {
+      if ( segmUndoPoints.isEmpty() ) {
+        if ( globalUndoPoints.isEmpty() )
+          // No more undo operations available
+          break;
+        else {
+          segmUndoPoints = globalUndoPoints.pop();
+          editorFeatureCollection.remove( globalUndoFeatures.peek() );
+
+          globalRedoFeatures.push( globalUndoFeatures.pop() );
+          if ( !segmRedoPoints.isEmpty() )
+            globalRedoPoints.push( segmRedoPoints );
+          segmRedoPoints = new Stack<Coordinate>();
+
+          // On lines the last point and finish feature operation
+          // are separated, so they also must be undone
+          // separately.
+          if ( editorGeometryForm == GeometryForm.LINE ) {
+            generateSegmentFeatureCollection();
+            continue;
+          }
+        }
+      }
+      segmRedoPoints.push( segmUndoPoints.pop() );
+      generateSegmentFeatureCollection();
+    }
+    refresh();
+  }
+
+  /**
+   * Makes the last made editing action undone.
+   */
+  public void undoEditing() {
+    undoEditing(1);
+  }
+
+  /**
+   * Makes all made editing action undone.
+   */
+  public void undoAll() {
+    undoEditing( Integer.MAX_VALUE );
+  }
+
+  /**
+   * Checks wheater a undo operation can be performed.
+   */
+  public boolean isUndoPossible() {
+    return !segmUndoPoints.isEmpty() || !globalUndoPoints.isEmpty();
+  }
+
+  /**
+   * Restores previous undone editing actions.
+   * @param count count of redo operations
+   */
+  public void redoEditing(int count) {
+    for (int i=0; i<count; i++)
+    {
+      if ( segmRedoPoints.isEmpty() ) {
+        if ( globalRedoPoints.isEmpty() )
+          // No more redo operations available
+          break;
+        else {
+          // Lines must be finished when all segment redo operations
+          // are redone; Points and Polygons must not, because they
+          // were already closed on last addSegment(.) (closing point
+          // of polygon is also a part of the redo stack!)
+          if ( editorGeometryForm == GeometryForm.LINE )
+            finishFeature(false, false);
+          segmRedoPoints = globalRedoPoints.pop();
+        }
+      }
+      addSegment( segmRedoPoints.pop(), false, false );
+    }
+    refresh();
+  }
+
+  /**
+   * Restores the last undone editing action.
+   */
+  public void redoEditing() {
+    redoEditing(1);
+  }
+
+  /**
+   * Checks wheater a redo operation can be performed.
+   */
+  public boolean isRedoPossible() {
+    // Redo possible if
+    // - some operations of the current segment can be redone
+    // - some operations of former segments can be redone
+    return !segmRedoPoints.isEmpty() || !globalRedoPoints.isEmpty();
+  }
+
+  //**********************************************************************
+  //*****  Helper methods for the editor operations
+  //**********************************************************************
+  /**
+   * Sets {@link #editorFeatureCollection} and {@link #editorLayer} to completely
+   * new instances.
+   * @param title title for the new layer
+   */
+  protected void initUndoRedo() {
+    // initialize the Undo/Redo-Stacks
+    for ( Stack s : globalRedoPoints ) s.clear();
+    for ( Stack s : globalUndoPoints ) s.clear();
+    globalRedoPoints.clear();
+    globalUndoPoints.clear();
+    globalRedoFeatures.clear();
+    globalUndoFeatures.clear();
+    segmRedoPoints.clear();
+    segmUndoPoints.clear();
+  }
+
+  /**
+   * Sets {@link #editorFeatureCollection} and {@link #editorLayer} to completely
+   * new instances.
+   * @param title title for the new layer
+   */
+  protected void initEditorFeatureCollection(String title) {
+    // create the new layer
+    editorFeatureCollection = (DefaultFeatureCollection)DefaultFeatureCollections.newCollection(title);
+    editorFeatureType       = createFeatureType(editorMode, getAdditionalAttributes());
+    DUMMY_EDITOR_FEATURE    = createDummyFeature(editorFeatureType);
+    editorFeatureCollection.add(DUMMY_EDITOR_FEATURE);
+    editorGeometryForm      = FeatureUtil.getGeometryForm(editorFeatureCollection);
+    editorLayer             = new DefaultMapLayer(editorFeatureCollection,getEditorStyle(editorGeometryForm));
+    editorLayer.setTitle(title);
+  }
+
+  /**
+   * Clears the {@link FeatureCollection} used to handle the current
+   * edited segment.
+   */
+  protected void initSegmentFeatureCollection() {
+    // initialize the segment to start a new feature
+    if ( segmLineFeatureCollection == null ) {
+      // initialize the objects used to display the
+      // current segment as line
+      segmLineFeatureCollection = (DefaultFeatureCollection)DefaultFeatureCollections.newCollection("SegmentAsLine");
+      segmLineFeatureType       = createFeatureType(LineString.class, null);
+      DUMMY_LINE_FEATURE        = createDummyFeature(segmLineFeatureType);
+      segmLineFeatureCollection.add(DUMMY_LINE_FEATURE);
+      segmLineLayer             = new DefaultMapLayer(segmLineFeatureCollection,getEditorStyle(GeometryForm.LINE));
+      segmLineLayer.setTitle("Line layer for current segment");
+      editorMapContext.addLayer(segmLineLayer);
+      // initialize the objects used to display the
+      // current segment as points
+      segmPointFeatureCollection = (DefaultFeatureCollection)DefaultFeatureCollections.newCollection("SegmentAsPoints");
+      segmPointFeatureType       = createFeatureType(MultiPoint.class, null);
+      DUMMY_POINT_FEATURE        = createDummyFeature(segmPointFeatureType);
+      segmPointFeatureCollection.add(DUMMY_POINT_FEATURE);
+      segmPointLayer             = new DefaultMapLayer(segmPointFeatureCollection,getEditorStyle(GeometryForm.POINT));
+      segmPointLayer.setTitle("Point layer for current segment");
+      editorMapContext.addLayer(segmPointLayer);
+    } else {
+      segmLineFeatureCollection.clear();
+      segmPointFeatureCollection.clear();
+    }
+  }
+
+  /**
+   * Creates a new segment Feature from the currently selected points.
+   */
+  protected void generateSegmentFeatureCollection() {
+    initSegmentFeatureCollection();
+    // show the segment as single points
+    Feature segmPointFeature = createSegmentPointFeature();
+    if ( segmPointFeature != null )
+      segmPointFeatureCollection.add( segmPointFeature );
+    // show the segment as a line
+    Feature segmLineFeature = createSegmentLineFeature();
+    if ( segmLineFeature != null )
+      segmLineFeatureCollection.add( segmLineFeature );
+    // Fire event even if no (real) feature is created!
+    fireMapPaneEvent( new FeatureModifiedEvent(this,segmLineLayer,segmLineFeature,this) );
+  }
+
+  /**
+   * Creates a new {@link Feature Line-Feature} from the currently selected
+   * segment points.
+   */
+  protected Feature createSegmentLineFeature() {
+    // Line can only be created with at least 2 points
+    // For point layer the segment is not shown
+    if ( segmUndoPoints.size() < 2 ||
+         editorGeometryForm == GeometryForm.POINT )
+      return null;
+
+    Coordinate[] coord      = segmUndoPoints.toArray(new Coordinate[0]);
+    LineString   lineString = createGeometryFromPoints(LineString.class, coord);
+    try {
+      return segmLineFeatureType.create( new Object[] {lineString} );
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Creates a new {@link Feature MultiPoint-Feature} from the currently selected
+   * segment points.
+   */
+  protected Feature createSegmentPointFeature() {
+    // Point can only be created with at least 1 point
+    // For point layer the segment is not shown
+    if ( segmUndoPoints.size() < 1 ||
+         editorGeometryForm == GeometryForm.POINT )
+      return null;
+
+    Coordinate[] coord      = segmUndoPoints.toArray(new Coordinate[0]);
+    MultiPoint   multiPoint = createGeometryFromPoints(MultiPoint.class, coord);
+    try {
+      return segmPointFeatureType.create( new Object[] {multiPoint} );
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Creates a new Feature from the currently selected points. The non-geometric
+   * attributes are set to default values.
+   */
+  private Feature createFeature() {
+    Coordinate[] coord   = segmUndoPoints.toArray(new Coordinate[0]);
+    Feature      feature = null;
+    try {
+      // if a feature was undone previously use this as
+      // default for the new feature
+      if ( !globalRedoFeatures.isEmpty() )
+        feature = globalRedoFeatures.pop();
+      else {
+        // generate default attribute values
+        Object[] attr = FeatureUtil.getDefaultAttributeValues(editorFeatureType);
+        // replace default attribute values with auto generate
+        // values (for all registered attributes)
+        for (int i=0; i<attr.length; i++) {
+          AttributeType aType = editorFeatureType.getAttributeType(i);
+          if ( FeatureUtil.getAutoValueGenerator(aType) != null )
+            attr[i] = FeatureUtil.getNextAutoValue(aType);
+        }
+        // create new feature
+        feature = editorFeatureType.create(attr);
+      }
+      feature.setDefaultGeometry(
+          createGeometryFromPoints(getGeometryType(editorMode), coord )
+      );
+
+      if ( feature.getNumberOfAttributes() > 1 ) {
+        attrInputOption.setValue(feature);
+        Object[] value = MultipleOptionPane.showMultipleInputDialog(
+                            this,
+                            GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorToolBar.NewFeature.title"),
+                            attrInputOption
+        );
+        if ( value == null )
+          return null;
+      }
+      return feature;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+
+  }
+
+
+  //**********************************************************************
+  //*****  Helper methods to deal with geometries
+  //**********************************************************************
+  /**
+   * Determines the geometry type to deal with according to the
+   * editor mode.
+   * @param mode editor mode
+   */
+  private static Class<? extends Geometry> getGeometryType(EditorMode mode) {
+    switch( mode ) {
+      case New_Point:   return Point.class;
+      case New_Line:    return LineString.class;
+      case New_Polygon: return Polygon.class;
+    }
+    return null;
+  }
+
+  /**
+   * Creates a geometry (point, linestring/ring, polygon) from a set of points. If
+   * no points are specified ({@code null}), a default geometry is created.
+   * @param gtype type of geometry
+   * @param coord set of points (can be {@code null})
+   */
+  private static <G extends Geometry> G createGeometryFromPoints(Class<G> gtype, Coordinate[] coord) {
+    // Point
+    if ( gtype.isAssignableFrom( com.vividsolutions.jts.geom.Point.class ) ) {
+      if ( coord == null )
+        coord = new Coordinate[] { new Coordinate() };
+      return (G)FeatureUtil.GEOMETRY_FACTORY.createPoint( coord[0] );
+    }
+    // MultiPoint
+    if ( gtype.isAssignableFrom( com.vividsolutions.jts.geom.MultiPoint.class ) ) {
+      if ( coord == null )
+        coord = new Coordinate[] { new Coordinate() };
+      return (G)FeatureUtil.GEOMETRY_FACTORY.createMultiPoint( coord );
+    }
+    // LineString
+    if ( gtype.isAssignableFrom( com.vividsolutions.jts.geom.LineString.class ) ) {
+      if ( coord != null && coord.length > 2 && coord[0].equals(coord[coord.length-1]) )
+        return (G)FeatureUtil.GEOMETRY_FACTORY.createLinearRing(coord);
+      return (G)FeatureUtil.GEOMETRY_FACTORY.createLineString(coord);
+    }
+    // Polygon
+    if ( gtype.isAssignableFrom( com.vividsolutions.jts.geom.Polygon.class ) ) {
+      LinearRing shell = null;
+      if ( coord != null && coord.length > 2 && coord[0].equals(coord[coord.length-1]) )
+        shell = FeatureUtil.GEOMETRY_FACTORY.createLinearRing(coord);
+      return (G)FeatureUtil.GEOMETRY_FACTORY.createPolygon(shell,null);
+    }
+
+    throw new UnsupportedOperationException("Can not create geometry for "+gtype.getName());
+  }
+
+  /**
+   * Extends a feature type with a default geometry attribute.
+   * @param gtype default geometry of the new feature type
+   * @param ftype a feature type which is extended (can be {@code null})
+   */
+  private FeatureType createFeatureType(Class<? extends Geometry> gtype, FeatureType ftype) {
+    GeometryAttributeType geomAttrType = new GeometricAttributeType(
+        GEOMETRY_ATTR,
+        gtype,
+        false,
+        createGeometryFromPoints(gtype,null),
+        mapContext.getCoordinateReferenceSystem(),
+        null
+    );
+    String ftypeName           = ftype != null ? ftype.getTypeName() : gtype.getSimpleName()+"_feature";
+    FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance(ftypeName);
+    builder.setDefaultGeometry( geomAttrType );
+    if ( ftype != null )
+      builder.addTypes( ftype.getAttributeTypes() );
+    try {
+      return builder.getFeatureType();
+    } catch ( SchemaException err ) {
+      throw new RuntimeException(err);
+    }
+  }
+
+  /**
+   * Extends a feature type with a default geometry attribute.
+   * @param mode  specifies the the default geometry used for the feature type
+   * @param ftype a feature type which is extended (can be {@code null})
+   */
+  private FeatureType createFeatureType(EditorMode mode, FeatureType ftype) {
+    Class<? extends Geometry> gtype = null;
+    switch( mode ) {
+      case New_Point:   gtype = Point.class; break;
+      case New_Line:    gtype = LineString.class; break;
+      case New_Polygon: gtype = Polygon.class; break;
+    }
+    return createFeatureType(gtype,ftype);
+  }
+
+  /**
+   * Creates a feature with default values for a given feature type.
+   * @param ftype a feature type
+   */
+  private static Feature createDummyFeature(FeatureType ftype) {
+    try {
+      return ftype.create( FeatureUtil.getDefaultAttributeValues(ftype) );
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Checks, whether the editor layer is empty. If it is, a dummy feature is inserted
+   * because the StreamingRenderer can not handle empty Feature-Layers yet
+   * (also if the layer is hidden!).
+   * At the moment, the first a feature is added, the dummy feature is removed, so
+   * is is not displayed any time.
+   */
+  private void resetEditorLayerVisibility() {
+    // if layer is empty, insert a Dummy-Feature (and hide the
+    // layer) because the StreamingRenderer can not handle empty
+    // Feature-Layers yet
+    if ( segmLineFeatureCollection != null && segmLineFeatureCollection.size() == 0 )
+      segmLineFeatureCollection.add( DUMMY_LINE_FEATURE );
+    if ( segmPointFeatureCollection != null && segmPointFeatureCollection.size() == 0 )
+      segmPointFeatureCollection.add( DUMMY_POINT_FEATURE );
+    if ( editorFeatureCollection != null && editorFeatureCollection.size() == 0 )
+      editorFeatureCollection.add( DUMMY_EDITOR_FEATURE );
+
+    // if the layer is not empty anymore, remove the Dummy-Feature, so
+    // it is not displayed
+    if ( segmLineFeatureCollection != null && segmLineFeatureCollection.size() == 2 )
+      segmLineFeatureCollection.remove( DUMMY_LINE_FEATURE );
+    if ( segmPointFeatureCollection != null && segmPointFeatureCollection.size() == 2 )
+      segmPointFeatureCollection.remove( DUMMY_POINT_FEATURE );
+    if ( editorFeatureCollection != null && editorFeatureCollection.size() == 2 )
+      editorFeatureCollection.remove( DUMMY_EDITOR_FEATURE );
+
+    // hide the editor layer if it only contains the dummy feature
+    if ( editorLayer != null )
+      editorLayer.setVisible(editorFeatureCollection.size() > 1 || !editorFeatureCollection.contains(DUMMY_EDITOR_FEATURE));
+    if ( segmLineLayer != null )
+      segmLineLayer.setVisible(segmLineFeatureCollection.size() > 1 || !segmLineFeatureCollection.contains(DUMMY_LINE_FEATURE));
+    if ( segmPointLayer != null )
+      segmPointLayer.setVisible(segmPointFeatureCollection.size() > 1 || !segmPointFeatureCollection.contains(DUMMY_POINT_FEATURE));
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/JEditorToolBar.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/JEditorToolBar.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/JEditorToolBar.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,530 @@
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import org.apache.log4j.Logger;
+import org.geotools.feature.FeatureType;
+import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffGCSCodes;
+
+import schmitzm.geotools.gui.GeoMapPane;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.MapContextControlPane;
+import schmitzm.geotools.gui.MapPaneStatusBar;
+import schmitzm.geotools.gui.JEditorPane.EditorMode;
+import schmitzm.geotools.map.event.FeatureModifiedEvent;
+import schmitzm.geotools.map.event.JEditorPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.LayerEditCanceledEvent;
+import schmitzm.geotools.map.event.LayerEditFinishedEvent;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.swing.ButtonGroup;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.ManualInputOption;
+import schmitzm.swing.MultipleOptionPane;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * A toolbar to control the operations of a {@link JEditorPane}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class JEditorToolBar extends JToolBar {
+	private static final Logger LOGGER = Logger.getLogger(JEditorToolBar.class.getName());
+	/** Constant for the tool "New layer" (10). */
+	public static final int LAYER_NEW = 10;
+    /** Constant for the tool "Save layer" (20). */
+    public static final int LAYER_SAVE = 20;
+    /** Constant for the tool "Cancel layer" (30). */
+    public static final int LAYER_CANCEL = 30;
+    /** Constant for the tool "Undo all editor actions" (100). */
+    public static final int EDIT_CLEAR = 100;
+	/** Constant for the tool "Undo last editor action" (110). */
+	public static final int EDIT_UNDO = 110;
+	/** Constant for the tool "Redo last undone editor action" (120). */
+	public static final int EDIT_REDO = 120;
+    /** Constant for the tool "Finish current segment" (130). */
+    public static final int EDIT_FINISH = 130;
+
+    /** Holds the action buttons of the bar. */
+    protected SortedMap<Integer, JButton> actionButtons = null;
+
+	/** Holds the {@link JEditorPane} this tool bar controls. */
+	protected JEditorPane editorPane = null;
+	
+	/** Holds the listener, that reacts on editor actions. */
+	protected JMapPaneListener mapPaneListener = null;
+
+    /**
+     * Creates a new toolbar. Notice: This toolbar does nothing
+     * until {@link #setMapPane(JEditorPane)} is called!
+     */
+    public JEditorToolBar() {
+      this(null);
+    }
+    
+    /**
+	 * Creates a new tool bar.
+	 * @param editorPane {@link JEditorPane} the tool bar controls
+	 */
+	public JEditorToolBar(JEditorPane editorPane) {
+	  super("Control the editor actions", JToolBar.HORIZONTAL);
+      this.actionButtons   = new TreeMap<Integer,JButton>();
+      // Create a Listener to sniff the zooms on the JMapPane
+      this.mapPaneListener = new JMapPaneListener() {
+          public void performMapPaneEvent(JMapPaneEvent e) {
+              if ( !(e instanceof JEditorPaneEvent) )
+                return;
+
+              // At the moment the layer editing is finished, the editor mode
+              // is not yet initialized. However for the button activation the
+              // mode NULL is essential in this case.
+              EditorMode mode = ((JEditorPaneEvent)e).getEditorMode();
+              if ( e instanceof LayerEditCanceledEvent ||
+                   e instanceof LayerEditFinishedEvent )
+                mode = null;
+              updateButtonActivation(mode);
+          }
+      };    
+     
+      setMapPane(editorPane);
+	  setFloatable(false);
+	  setRollover(true);
+	  
+	  init();
+	}
+	
+	/**
+	 * Sets the {@link JEditorPane} controlled by this tool bar.
+	 * @param editorPane {@link JEditorPane} to control (if {@code null} this
+	 *                   tool bar controls NOTHING!)
+	 */
+	public void setMapPane(JEditorPane editorPane) {
+	  // Remove listener from old MapPane
+	  if ( this.editorPane != null )
+	    this.editorPane.removeMapPaneListener( mapPaneListener );
+      this.editorPane = editorPane;
+      if ( this.editorPane != null && mapPaneListener != null )
+        this.editorPane.addMapPaneListener( mapPaneListener );
+	}
+	
+	/**
+	 * Calls {@link #initActions()} and then puts all action buttons
+	 * to the tool bar.
+	 */
+	protected void init() {
+	  initActions();
+	  initToolBar();
+	  updateButtonActivation(null);
+	}
+
+    /**
+     * Creates the action buttons and adds them to {@link #actionButtons}.
+     */
+    protected void initActions() {
+      // Action button to create a new layer
+      addAction( new EditorPaneToolBarAction(
+          LAYER_NEW,
+          this,
+          "button.layer.new",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/layer_new.png"))
+      ), false);
+
+      // Action button to finish a layer
+      addAction( new EditorPaneToolBarAction(
+          LAYER_SAVE,
+          this,
+          "button.layer.save",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/layer_finish.png"))
+      ), false);
+
+      // Action button to cancel a layer
+      addAction( new EditorPaneToolBarAction(
+          LAYER_CANCEL,
+          this,
+          "button.layer.cancel",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/layer_cancel.png"))
+      ), false);
+
+      // Action button to undo all editing action
+      addAction( new EditorPaneToolBarAction(
+          EDIT_CLEAR,
+          this,
+          "button.edit.clear",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/edit_clear.png"))
+      ), false);
+
+      // Action button to undo a editing action
+      addAction( new EditorPaneToolBarAction(
+          EDIT_UNDO,
+          this,
+          "button.edit.undo",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/edit_undo.png"))
+      ), false);
+
+      // Action button to redo a undone editing action
+      addAction( new EditorPaneToolBarAction(
+          EDIT_REDO,
+          this,
+          "button.edit.redo",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/edit_redo.png"))
+      ), false);
+
+      // Action button to finish a feature
+      addAction( new EditorPaneToolBarAction(
+          EDIT_FINISH,
+          this,
+          "button.edit.finish",
+          new ImageIcon(JEditorPane.class.getResource("resource/icons/edit_finish.png"))
+      ), false);
+    }
+    
+    /**
+     * Clears the GUI of all components and adds all action buttons to the
+     * tool bar.
+     */
+    protected void initToolBar() {
+      setAlignmentY( 1f );
+      removeAll();
+//      // Separator to the left of the tool actions to start
+//      // the tool buttons with the map (not with the coordinate grid)
+//      Dimension dimension = new Dimension( 49,10);
+//      addSeparator(dimension);
+//      // Space between tool buttons and action buttons
+//      Dimension dimension2 = new Dimension( 10,10);
+//      this.addSeparator(dimension2);
+      // Action buttons
+      for (JButton b : actionButtons.values())
+        add(b);
+    }
+    
+
+    /**
+     * Performs the action of an action button.
+     * @param tool the action
+     * @param e    the event of the button
+     */
+	protected void performActionButton(int action, ActionEvent e) {
+      if ( editorPane == null )
+        return;
+      
+      try {
+        // Perform the action "New layer"
+        if ( action == LAYER_NEW )
+          createNewLayer();
+        // Perform the action "Finish layer"
+        if ( action == LAYER_SAVE )
+          editorPane.finishEditing();
+        // Perform the action "Cancel layer"
+        if ( action == LAYER_CANCEL )
+          editorPane.cancelEditing();
+  
+        // Perform the action "Finish feature"
+        if ( action == EDIT_FINISH )
+          editorPane.finishFeature();
+        // Perform the action "Undo editing"
+        if ( action == EDIT_UNDO )
+          editorPane.undoEditing();
+        // Perform the action "Redo editing"
+        if ( action == EDIT_REDO )
+          editorPane.redoEditing();
+        // Perform the action "Clear editing"
+        if ( action == EDIT_CLEAR )
+          editorPane.undoAll();
+      } catch (Exception err) {
+        ExceptionDialog.show(err);
+      }
+	}
+	
+	/**
+	 * Sets the enables/disables property for every toolbar button according 
+	 * to the current editor state. 
+	 * @param editorMode editor mode (because some events are fired BEFORE
+	 *                   the new mode is set)
+	 */
+	protected void updateButtonActivation(EditorMode editorMode) {
+	  if ( editorPane == null ) {
+	    setAllActionsEnabled(false, false);
+	    return;
+	  }
+      getButton( LAYER_NEW ).setEnabled( editorMode == null );
+	  getButton( LAYER_SAVE ).setEnabled( editorMode != null );
+      getButton( LAYER_CANCEL ).setEnabled( editorMode != null );
+      getButton( EDIT_UNDO ).setEnabled( editorPane.isUndoPossible() );
+      getButton( EDIT_REDO ).setEnabled( editorPane.isRedoPossible() );
+      getButton( EDIT_CLEAR ).setEnabled( editorPane.isUndoPossible() );
+      getButton( EDIT_FINISH ).setEnabled( editorMode != null && editorMode != EditorMode.New_Point );
+	}
+	
+	/**
+	 * Starts new layer.
+	 */
+	protected void createNewLayer() {
+	  // Ask layer type and name
+	  ManualInputOption.Text titleOption = new ManualInputOption.Text(
+	      getResourceString("NewLayer.layer.title"), true, getResourceString("NewLayer.layer.title.default")
+      );
+	  SelectionInputOption.Radio<JEditorPane.EditorMode> typeOption = new SelectionInputOption.Radio<JEditorPane.EditorMode>(
+          getResourceString("NewLayer.layer.type"),
+          true,
+          new JEditorPane.EditorMode[] { EditorMode.New_Point, EditorMode.New_Line, EditorMode.New_Polygon },
+          new String[] { getResourceString("NewLayer.layer.type.point"), getResourceString("NewLayer.layer.type.line"), getResourceString("NewLayer.layer.type.polygon") }
+	  );
+	  FeatureTypeInputOption ftOption = new FeatureTypeInputOption(
+	      getResourceString("NewLayer.ftype.title"),
+	      false
+	  ) {
+	        // Checks whether "the_geom" is used as attribute name
+	        // --> not allowed because JEditorPane uses this attribute
+	        //     for the default geometry of new layers
+    	    public boolean performIsInputValid() {
+    	      if ( super.performIsInputValid() ) {
+    	        FeatureType ft = inpTableModel.createFeatureType();
+    	        if ( ft.getAttributeType( JEditorPane.GEOMETRY_ATTR ) != null )
+    	          throw new UnsupportedOperationException(getResourceString("NewLayer.Err.GeomAttr",JEditorPane.GEOMETRY_ATTR));
+    	      }
+    	      return true;
+    	    }
+	  };
+	  Object[] value = MultipleOptionPane.showMultipleInputDialog(
+	      this,
+	      getResourceString("NewLayer.dialog.title"),
+	      titleOption,
+	      typeOption,
+	      ftOption
+	  );
+	  if ( value == null )
+	    return; // Dialog canceled
+	  editorPane.setAdditionalAttributes( ftOption.getValue() );
+	  editorPane.startEditing(
+	      (JEditorPane.EditorMode)value[1],
+	      (String)value[0]
+	  );
+	}
+
+    /**
+     * Adds an action to the tool bar. Does nothing if a tool or action with the
+     * specified ID already exists!
+     * @param buttonAction action for the button
+     * @param resetToolBar indicates whether the toolbar GUI is reset after adding
+     *                     the button (if adding several actions it useful only to
+     *                     reset the GUI for the last added tool) 
+     */
+    public void addAction(EditorPaneToolBarAction buttonAction, boolean resetToolBar) {
+      if ( isButtonIDUsed(buttonAction.getID()) ) {
+        LOGGER.warn("addAction(.) ignored because ID already used for tool or action: "+buttonAction.getID());
+        return;
+      }
+      JButton button = new JButton(buttonAction);
+      actionButtons.put( buttonAction.getID(), button );
+      if ( resetToolBar )
+        initToolBar();
+    }
+
+    /**
+     * Adds an action to the tool bar and resets the toolbar GUI.
+     * @param buttonAction action for the toggle button
+     */
+    public void addAction(EditorPaneToolBarAction buttonAction) {
+      addAction(buttonAction, true);
+    }
+    
+    /**
+     * Returns the button for a specific tool or action.
+     * @param id the constant for a tool
+     * @return a {@link JButton} if {@code id} specifies an {@linkplain #getActionButton(int) action button}
+     *         or {@link JToogleButton} if {@code id} specifies a {@linkplain #getToolButton(int) tool button}
+     */
+    public AbstractButton getButton(int id) {
+      AbstractButton button = actionButtons.get(id);
+      if ( button == null )
+        LOGGER.warn("Unknown action ID: "+id);
+      return button;
+    }
+
+    /**
+     * Returns the button for a specific action.
+     * @param action the constant an action 
+     */
+    public JButton getActionButton(int action) {
+      AbstractButton button = getButton(action);
+      if ( button != null && !(button instanceof JButton) ) {
+        LOGGER.warn("ID specifies no action: "+action);
+        button = null;
+      }
+      return (JButton)button; 
+
+    }
+
+    /**
+     * Sets whether an action is activated or not. The visible property
+     * of the button is not affected.
+     * @param id actionID
+     * @param enabled if {@code true} the action becomes available
+     */
+    public void setButtonEnabled(int id, boolean enabled) {
+      AbstractButton button = getButton(id);
+      if ( button == null )
+        return;
+      button.setEnabled( enabled );
+    }
+
+    /**
+     * Sets whether an action is activated or not.
+     * @param id actionID
+     * @param enabled if {@code true} the tool becomes available
+     * @param hideOnDisable if {@code true} the button is also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+	public void setButtonEnabled(int id, boolean enabled, boolean hideOnDisable) {
+	  AbstractButton button = getButton(id);
+	  if ( button == null )
+	    return;
+	  button.setEnabled( enabled );
+	  // if button is enabled, it becomes visible anyway
+	  // if button is disabled and the "hide" option is set, it is also hidden 
+	  if ( enabled )
+	    button.setVisible( true );
+	  else
+	    button.setVisible( !hideOnDisable );
+	}
+
+    /**
+     * Checks whether a ID is already used for a tool or action.
+     * @param tool tool ID
+     */
+    public boolean isButtonIDUsed(int id) {
+      return actionButtons.get(id) != null;
+    }
+
+    /**
+     * Checks whether a tool is activated.
+     * @param tool tool ID
+     * @return {@code false} if an unknown ID is specified
+     */
+    public boolean isButtonEnabled(int id) {
+      AbstractButton button = getButton(id);
+      if ( button != null )
+        return button.isEnabled();
+      return false;
+    }
+
+    /**
+     * Sets the activation for all actions.
+     * @param enabled if {@code true} all actions becomes available
+     * @param hideOnDisable if {@code true} the buttons are also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+    public void setAllActionsEnabled(boolean enabled, boolean hideOnDisable) {
+      for (int tool : actionButtons.keySet())
+        setButtonEnabled(tool,enabled,hideOnDisable);
+    }   
+    
+    /**
+     * Returns the maximum ID of actions. 
+     */
+    public int getMaxActionID() {
+      return actionButtons.lastKey();
+    }
+
+    /**
+     * Returns the minimum ID of actions. 
+     */
+    public int getMinActionID() {
+      return actionButtons.firstKey();
+    }
+    
+    protected static String getResourceString(String key, Object... params) {
+      return GeotoolsGUIUtil.RESOURCE.getString("schmitzm.geotools.gui.JEditorToolBar."+key,params);
+    }
+    
+    /**
+     * Extends the {@link AbstractAction} with maintaining an ID and
+     * the {@link JEditorToolBar} the action controls.
+     * Additionally this class automatically calls
+     * {@link JEditorToolBar#performActionButton(int, ActionEvent)}.
+     * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+     */
+    public static class EditorPaneToolBarAction extends AbstractAction {
+      /** The ID of the action */
+      protected int id = -1;
+      /** The tool bar, this action is made for. */
+      protected JEditorToolBar toolBar = null;
+
+      /**
+       * Creates a new action with a dummy description and no icon.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       */
+      public EditorPaneToolBarAction(int id, JEditorToolBar toolBar) {
+        this(id,toolBar,""+id);
+      }
+
+      /**
+       * Creates a new action without an icon.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       * @param name    description used for buttons or menus 
+       */
+      public EditorPaneToolBarAction(int id, JEditorToolBar toolBar, String name) {
+        this(id,toolBar,name,null);
+      }
+
+      /**
+       * Creates a new action.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       * @param name    description used for buttons or menus 
+       * @param icon    icon used for buttons or menus 
+       */
+      public EditorPaneToolBarAction(int id, JEditorToolBar toolBar, String name, Icon icon) {
+        super("",icon);
+        this.id      = id;
+        this.toolBar = toolBar;
+        this.putValue(SHORT_DESCRIPTION, getResourceString(name));
+      }
+
+      /**
+       * Calls {@link JEditorToolBar#performActionButton(int, ActionEvent)}.
+       */
+      public void actionPerformed(ActionEvent e) {
+        if ( toolBar.actionButtons.get(id) != null )
+          toolBar.performActionButton(id, e);
+      }
+      
+      /**
+       * Returns the (unique) id of this action.
+       * @return
+       */
+      public int getID() {
+        return id;
+      }
+    }
+}

Added: trunk/src/schmitzm/geotools/gui/JMapPane.20080418_eigenesPanning
===================================================================
--- trunk/src/schmitzm/geotools/gui/JMapPane.20080418_eigenesPanning	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/JMapPane.20080418_eigenesPanning	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,1886 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.LayoutManager;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.Vector;
+
+import javax.swing.JList;
+import javax.swing.event.MouseInputAdapter;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.grid.GeneralGridRange;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.data.FeatureSource;
+import org.geotools.data.coverage.grid.AbstractGridCoverage2DReader;
+import org.geotools.data.coverage.grid.AbstractGridFormat;
+import org.geotools.data.memory.MemoryFeatureCollection;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureType;
+import org.geotools.filter.AbstractFilter;
+import org.geotools.filter.BetweenFilter;
+import org.geotools.filter.FilterFactoryImpl;
+import org.geotools.filter.GeometryFilterImpl;
+import org.geotools.filter.spatial.DWithinImpl;
+import org.geotools.geometry.GeneralEnvelope;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.gui.swing.MouseSelectionTracker_Public;
+import org.geotools.gui.swing.event.GeoMouseEvent;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.parameter.Parameter;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.renderer.lite.RendererUtilities;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.resources.image.ImageUtilities;
+import org.geotools.styling.Style;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.expression.Expression;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.FilterUtil;
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.grid.GridUtil;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import schmitzm.geotools.map.event.GeneralSelectionEvent;
+import schmitzm.geotools.map.event.GridCoverageSelectedEvent;
+import schmitzm.geotools.map.event.GridCoverageValueSelectedEvent;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.map.event.ScaleChangedEvent;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.MultiPoint;
+
+/**
+ * Diese Klasse erweitert die Geotools-Klasse {@link org.geotools.gui.swing.JMapPane} um
+ * folgende Features:
+ * <ul>
+ *   <li>zusaetzliche Maus-Steuerungen:
+ *       <ul>
+ *         <li><b>Linksklick:</b> ueber {@link #setState(int)} eingestellte Aktion</li>
+ *         <li><b>Rechtsklick:</b> Zoom-Out um Faktor 2 (nur wenn Linksklick auf Zoom-In
+ *                                 eingestellt ist)</li>
+ *         <li><b>Drag mit linker Maustaste:</b> neuen Karten-Bereich selektieren oder
+ *                                               Features selektieren (siehe {@link #setWindowSelectionState(int)})</li>
+ *         <li><b>Drag mit rechter Maustaste:</b> Karten-Bereich verschieben</li>
+ *         <li><b>Mausrad:</b> Zoom-In/Out ueber aktueller Position (Faktor 1.2)</li>
+ *       </ul></li>
+ *   <li>Ankoppeln von {@link JMapPaneListener} und Ausloesung diverser
+ *       Ereignisse:
+ *       <ul>
+ *         <li><b>{@link ScaleChangedEvent}:</b> Wird ausgeloest, wenn sich die Aufloesung
+ *             der angezeigten Karte aendert</li>
+ *         <li><b>{@link MapAreaChangedEvent}:</b> Wird ausgeloest, wenn sich die Aufloesung
+ *                angezeigte Karte-Ausschnitt aendert</li>
+ *         <li><b>{@link GeneralSelectionEvent}:</b> Wird ausgeloest, wenn der Anwender
+ *                einen Bereich aus der Karte ausgewaehlt hat (egal ob dabei gezoomt wurde,
+ *                Features/Raster selektiert wurden, oder nicht selektiert wurde)</li>
+ *         <li><b>{@link FeatureSelectedEvent}:</b> Wird ausgeloest, wenn der Anwender
+ *                Features aus der Karte ausgewaehlt hat</li>
+ *         <li><b>{@link GridCoverageSelectedEvent}:</b> Wird ausgeloest, wenn der Anwender
+ *                Raster-Bereiche aus der Karte ausgewaehlt hat</li>
+ *       </ul></li>
+ * </ul>
+ * Sofern eingeschaltet, erfolgt {@linkplain #setHighlight(boolean) Highlighting}
+ * immer auf dem obersten sichtbaren Nicht-Raster-Layer.<br>
+ * Darueberhinaus besteht ueber {@link #getTransform()} Zugriff auf eine
+ * {@linkplain AffineTransform affine Transformation} mit der die aktuellen
+ * Fenster-Koordinaten (z.B. eines <code>MouseEvent</code>) in Karten-Koordinaten
+ * (Latitude/Longitude) umgerechnet werden koennen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class JMapPane extends org.geotools.gui.swing.JMapPane {
+  private static final Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
+
+private static final Logger LOGGER = Logger.getLogger( JMapPane.class.getName() );
+
+  /** @deprecated ersetzt durch {@link #ZOOM_IN} */
+  public static final int ZoomIn = org.geotools.gui.swing.JMapPane.ZoomIn;
+  /** @deprecated ersetzt durch {@link #ZOOM_OUT} */
+  public static final int ZoomOut = org.geotools.gui.swing.JMapPane.ZoomOut;
+  /** @deprecated ersetzt durch {@link #PAN} */
+  public static final int Pan = org.geotools.gui.swing.JMapPane.Pan;
+  /** @deprecated ersetzt durch {@link #RESET} */
+  public static final int Reset = org.geotools.gui.swing.JMapPane.Reset;
+  /** @deprecated ersetzt durch {@link #SELECT_TOP} */
+  public static final int Select = org.geotools.gui.swing.JMapPane.Select;
+
+  /** Flag fuer Modus "Nichts machen".
+   *  @see #setWindowSelectionState(int)
+   *  @see #setState(int) */
+  public static final int NONE = 100;
+  /** Flag fuer Modus "Zuruecksetzen". Nicht fuer Window-Auswahl moeglich!
+   *  @see #setState(int) */
+  public static final int RESET = org.geotools.gui.swing.JMapPane.Reset;
+  /** Flag fuer Modus "Kartenausschnitt bewegen". Nicht fuer Window-Auswahl moeglich!
+   *  @see #setState(int) */
+  public static final int PAN = org.geotools.gui.swing.JMapPane.Pan;
+  /** Flag fuer Modus "Heran zoomen".
+   *  @see #setWindowSelectionState(int)
+   *  @see #setState(int) */
+  public static final int ZOOM_IN = org.geotools.gui.swing.JMapPane.ZoomIn;
+  /** Flag fuer Modus "Heraus zoomen". Nicht fuer Window-Auswahl moeglich!
+   *  @see #setState(int) */
+  public static final int ZOOM_OUT = org.geotools.gui.swing.JMapPane.ZoomOut;
+  /** Flag fuer Modus "Feature-Auswahl auf dem obersten (sichtbaren) Layer".
+   *  @see #setWindowSelectionState(int)
+   *  @see #setState(int) */
+  public static final int SELECT_TOP = org.geotools.gui.swing.JMapPane.Select;
+  /** Flag fuer Modus "Feature-Auswahl auf allen (sichtbaren) Layern".
+   *  @see #setWindowSelectionState(int)
+   *  @see #setState(int) */
+  public static final int SELECT_ALL = 103;
+  /** Modus fuer Window-Selektion (Default: {@link #ZOOM_IN}). */
+  protected int selState = NONE;
+
+  /** Transformation zwischen Fenster-Koordinaten und Karten-Koordinaten (lat/lon) */
+  protected AffineTransform transform = null;
+  /** Liste der angeschlossenen Listener, die auf Aktionen des MapPanes lauschen. */
+  protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
+
+  /** MouseListener, der auf eine Karten-Auswahl via "Drag" lauscht und mit einem selectionPerformedEvent reagiert */
+  final protected MouseSelectionTracker_Public selTracker = new MouseSelectionTracker_Public() {
+		protected void selectionPerformed(int ox, int oy, int px, int py) {
+			performSelectionEvent(ox, oy, px, py);
+		}
+	};
+
+  private static final FilterFactoryImpl ff = FilterUtil.FILTER_FAC;
+  private static final FilterFactory2 ff2 = FilterUtil.FILTER_FAC2;
+  private static final GeometryFactory gf = FilterUtil.GEOMETRY_FAC;
+
+  /** A flag indicating if dispose() was already called. If true, then further use of this {@link JMapPane} is undefined.  */
+   private boolean disposed = false;
+
+   /** Listener, der auf Maus-Drags lauscht **/
+   private MouseInputAdapter mia;
+
+   /** Listener, der auf das Mausrad lauscht und mit Zoom reagiert */
+   private MouseWheelListener mouseWheelZoomListener;
+
+   /** Wenn true, dann werden RasterLayer waehrend des Panning auf setVisible(false) gesetzt **/
+   protected boolean hideRasterLayersDuringPan = false;
+   /** Remebers the layers that are hidden during a PAN action **/
+   protected List<MapLayer> hiddenForPanning = new LinkedList<MapLayer>();
+   /** remebers if the hideRastersForPanning() method was already called **/
+   private boolean rastersHiddenforPanning = false;
+
+   /** Wenn true, dann wird der Cursor waehrend des naechsten Repaint auf die WAIT gesetzt. **/
+   private boolean setWaitCursorDuringNextRepaint;
+   /** Defines which Component to change the MouseCursor if in WAIT STATE. If unset, only THIS component is used**/
+   private Component mouseWaitCursorComponent;
+
+   /** Cursor wenn kein Mausbutton gedrueckt wird. default oder SwingUtil.PAN **/
+   private Cursor normalCursor = Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR );
+
+   /**
+    * Gibt die optional gesetzte {@link Component} zurueck, deren Cursor auch auf WAIT gesetzt werden soll
+    *
+    * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+    *
+    * @return null oder {@link Component}
+    */
+  public Component getWaitCursorComponent() {
+	return mouseWaitCursorComponent;
+  }
+
+  /**
+   * Setzt eine Componente, deren Cursor zusaetzlich zu THIS noch auf WAIT gesetzt wird
+   * falls durch dies ueberhaupt durch setSetWaitCursorDuringNextRepaint(true) veranlasst wurde
+   *
+   * @param parentComponent z.b. der Frame, der diese {@link JMapPane} enhaelt
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setWaitCursorComponent(Component parentComponent) {
+
+	this.mouseWaitCursorComponent = parentComponent;
+  }
+
+/**
+   * Erzeugt ein neues MapPane mit {@link BorderLayout}, {@link StreamingRenderer} und
+   * {@link DefaultMapContext}.
+   */
+  public JMapPane() {
+    this( new BorderLayout(),
+          true,
+          new StreamingRenderer(),
+          new DefaultMapContext(DefaultGeographicCRS.WGS84)
+    );
+  }
+
+  /**
+   * Erzeugt ein neues MapPane.
+   * @param layout Layout-Manager fuer die GUI-Komponente (z.B. {@link BorderLayout})
+   * @param isDoubleBuffered siehe Konstruktor der {@linkplain org.geotools.gui.swing.JMapPane#JMapPane(LayoutManager,boolean,GTRenderer,MapContext) Oberklasse}
+   * @param renderer Renderer fuer die graphische Darestellung (z.B. {@link StreamingRenderer})
+   * @param context  Verwaltung der einzelnen Layer (z.B. {@link DefaultMapContext}).
+   */
+  public JMapPane(LayoutManager layout, boolean isDoubleBuffered, GTRenderer renderer, MapContext context) {
+    super(layout,isDoubleBuffered,renderer,context);
+
+    // Dieser Hint sorgt wohl dafuer, dass die Rasterpixel nicht interpoliert werden
+    // Ueber die Methode enableAntiAliasing(boolean) kann das rechenintensive AntiAliasing fuer Text un Vectoren eingeschaltet werden
+    RenderingHints hints = ImageUtilities.NN_INTERPOLATION_HINT;
+    renderer.setJava2DHints(hints);
+
+//    hints.add( new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF ) );
+//    hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ) );
+//    hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC ) );
+//    hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ) );
+//    hints.add( new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED ) );
+//    hints.add( new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY ) );
+//    Map rendererParams = new HashMap();
+//    rendererParams.put("optimizedDataLoadingEnabled",new Boolean(true) );
+//    renderer.setRendererHints( rendererParams );
+
+    setWindowSelectionState( ZOOM_IN );
+    setState( ZOOM_IN );
+
+    // Listener, der auf das Mausrad lauscht und mit Zoom reagiert
+    mouseWheelZoomListener = new MouseWheelListener() {
+      public void mouseWheelMoved(MouseWheelEvent e) {
+        performMouseWheelEvent(e);
+      }
+    };
+    this.addMouseWheelListener( mouseWheelZoomListener );
+
+    // Listener, der auf Maus-Drags lauscht
+    mia = new MouseInputAdapter() {
+      private int      pressedButton = 0;
+      private Point2D  pressedPos    = null;
+      private Envelope pressedEnv    = null;
+      // private Cursor   oldCursor     = null;
+
+      public void mousePressed(MouseEvent e) {
+        // Start-Position merken
+        pressedButton = e.getButton();
+        pressedPos    = e.getPoint();
+        pressedEnv    = getMapArea();
+
+        // Cursor merken, falls er in performMouseDraggedEvent(.)
+        // veraendert wird
+        // oldCursor = getCursor();
+      }
+
+      public void mouseDragged(MouseEvent e) {
+        if ( pressedPos != null )
+          performMouseDraggedEvent(pressedButton, pressedPos, pressedEnv, e);
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        // Start-Position zuruecksetzen
+        pressedButton = 0;
+        pressedPos = null;
+        pressedEnv = null;
+
+        // Cursor wieder herstelen
+        // setCursor(oldCursor);
+        updateCursor();
+
+        // Den Click merken, um den Wait Cursor zu setzen
+        setWaitCursorDuringNextRepaint = true;
+
+        if ( hideRastersForPanning(false) )
+        	repaint(); // Es wurden gerade Layer sichbar gemacht
+      }
+    };
+    this.addMouseListener(mia);
+    this.addMouseMotionListener(mia);
+
+    // Hightlight immer auf dem obersten sichtbaren Nicht-Raster-Layer
+//MS-01.sc: Der GT-Highlight-Manager arbeitet zu langsam und ohnehin nur
+//          fuer unprojizierte Layer korrekt
+//    this.setHighlight(true);
+    this.setHighlight(false);
+//MS-01.ec
+    context.addMapLayerListListener( new schmitzm.geotools.map.event.MapLayerListAdapter() {
+      private void resetHighlightLayer() {
+        if ( isHighlight() )
+          setHighlightLayer( getTopVisibleNonGridCoverageLayer() );
+      }
+      public void layerAdded(org.geotools.map.event.MapLayerListEvent e) {
+        resetHighlightLayer();
+      }
+      public void layerChanged(org.geotools.map.event.MapLayerListEvent e) {
+        resetHighlightLayer();
+      }
+      public void layerMoved(org.geotools.map.event.MapLayerListEvent e) {
+        resetHighlightLayer();
+      }
+      public void layerRemoved(org.geotools.map.event.MapLayerListEvent e) {
+        resetHighlightLayer();
+      }
+    } );
+
+    // CRS wird immer vom ersten in die Karte eingefuegten Layer uebernommen
+    // Wenn noch keine MapArea gesetzt wurde, wird diese vom Layer uebernommen
+    context.addMapLayerListListener( new schmitzm.geotools.map.event.MapLayerListAdapter() {
+      public void layerAdded(org.geotools.map.event.MapLayerListEvent e) {
+        if ( getContext().getLayerCount() == 1 ) {
+          CoordinateReferenceSystem crs = null;
+          // CRS aus Layer ermitteln
+          try {
+            crs = e.getLayer().getFeatureSource().
+                getSchema().getDefaultGeometry().getCoordinateSystem();
+            if ( getMapArea() == null )
+              setMapArea( e.getLayer().getFeatureSource().getBounds() );
+          } catch (Exception err) {
+            LOGGER.warn("CRS could not be determined from map layer. WGS84 used.");
+            crs = DefaultGeographicCRS.WGS84;
+          }
+          // CRS dem MapContext zuweisen
+          try {
+            getContext().setCoordinateReferenceSystem(crs);
+          } catch (Exception err) {
+            LOGGER.error("CRS could not be assigned to map context.");
+          }
+        }
+      }
+    } );
+  }
+
+  /**
+   * Aktualisiert die Karten-Anzeige vollstaendig. Ruft
+   * {@link JMapPane#setReset(boolean) JMapPane#setReset(true)} auf und
+   * anschliessend {@link #repaint()}.
+   *
+   * <br>
+   * SK: Der Mauszeiger wird waehrend des repaints auf WAIT gesetzt mittels {@link #setSetWaitCursorDuringNextRepaint(boolean)}
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void refresh() {
+
+	// SK: Added by SK, 27.09.2007
+	// Durch den reset ist das repaint immer etwas aufwaendiger. Der Cursor wechselt dann auf WAIT
+	setSetWaitCursorDuringNextRepaint(true);
+
+    setReset(true);
+    repaint();
+  }
+
+  /**
+   * Aktiviert oder Deaktiviert das AntiAliasing for diese {@link JMapPane}.
+   * AntiALiasing ist besonders für Textbeschriftung sehr schoen, verbraucht aber auch mehr Performance.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setAntiAliasing(final boolean aa){
+	  LOGGER.info("Setting AntiAliasing for this JMapPane to "+aa);
+	  final RenderingHints java2DHints = getRenderer().getJava2DHints();
+	  java2DHints.put( RenderingHints.KEY_ANTIALIASING, aa ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
+  }
+
+  /**
+   * Setzt den Kartenausschnitt auf die Ausdehnung eines bestimmten Layers.
+   * Macht nichts, wenn {@code null} uebergeben wird.
+   *
+   * <br>A refresh of the map is NOT called.
+   *
+   * @param layer ein Layer
+   */
+  public void zoomToLayer(MapLayer layer) {
+    if ( layer == null )
+      return;
+    // This action ususally takes some time..
+    setWaitCursorDuringNextRepaint = true;
+    try {
+
+      // BB umrechnen von Layer-CRS in Map-CRS
+      final Envelope mapAreaNew = JTSUtil.transformEnvelope(
+          layer.getFeatureSource().getBounds(),
+          layer.getFeatureSource().getSchema().getDefaultGeometry().getCoordinateSystem(),
+          getContext().getCoordinateReferenceSystem());
+
+      setMapArea( mapAreaNew );
+    } catch (Exception err) {
+      LOGGER.warn("Zoom to layer did not terminate correctly",err);
+    }
+  }
+
+
+  /**
+   * Zooms the {@link JMapPane} to the {@link Envelope} of a layer.
+   *
+   * <br>A refresh of the map is not done automatically
+   *
+   * @param index Index of the {@link MapLayer} in the {@link MapContext} (from back to top)
+
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void zoomToLayer(int index) {
+	  zoomToLayer( getContext().getLayer(index) );
+  }
+
+  /**
+   * Zooms the {@link JMapPane} to the {@link Envelope} of the selected layer.
+   * The layer is selected by the idx, counting from front to back, like humans would expect in a {@link JList}
+   *
+   * <br>A refresh of the map is not done automatically
+   *
+   *
+   *
+   * @param index Reverse index of the {@link MapLayer} in the {@link MapContext}
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+	public void zoomToLayerIdxReverse(int index) {
+		zoomToLayer( getContext().getLayerCount() -1 - index );
+	}
+
+
+  /**
+   * Liefert die Anzahl der Einheiten, die ein Bildschirm-Pixel darstellt. Die Einheit ist die Grundeinheit des CRS
+   */
+  public double getScale() {
+    if ( getWidth() == 0 || getMapArea() == null)
+      return 0.0;
+    return getMapArea().getWidth() / getWidth();
+  }
+
+  /**
+   * Liefert oberste Layer (sichtbar oder unsichtbar).
+   */
+  public MapLayer getTopLayer() {
+    int count = getContext().getLayerCount();
+    return count > 0 ? getContext().getLayer(count-1) : null;
+  }
+
+  /**
+   * Liefert oberste sichtbare Layer.
+   */
+  public MapLayer getTopVisibleLayer() {
+    for (int i=getContext().getLayerCount()-1; i>=0; i--) {
+      MapLayer layer = getContext().getLayer(i);
+      if (layer.isVisible())
+        return layer;
+    }
+    return null;
+  }
+
+  /**
+   * Liefert oberste sichtbare Raster-Layer.
+   */
+  public MapLayer getTopVisibleGridCoverageLayer() {
+    for (int i=getContext().getLayerCount()-1; i>=0; i--) {
+      MapLayer layer = getContext().getLayer(i);
+      if ( layer.isVisible() && isGridCoverageLayer(layer) )
+        return layer;
+    }
+    return null;
+  }
+
+  /**
+   * Liefert oberste sichtbare Nicht-Raster-Layer.
+   */
+  public MapLayer getTopVisibleNonGridCoverageLayer() {
+    for (int i=getContext().getLayerCount()-1; i>=0; i--) {
+      MapLayer layer = getContext().getLayer(i);
+      if ( layer.isVisible() && !isGridCoverageLayer(layer) )
+        return layer;
+    }
+    return null;
+  }
+
+  /**
+   * Liefert unterste Layer (sichtbar oder unsichtbar).
+   */
+  public MapLayer getBottomLayer() {
+    return getContext().getLayerCount() > 0 ? getContext().getLayer(0) : null;
+  }
+
+  /**
+   * Setzt den Modus fuer Window-Selektion. Default ist {@link #ZOOM_IN}.
+   *
+   *
+   *  <ul>
+   *    <li>{@link #ZOOM_IN}: Zoom auf selektierten Bereich</li>
+   *    <li>{@link #SELECT_TOP}: Auswahl der Features im selektierten
+   *        Bereich des  <b>obersten</b> (sichtbaren) Layers</li>
+   *    <li>{@link #SELECT_ALL} Auswahl der Features im selektierten
+   *        ueber alle Layer</li>
+   *    <li>{@link #NONE} Nichts machen</li>
+   *  </ul>
+   * @param newSelState Modus fuer Window-Selektion
+   */
+  public void setWindowSelectionState(final int newSelState) {
+    if ( newSelState != NONE && newSelState != ZOOM_IN &&
+         newSelState != SELECT_TOP && newSelState != SELECT_ALL )
+      throw new IllegalArgumentException("Unknown selection state for window selection!");
+
+    // Den selTracker bei Wechsel zu NONE deaktivieren (SK)
+    if ((newSelState == NONE) && (selState != NONE)) {
+    	this.removeMouseListener( selTracker );
+    } else
+   	// Den selTracker bei Wechsel von NONE aktivieren (SK)
+    if (( newSelState != NONE) && (selState == NONE)) {
+    	this.addMouseListener( selTracker );
+    }
+
+    this.selState = newSelState;
+
+    // Je nach Aktion den Cursor umsetzen
+    updateCursor();
+  }
+
+
+
+  /**
+   * Abhaengig von selState wird der Cursor gesetzt
+   */
+  public void updateCursor() {
+	  // Je nach Aktion den Cursor umsetzen
+	  switch ( this.selState ) {
+	  case SELECT_TOP:
+	  case SELECT_ALL: setCursor( SwingUtil.CROSSHAIR_CURSOR); break;
+//	  case SELECT_ALL: setCursor( Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR )); break;
+	  case ZOOM_IN:    setCursor( SwingUtil.ZOOMIN_CURSOR ); break;
+	  case ZOOM_OUT:   setCursor( SwingUtil.ZOOMOUT_CURSOR ); break;
+	  default:         setCursor( getNormalCursor() ); break;
+	  }
+  }
+
+/**
+   * Liefert den Modus fuer Window-Selektion.
+   * @see #setWindowSelectionState(int)
+   */
+  public int getWindowSelectionState() {
+    return this.selState;
+  }
+
+  /**
+   * Fuegt der Map einen Listener hinzu.
+   * @param l neuer Listener
+   */
+  public void addMapPaneListener(JMapPaneListener l) {
+    mapPaneListeners.add(l);
+  }
+
+  /**
+   * Entfernt einen Listener von der Map.
+   * @param l zu entfernender Listener
+   */
+  public void removeMapPaneListener(JMapPaneListener l) {
+    mapPaneListeners.remove(l);
+  }
+
+  /**
+   * Propagiert ein Ereignis an alle angeschlossenen Listener.
+   * @param e Ereignis
+   */
+  protected void fireMapPaneEvent(JMapPaneEvent e) {
+    for( JMapPaneListener l : mapPaneListeners )
+      l.performMapPaneEvent(e);
+  }
+
+  /**
+   * Konvertiert die Maus-Koordinaten (relativ zum <code>JMapPane</code>) in
+   * Karten-Koordinaten.
+   * @param e Maus-Ereignis
+   */
+  public static Point2D getMapCoordinatesFromEvent(MouseEvent e) {
+    // aktuelle Geo-Position aus GeoMouseEvent ermitteln
+    if ( e != null && e instanceof GeoMouseEvent )
+      try {
+        return ( (GeoMouseEvent) e).getMapCoordinate(null).toPoint2D();
+      } catch (Exception err) {
+        err.printStackTrace();
+      }
+
+    // aktuelle Geo-Position ueber Transformation des JMapPane berechnen
+    if ( e != null && e.getSource() instanceof JMapPane ) {
+      AffineTransform at = ((JMapPane)e.getSource()).getTransform();
+      if ( at != null )
+       return at.transform( e.getPoint(), null );
+    }
+
+    return null;
+  }
+
+  /**
+   * Verarbeitet die Selektion eines Karten-Ausschnitts. Erzeugt immer ein
+   * {@link GeneralSelectionEvent} fuer den ausgewaehlten Bereich. Wurden
+   * Features oder Raster selektiert, werden zudem {@link FeatureSelectedEvent FeatureSelectedEvents}
+   * (bzw. GridCoverageSelectedEvents GridCoverageSelectedEvents) ausgeloest.
+   * @param ox X-Koordinate der VON-Position
+   * @param oy Y-Koordinate der VON-Position
+   * @param px X-Koordinate der BIS-Position
+   * @param py Y-Koordinate der BIS-Position
+   */
+  protected void performSelectionEvent(int ox, int oy, int px, int py) {
+    if ( getContext().getLayerCount() == 0 )
+      return;
+
+    // keine wirkliche Selektion, sondern nur ein Klick
+    if (ox == px || oy == py)
+      return;
+
+    //****************************************************************************
+    // SK: Wenn der ausgewaehlte Bereich kleiner als 12x12 pixel ist, dann war es wohl ein Irrtum ("Verklickt")
+    // und wird ignoriert. Zu starkes Zoomen (z.B. auf 2x2 pixel) kostet sehr viel Rechenzeit.
+    //****************************************************************************
+    if ( (Math.abs(ox-px)<12) || (Math.abs(oy-py)<12) ){
+
+    	//****************************************************************************
+        // Changed by SK:
+    	// performing the zoom and/or pan by recalculating the mapArea
+        // important and new here: don't zoom in/out more that the min/max scale!
+        // n.b.: zoom is not only performed here, but also and with the mouse wheel and setMapArea
+    	//****************************************************************************
+    	Coordinate centre = getMapArea().centre();
+    	double zlevel = 2.6;
+
+    	Coordinate ll = new Coordinate(centre.x - (getMapArea().getWidth() / zlevel), centre.y
+    			- (getMapArea().getHeight() / zlevel));
+    	Coordinate ur = new Coordinate(centre.x + (getMapArea().getWidth() / zlevel), centre.y
+    			+ (getMapArea().getHeight() / zlevel));
+
+    	final Envelope newMapArea = new Envelope(ll, ur);
+
+    	// Hier passiert die Ueberpruefung und ggf Anpassung der Scale
+        mapArea = bestAllowedMapArea( newMapArea );
+
+        repaint();
+    	return;
+    }
+
+    // Fenster-Koordinaten in Map-Koordinaten umwandeln
+    Envelope env = tranformWindowToGeo(ox, oy, px, py);
+
+    // Generelles Event ausloesen
+    fireMapPaneEvent( new GeneralSelectionEvent(this,env) );
+
+    int selectState = getWindowSelectionState();
+    switch( selectState ) {
+      case ZOOM_IN: // Karte neu setzen
+                    this.setMapArea(env);
+                    refresh(); // WICHTIG!! Damit die veraenderte Form beruecksichtigt wird!?
+                    break;
+      case SELECT_TOP:
+      case SELECT_ALL: // Features selektieren
+                       boolean featuresFound = findFeaturesAndFireEvents( createBoundingBoxFilter(env), selectState, env );
+                       if ( selectState == SELECT_ALL || !featuresFound )
+                         findGridCoverageSubsetsAndFireEvents( env, selectState );
+                       break;
+    }
+  }
+
+  /**
+   * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
+   * @param e Mausrad-Event
+   */
+  protected void performMouseWheelEvent(MouseWheelEvent e) {
+    if ( getContext().getLayerCount() == 0 )
+      return;
+
+    int units = e.getUnitsToScroll();
+    // Positiver Wert --> Zoom in  --> Faktor < 1
+    // Negativer Wert --> Zoom out --> Faktir > 1
+
+    // SK: 9.9.2007 zoom jetzt wie bei GoogleEarth
+    double  zFactor = units > 0 ? 1.3 : 1/1.3;
+    // vorher    double  zFactor = units > 0 ? 1/1.2 : 1.2;
+
+    // Fenster-Koordinaten zu Karten-Koordinaten transformieren
+    Point2D mapCoord = getTransform().transform( e.getPoint(), null );
+    // Relative Position des Mauszeigers zum Kartenausschnitt
+    // -> Nach Zoom soll dieselbe Kartenposition unterhalb des Mauszeigers
+    //    erscheinen, wie vor dem Zoom
+    double  relX = (mapCoord.getX()-getMapArea().getMinX()) / getMapArea().getWidth();
+    double  relY = (mapCoord.getY()-getMapArea().getMinY()) / getMapArea().getHeight();
+
+    // Neuen Karten-Ausschnitt berechnen
+    Coordinate ll = new Coordinate( mapCoord.getX()-getMapArea().getWidth()*relX*zFactor,
+                                    mapCoord.getY()-getMapArea().getHeight()*relY*zFactor );
+    Coordinate ur = new Coordinate( mapCoord.getX()+getMapArea().getWidth()*(1-relX)*zFactor,
+                                    mapCoord.getY()+getMapArea().getHeight()*(1-relY)*zFactor );
+    setMapArea( new Envelope(ll,ur) );
+    repaint();
+  }
+
+
+  /**
+   * As soon as the mouse button is pressed the cursor should change to PAN
+   *
+   *  @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  @Override
+  public void mousePressed(MouseEvent e) {
+	if ( e.getButton() == MouseEvent.BUTTON3 || getState() == PAN ) {
+		setCursor( SwingUtil.PANNING_CURSOR);
+	}
+	super.mousePressed(e);
+  }
+
+  /**
+   * Machts nichts. Verhindert die "neuen" {@code JMapPane}-Aktionen
+   * von GT2-2.4.2, welche diese {@code JMapPane}-Implementierung stoeren.
+   * @param e MouseEvent
+   */
+  public void mouseReleased(MouseEvent e) {
+  }
+
+  /**
+   * Machts nichts. Verhindert die "neuen" JMapPane-Aktionen von GT2-2.4.2,
+   * welche diese {@code JMapPane}-Implementierung stoeren.
+   * @param e MouseEvent
+   */
+  public void mouseDragged(MouseEvent e) {
+    // ignore the gt2-2.4.2 functionality
+  }
+
+/**
+   * Verarbeitet die Maus-Ziehen-Aktion, indem (bei {@link #PAN}-State oder
+   * Drag mit rechter Maustaste) der Kartenausschnitt verschoben wird.
+   * @param e Maus-Event
+   */
+  protected void performMouseDraggedEvent(int pressedButton, Point2D pressedPos, Envelope pressedEnv, MouseEvent e) {
+    if ( getContext().getLayerCount() == 0 )
+      return;
+
+    if ( pressedButton == MouseEvent.BUTTON3 || getState() == PAN ) {
+    	setCursor( SwingUtil.PANNING_CURSOR);
+
+      // Rasterlayer verstecken (wenn noch nicht geschehen)
+      hideRastersForPanning(true);
+
+      // Fenster-Koordinaten zu Karten-Koordinaten transformieren
+      Point2D pressedCoord = getTransform().transform(pressedPos, null);
+      Point2D newCoord = getTransform().transform(e.getPoint(), null);
+      // Veraenderung bzgl. der urspruenglichen Position errechnen
+      double deltaX = pressedCoord.getX() - newCoord.getX();
+      double deltaY = pressedCoord.getY() - newCoord.getY();
+      // Urspruenglichen Ausschnitt entsprechend der Veraenderung verschieben
+      Envelope newArea = new Envelope(pressedEnv);
+      newArea.translate(deltaX, deltaY);
+      setMapArea(newArea);
+      repaint();
+    }
+  }
+
+  /**
+   * Diese Methode macht nichts, wenn hideRasterLayersDuringPan==false
+   * @param hide wenn true, dann werden alle sichtbaren Rasterlayer versteckt, sonst werden alle verstecken Rasterlayer wieder sichtbar gemacht.
+   * @return Wurde die Sichtbarkeit von mind. einem Raster geaendert?
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  private boolean hideRastersForPanning(boolean hide) {
+
+	  if (!hideRasterLayersDuringPan) return false;
+
+	  boolean affectedLayers = false;
+	  if (hide){
+		  if (rastersHiddenforPanning) return false;
+		  rastersHiddenforPanning = true;
+		  for (int i=getContext().getLayerCount()-1; i>=0; i--) {
+			  MapLayer layer = getContext().getLayer(i);
+			  if ( layer.isVisible() && isGridCoverageLayer(layer) ) {
+				  layer.setVisible(false);
+				  hiddenForPanning.add(layer);
+				  affectedLayers = true;
+			  }
+		  }
+	  } else {
+		  rastersHiddenforPanning = false;
+		  for (MapLayer layer : hiddenForPanning) {
+			  layer.setVisible(true);
+			  affectedLayers = true;
+		  }
+	  }
+	  return affectedLayers;
+  }
+
+  /**
+   * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
+   * @param ox X-Koordinate der VON-Position
+   * @param oy Y-Koordinate der VON-Position
+   * @param px X-Koordinate der BIS-Position
+   * @param py Y-Koordinate der BIS-Position
+   */
+  public Envelope tranformWindowToGeo(int ox, int oy, int px, int py) {
+    AffineTransform at = getTransform();
+    Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
+    Point2D geoP = at.transform(new Point2D.Double(px, py), null);
+    return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
+  }
+
+  /**
+   * Transformiert eine Fenster-Koordinate in eine Geo-Koordinate.
+   * @param x X-Koordinate
+   * @param y Y-Koordinate
+   */
+  public Point2D tranformWindowToGeo(int x, int y) {
+    AffineTransform at = getTransform();
+    return at.transform(new Point2D.Double(x, y), null);
+  }
+
+  /**
+   * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
+   * neu.
+   */
+  protected void resetTransform() {
+    if ( getMapArea() == null || getWidth() == 0 || getHeight() == 0 )
+      return;
+    this.transform =  new AffineTransform(
+        // Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
+        // nach rechts (Osten) hin groesser
+        // --> positive Verschiebung
+        getMapArea().getWidth()/getWidth(),
+        // keine Verzerrung
+        0.0,
+        0.0,
+        // Waehrend die Fenster-Koordinaten nach unten hin groesser werden,
+        // werden Latitude-Koordinaten nach Sueden hin keiner
+        // --> negative Verschiebung
+        -getMapArea().getHeight()/getHeight(),
+        // Die Longitude-Koordinaten werden nach Osten hin groesser
+        // --> obere linke Ecke des Fensters hat also den Minimalwert
+        getMapArea().getMinX(),
+        // Die Latitude-Koordinaten werden nach Norden hin groesser
+        // --> obere linke Ecke des Fensters hat also den Maximalwert
+        getMapArea().getMaxY()
+    );
+  }
+
+  /**
+   * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
+   * Karten-Koordinaten (Lat/Lon) umzurechnen.
+   * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
+   *         noch keine Karte angezeigt wird
+   */
+  public AffineTransform getTransform() {
+    // Workaround: Obwohl eine Karte gesetzt ist, kann es sein, dass die
+    //             Transformation noch nicht gesetzt ist (da noch kein
+    //             setMapArea(.)-Aufruf stattgefunden hat!)
+    if ( transform == null )
+      resetTransform();
+    // nur Kopie der Transformation zurueckgeben!
+    if ( transform != null )
+      return new AffineTransform(transform);
+    return null;
+  }
+
+  /**
+   * Setzt die sichtbare Karte. Danach wird die {@linkplain AffineTransform Transformation}
+   * zwischen Fenster-Koordinaten und Karten-Koordinaten neu berechnet.<br>
+   * Loest ein {@link ScaleChangedEvent aus}
+   *
+   * {@link #setMapArea(Envelope)} wird ignoriert, falls durch die neue MapArea ein nicht gueltiger
+   * Massstab entstehen wuerde UND die bisherige maparea != null ist
+   *
+   * @param env neuer Kartenausschnitt
+   * @see #resetTransform()
+   * @see #getTransform()
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  @Override
+  public void setMapArea(Envelope env) {
+
+
+	//****************************************************************************
+	// Ueber die Funktionen setMaxZoomScale und setMinZoomScale kann der
+	// geotools JMapPane ein gueltiger Massstabsbereich gesetzt werden. Dieser
+	// wird hier ueberprueft. (SK)
+	//****************************************************************************
+	if (super.getMapArea()!=null)  {
+		env = bestAllowedMapArea(env);
+    }
+
+    double   oldScale = getScale();
+    Envelope oldEnv   = getMapArea();
+
+    super.setMapArea(env);
+
+    resetTransform();
+    double   newScale = getScale();
+    Envelope newEnv   = getMapArea();
+    if ( oldScale != newScale )
+      fireMapPaneEvent( new ScaleChangedEvent(this,oldScale,newScale) );
+    if ( oldEnv == null && newEnv != null || oldEnv != null && !oldEnv.equals( newEnv ) )
+      fireMapPaneEvent( new MapAreaChangedEvent(this,oldEnv,newEnv) );
+  }
+
+
+	/**
+	 * Reagiert auf Linksklick mit der ueber {@link #setState(int)}eingestellten
+	 * Aktion und auf Rechtsklick mit Zoom-Out (sofern {@link #ZOOM_IN}-State
+	 * fuer Linksklick eingestellt). Alle anderen Klicks werden ignoriert.
+	 * 
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public void mouseClicked(MouseEvent e) {
+		// wenn noch kein Layer dargestellt wird, nichts machen
+		if (getContext().getLayerCount() == 0)
+			return;
+
+		double oldScale = getScale();
+		Envelope oldEnv = getMapArea();
+
+		switch (e.getButton()) {
+		// Linksklick --> Eingestellte Funktion
+		case MouseEvent.BUTTON1: // Feature-Auswahl nicht ueber die
+									// super-Funktion
+			int state = getState();
+			if (state == SELECT_TOP || state == SELECT_ALL) {
+				
+				
+				/**
+				 * BEGIN StefanChange
+				 * Dieser Block findet sichtbare Features im Umkreis des Mausklicks
+				 * und kümmert sich selbst um das verschicken der Events. Dabei wird z.Zt. immer
+				 * eine FeaterCollection mit nur einem Feature verschickt. Und zwar jenes Feature, 
+				 * welches am nächsten liegt.  
+				 */
+				
+				// Fenster-Koordinate in Geo-Koordinate umwandelt
+				Point2D geoCoord = getTransform().transform(e.getPoint(), null);
+				com.vividsolutions.jts.geom.Point mousePoint = new GeometryFactory()
+						.createPoint(new Coordinate(geoCoord.getX(), geoCoord
+								.getY()));
+
+				// Rechnet 10 Pixel in die Einheit der Karte um.
+				final Point pAtDistance = new Point(
+						(int) e.getPoint().getX() + 10, (int) e.getPoint()
+								.getY());
+				final Point2D geoCoordAtDistance = getTransform().transform(
+						pAtDistance, null);
+				final Double dist = Math.abs(geoCoordAtDistance.getX()
+						- geoCoord.getX());
+
+				final Envelope envelope = new Envelope(geoCoord.getX(),
+						geoCoord.getX(), geoCoord.getY(), geoCoord.getY());
+				Hashtable<MapLayer, FeatureCollection> result = findVisibleFeatures(
+						createNearPointFilter(geoCoord, dist), state, envelope);
+
+				// Events ausloesen fuer jedes Layer
+				for (Enumeration<MapLayer> element = result.keys(); element
+						.hasMoreElements();) {
+					
+					MapLayer layer = element.nextElement();
+					FeatureCollection fc = result.get(layer);
+					FeatureCollection fcOne;
+					
+					if (fc != null && !fc.isEmpty()) {
+
+						if (fc.size() > 1) {
+							// Hier werden alle Features weggeschmissen, bis auf das nächste.  
+
+							Feature nearestFeature = null;
+							Double nearestDist = 0.0;
+
+							Iterator<Feature> fcIt = fc.iterator();
+							while (fcIt.hasNext()) {
+								Feature f = fcIt.next();
+								Object obj = f.getAttribute(0);
+
+								if (obj instanceof Geometry) {
+									// Bei Punkten ja noch ganz einfach:
+									Geometry featureGeometry = (Geometry) obj;
+									double distance = featureGeometry
+											.distance(mousePoint);
+
+									if ((nearestFeature == null)
+											|| (distance < nearestDist)) {
+										nearestFeature = f;
+										nearestDist = distance;
+									}
+								}
+
+							}
+							fc.close( fcIt);
+
+							fcOne = new MemoryFeatureCollection(fc.getFeatureType());
+							fc.clear();
+							fcOne.add(nearestFeature);
+						} else {
+							fcOne = fc;
+						}
+						fireMapPaneEvent(new FeatureSelectedEvent(
+								JMapPane.this, layer, envelope, fcOne));
+					}
+				}
+
+				/**
+				 * ENDE Stefan Change
+				 */
+                        
+
+				if (state == SELECT_ALL || !result.isEmpty())
+					findGridCoverageValuesAndFireEvents(geoCoord, state);
+				break;
+			}
+			super.mouseClicked(e);
+			break;
+
+		// Rechtsklick --> Zoom-Out
+		case MouseEvent.BUTTON3:
+			int oldState = getState();
+			// MS: ist doch eleganter, wenn IMMER mit Rechtsklick raus-gezoomt
+			// werden kann!
+			// // ZOOM_OUT nur wenn Links-Klick auf ZOOM_IN eingestellt ist)
+			// if ( oldState != ZOOM_IN )
+			// return;
+			setState(ZOOM_OUT);
+			super.mouseClicked(e);
+			setState(oldState);
+			break;
+		// Sonst nix
+		default:
+			return;
+		}
+
+
+    // In super.mouseClicked(.) wird (nach Zoom) die MapArea neu gesetzt, aber
+    // leider ohne setMapArea(.) aufzurufen, sondern indem einfach die Variable
+    // 'mapArea' neu belegt wird
+    // --> Transform muss neu berechnet werden
+    // --> Aenderung der Aufloesung propagieren
+    resetTransform();
+    double   newScale = getScale();
+    Envelope newEnv   = getMapArea();
+    if ( oldScale != newScale )
+      fireMapPaneEvent( new ScaleChangedEvent(this,oldScale,newScale) );
+    if ( oldEnv == null && newEnv != null || oldEnv != null && !oldEnv.equals( newEnv ) )
+      fireMapPaneEvent( new MapAreaChangedEvent(this,oldEnv,newEnv) );
+  }
+
+  /**
+	 * In <code>super.paintComponent(.)</code> wird unter gewissen Umstaenden
+	 * die MapArea neu gesetzt, aber leider ohne {@link #setMapArea(Envelope)}
+	 * aufzurufen, sondern indem einfach die Variable <code>mapArea</code> neu
+	 * belegt wird. Damit auch die Transformation an den neuen Kartenbereich
+	 * angepasst wird, muss diese Methode ueberschrieben werden.
+	 * <p>
+	 * Neu von SK: Ich habe in der Geotools
+	 * {@link org.geotools.gui.swing.JMapPane} die noetigen variablen protected
+	 * gemacht, um hier schon festzustellen, ob der aufruf von
+	 * super.paintComponent das eigentliche aussehen der Karte veraendern wird.
+	 * Die Methode paintComponent wird naemlich bei jeder Bewegung der Maus
+	 * aufgerufen, und meistens wird super.paintComponent nur ein gebufferted
+	 * Image auf den Ausschnitt zeichnen. Falls die "seriouseChange" zu erwarten
+	 * ist, wird evt. der Mauscursor auf WAIT gesetzt, und auf jeden Fall die
+	 * Methode {@link #resetTransform()} aufgerufen.<br>
+	 * Sinn des Ganzen? Ich habe versucht etwas Performance zu gewinnen, da
+	 * sonst bei jeder Mausbewegung die die {@link AffineTransform} durch
+	 * {@link #resetTransform()} neu berechnet wurde.
+	 * 
+	 * @param g
+	 *            Graphics
+	 * @see #resetTransform()
+	 * 
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+  protected void paintComponent(Graphics g) {
+	  Cursor oldCursor = null;
+	  boolean seriouseChange = false;
+
+	  if (!getBounds().equals(oldRect) || reset) seriouseChange = true;
+	  else if (!mapArea.equals(oldMapArea)) seriouseChange = true;
+
+	  if (seriouseChange){
+		  // LOGGER.debug("seriouse!");
+		  if (setWaitCursorDuringNextRepaint) {
+
+			  if (getWaitCursorComponent() != null) {
+				  // Der Cursor soll auch in einer anderen Component geaendert werden..
+				  oldCursor = getWaitCursorComponent().getCursor();
+				  getWaitCursorComponent().setCursor(
+						  WAIT_CURSOR);
+			  }
+
+			  // Den Cursor extra nochmal in dieser COmponente aendern
+			  setCursor(WAIT_CURSOR);
+		  }
+	  }
+
+		super.paintComponent(g);
+
+		if (seriouseChange){
+			resetTransform();
+			if (setWaitCursorDuringNextRepaint) {
+				setWaitCursorDuringNextRepaint = false;
+				if (oldCursor != null)
+					getWaitCursorComponent().setCursor(oldCursor);
+				updateCursor(); // Den Cursor in dieser Componente immer wieder herstellen
+			}
+		}
+
+	}
+
+  /**
+   * Testet (anhand der Bounding-Box), ob das Objekt eines Layers eine
+   * andere Bounding-Box schneidet. Die Bounding-Box des Layer-Objekts wird
+   * zunaechst in das CRS des MapPanes umgerechnets.
+   * @param layer ein Layer
+   * @param env   Bounding-Box in CRS des MapPane
+   */
+  private boolean gridLayerIntersectsEnvelope(MapLayer layer, Envelope env) {
+    try {
+      // BB des Layers umrechnen von Layer-CRS in Map-CRS
+      Envelope bounds_MapCRS = JTSUtil.transformEnvelope(
+          layer.getFeatureSource().getBounds(),
+          layer.getFeatureSource().getSchema().getDefaultGeometry().getCoordinateSystem(),
+          getContext().getCoordinateReferenceSystem()
+      );
+
+      // TODO warum kann bounds_MapCRS == null sein ?? ?SK fragt martin???
+      if (bounds_MapCRS == null) return false;
+
+      return bounds_MapCRS.intersects(env);
+    } catch (Exception err) {
+      return false;
+    }
+  }
+
+  /**
+   * Testet (anhand der Features), ob das Objekt eines Layers eine
+   * Bounding-Box schneidet.
+   * @param layer ein Layer
+   * @param env   Bounding-Box in CRS des MapPane
+   */
+  private boolean featureLayerIntersectsEnvelope(MapLayer layer, Envelope env) {
+    try {
+      // BB umrechnen von Map-CRS in Layer-CRS
+      Envelope env_LayerCRS = JTSUtil.transformEnvelope(
+          env,
+          getContext().getCoordinateReferenceSystem(),
+          layer.getFeatureSource().getSchema().getDefaultGeometry().getCoordinateSystem()
+      );
+      GeometryFilterImpl filter   = createBoundingBoxFilter(env_LayerCRS);
+      Expression         geometry = ff.createAttributeExpression( layer.getFeatureSource().getSchema().getDefaultGeometry().getLocalName() );
+      filter.setExpression1( geometry );
+      return !layer.getFeatureSource().getFeatures( filter ).isEmpty();
+    } catch (Exception err) {
+      return false;
+    }
+  }
+
+  /**
+   * Ermittelt alle sichtbaren Features, die einen Filter erfuellen. Beim Modus
+   * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+   * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+   * 
+   * @see #findFeatures(GeometryFilterImpl, int, Envelope)
+   * 
+   * @param filter Filter
+   * @param mode Suchmodus
+   * @return eine leere {@link Hashtable} falls der Filter {@code null} ist
+   */
+protected Hashtable<MapLayer,FeatureCollection> findVisibleFeatures(GeometryFilterImpl filter, int mode, Envelope env) {
+    Hashtable<MapLayer,FeatureCollection> result = new Hashtable<MapLayer,FeatureCollection>();
+    if ( filter == null )
+      return result;
+
+    // Je nach Modus: Alle oder nur das oberste Layer
+    MapContext context   = getContext();
+    MapLayer[] layerList = context.getLayers();
+    for (int i=layerList.length-1; i>=0; i--) {
+      MapLayer layer = layerList[i];
+      if ( !layer.isVisible() )
+        continue;
+
+      // Bei einem Raster-Layer, das die BB schneidet, abbrechen, wenn nur im
+      // obersten (sichtbaren) Layer gesucht wird.AbstractGridCoverage2DReader
+      // Ansonsten Raster-Layer einfach  uebergehen.
+      if ( isGridCoverageLayer(layer) ) {
+        if ( mode == SELECT_TOP && gridLayerIntersectsEnvelope(layer,env) )
+          break;
+        continue;
+      }
+
+      // Filter an Layer koppeln
+      // WICHTIG: Dies darf erst geschehen, NACHDEM das Schleifen-Abbruch-Kriterium
+      //          fuer die Raster-Layer geprueft wurde!!
+      //          Werden folgende Zeilen naemlich auf das FeatureSource des
+      //          Raster-Layers angewandt, dann "bricht" der Filter "irgendwie"
+      //          zusammen und auch die ZUVOR gefilterten FeatureCollections,
+      //          sind ploetzlich EMPTY!!!!!!!!!!!!!!!!!!!!!!!!!!!
+      final FeatureSource featureSource = layer.getFeatureSource();
+	Expression geometry = ff.createAttributeExpression( featureSource.getSchema().getDefaultGeometry().getLocalName() );
+	
+      filter.setExpression1( geometry );
+
+      try {
+        // Filter auf Layer des Features anwenden
+//        FeatureCollection fc = layer.getFeatureSource().getFeatures( FilterUtil.cloneFilter(filter) ); // KLAPPT (NOCH) NICHT
+        FeatureCollection fc = featureSource.getFeatures( filter );
+  
+        // Liefert eine FeatureCollection zurück, in welcher nur Features enthalten sind, welche bei der aktuellen Anzeigeskala aufgrund des Styles gerendert werden.
+        fc = filterSLDVisibleOnly( fc, layer.getStyle() );
+
+        if ( !fc.isEmpty() ) {
+          result.put(layer,fc);
+          // Beim Modus "oberstes Layer selektieren" die Schleife beenden
+          if ( mode == SELECT_TOP )
+            break;
+        }
+      } catch (Exception err) {
+        err.printStackTrace();
+      }
+
+//      for ( FeatureCollection fc1 : result.values() )
+//        LOGGER.debug("A  "+fc1+"    "+fc1.isEmpty());
+    }
+//    for ( FeatureCollection fc1 : result.values() )
+//      LOGGER.debug("B   "+fc1+"    "+fc1.isEmpty());
+
+    return result;
+  }
+
+
+
+/**
+ * Ermittelt alle Features, die einen Filter erfuellen. Beim Modus
+ * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+ * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+ * 
+ *  17.4.08, Stefan
+ * 
+ * @param filter Filter
+ * @param mode Suchmodus
+ * @return eine leere {@link Hashtable} falls der Filter {@code null} ist
+ */
+protected Hashtable<MapLayer,FeatureCollection> findFeatures(GeometryFilterImpl filter, int mode, Envelope env) {
+  Hashtable<MapLayer,FeatureCollection> result = new Hashtable<MapLayer,FeatureCollection>();
+  if ( filter == null )
+    return result;
+
+  // Je nach Modus: Alle oder nur das oberste Layer
+  MapContext context   = getContext();
+  MapLayer[] layerList = context.getLayers();
+  for (int i=layerList.length-1; i>=0; i--) {
+    MapLayer layer = layerList[i];
+    if ( !layer.isVisible() )
+      continue;
+
+    // Bei einem Raster-Layer, das die BB schneidet, abbrechen, wenn nur im
+    // obersten (sichtbaren) Layer gesucht wird.AbstractGridCoverage2DReader
+    // Ansonsten Raster-Layer einfach  uebergehen.
+    if ( isGridCoverageLayer(layer) ) {
+      if ( mode == SELECT_TOP && gridLayerIntersectsEnvelope(layer,env) )
+        break;
+      continue;
+    }
+
+    // Filter an Layer koppeln
+    // WICHTIG: Dies darf erst geschehen, NACHDEM das Schleifen-Abbruch-Kriterium
+    //          fuer die Raster-Layer geprueft wurde!!
+    //          Werden folgende Zeilen naemlich auf das FeatureSource des
+    //          Raster-Layers angewandt, dann "bricht" der Filter "irgendwie"
+    //          zusammen und auch die ZUVOR gefilterten FeatureCollections,
+    //          sind ploetzlich EMPTY!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    final FeatureSource featureSource = layer.getFeatureSource();
+	Expression geometry = ff.createAttributeExpression( featureSource.getSchema().getDefaultGeometry().getLocalName() );
+	
+    filter.setExpression1( geometry );
+
+    try {
+      // Filter auf Layer des Features anwenden
+//      FeatureCollection fc = layer.getFeatureSource().getFeatures( FilterUtil.cloneFilter(filter) ); // KLAPPT (NOCH) NICHT
+      FeatureCollection fc = featureSource.getFeatures( filter );
+
+      // Liefert eine FeatureCollection zurück, in welcher nur Features enthalten sind, welche bei der aktuellen Anzeigeskala aufgrund des Styles gerendert werden.
+      fc = filterSLDVisibleOnly( fc, layer.getStyle() );
+
+      if ( !fc.isEmpty() ) {
+        result.put(layer,fc);
+        // Beim Modus "oberstes Layer selektieren" die Schleife beenden
+        if ( mode == SELECT_TOP )
+          break;
+      }
+    } catch (Exception err) {
+      err.printStackTrace();
+    }
+
+//    for ( FeatureCollection fc1 : result.values() )
+//      LOGGER.debug("A  "+fc1+"    "+fc1.isEmpty());
+  }
+//  for ( FeatureCollection fc1 : result.values() )
+//    LOGGER.debug("B   "+fc1+"    "+fc1.isEmpty());
+
+  return result;
+}
+
+
+
+
+	/**
+	 *  SLD Rules können die Paramter MinScaleDenominator und
+	 * MaxScaleDenominator enthalten. Dadurch können Elemente für manche
+	 * Zoom-Stufen deaktiviert werden. 
+	 * 
+	 * @param fc
+	 *            Die zu filternde FeatureCollection. Diese wird nicht
+	 *            verändert.
+	 * @param style
+	 *            Der Style, mit dem die Features gerendert werden (z.b.
+	 *            layer.getStyle() )
+     *
+	 * @return Eine FeatureCollection in welcher nur die Features enthalten
+	 *         sind, welche bei aktuellen Scale mit dem übergebenen Style
+	 *         gerendert werden.
+	 * 
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	private MemoryFeatureCollection filterSLDVisibleOnly(final FeatureCollection fc,
+			final Style style) {
+		
+		// Der "scaleDenominator" der aktuellen JMapPane
+		Double scaleDenominator = RendererUtilities
+		.calculateOGCScale(new ReferencedEnvelope(
+				getMapArea(), getContext()
+				.getCoordinateReferenceSystem()),
+				getSize().width, null);
+		
+		return FeatureUtil.filterSLDVisibleOnly(fc, style, scaleDenominator);
+	}
+
+/**
+ * Ermittelt alle Features, die in einem Bereich liegen und erzeugt
+ * entsprechende {@link FeatureSelectedEvent FeatureSelectedEvents}. Beim Modus
+ * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht. Beim
+ * Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+ * 
+ * @param filter
+ *            Filter
+ * @param mode
+ *            Suchmodus
+ * @param env
+ *            Bereich der durchsucht wird (fuer das Filtern irrelevant; wird nur
+ *            fuer Events benoetigt!)
+ */
+  protected boolean findFeaturesAndFireEvents(GeometryFilterImpl filter, int mode, Envelope env) {
+    Hashtable<MapLayer,FeatureCollection> result = findVisibleFeatures(filter,mode,env);
+    
+    // Events ausloesen fuer jedes Layer
+    for (Enumeration<MapLayer> e = result.keys(); e.hasMoreElements(); ) {
+      MapLayer layer = e.nextElement();
+      FeatureCollection fc = result.get(layer);
+      if ( fc != null && !fc.isEmpty() )
+        fireMapPaneEvent( new FeatureSelectedEvent(this,layer,env,fc) );
+    }
+    return !result.isEmpty();
+  }
+
+  /**
+   * Ermittelt alle Teil-Raster, die in einem Bereich liegen. Beim Modus
+   * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+   * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+   * @param env Bounding-Box
+   * @param mode Suchmodus
+   * @return eine leere {@link Hashtable} falls die Bounding-Box {@code null} ist
+   */
+  protected Hashtable<MapLayer,GridCoverage2D> findGridCoverageSubsets(Envelope env, int mode) {
+    Hashtable<MapLayer,GridCoverage2D> result = new Hashtable<MapLayer,GridCoverage2D>();
+    if ( env == null )
+      return result;
+
+    MapContext context = getContext();
+    // Je nach Modus: Alle oder nur das oberste Layer
+    MapLayer[] layerList = context.getLayers();
+    for (int i=layerList.length-1; i>=0; i--) {
+      MapLayer layer = layerList[i];
+      Object   layerObj = getLayerSourceObject(layer);
+      if ( !layer.isVisible() )
+        continue;
+
+      // Bei einem Nicht-Raster-Layer, das die BB schneidet, abbrechen, wenn nur im
+      // obersten (sichtbaren) Layer gesucht wird.
+      // Ansonsten Nicht-Raster-Layer einfach uebergehen.
+      if (  !(layerObj instanceof GridCoverage2D) ) {
+        if ( mode == SELECT_TOP && featureLayerIntersectsEnvelope(layer,env) )
+          break;
+        continue;
+      }
+
+      GridCoverage2D sourceGrid = (GridCoverage2D)layerObj;
+      com.vividsolutions.jts.geom.Envelope jtsEnv = env;
+      org.geotools.geometry.Envelope2D gtEnv2D = JTS.getEnvelope2D(jtsEnv,sourceGrid.getCoordinateReferenceSystem());
+//      org.opengis.spatialschema.geometry.Envelope gtGenEnv = new GeneralEnvelope((org.opengis.spatialschema.geometry.Envelope)gtEnv2D); // gt2-2.3.4
+      org.opengis.geometry.Envelope gtGenEnv = new GeneralEnvelope((org.opengis.geometry.Envelope)gtEnv2D); // gt2-2.4.2
+
+
+//      GridCoverage2D subsetGrid = (GridCoverage2D)Operations.DEFAULT.crop(sourceGrid,gtGenEnv);
+      GridCoverage2D subsetGrid = GridUtil.createGridCoverage(sourceGrid,gtGenEnv);
+      if ( subsetGrid != null)
+        result.put(layer,subsetGrid);
+      // Beim Modus "oberstes Layer selektieren" die Schleife beenden
+      if ( mode == SELECT_TOP )
+        break;
+    }
+    return result;
+  }
+
+  /**
+   * Ermittelt alle Teil-Raster, die in einem Bereich liegen und erzeugt entsprechende
+   * {@link GridCoverageSelectedEvent GridCoverageSelectedEvents}. Beim Modus
+   * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+   * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+   * @param env Bounding-Box
+   * @param mode Suchmodus
+   * @return eine leere {@link Hashtable} falls die Bounding-Box {@code null} ist
+   */
+  protected boolean findGridCoverageSubsetsAndFireEvents(final Envelope env, final int mode) {
+    final Hashtable<MapLayer,GridCoverage2D> result = findGridCoverageSubsets(env,mode);
+    // Events ausloesen fuer jedes Layer
+    for (final Enumeration<MapLayer> e = result.keys(); e.hasMoreElements(); ) {
+      final MapLayer layer = e.nextElement();
+      final GridCoverage2D subset = result.get(layer);
+      if ( subset != null )
+        fireMapPaneEvent( new GridCoverageSelectedEvent(this,layer,env,subset) );
+    }
+    return !result.isEmpty();
+  }
+
+  /**
+   * Ermittelt alle Raster-Werte, die an einer bestimmten Geo-Position liegen.
+   * Beim Modus {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+   * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+   * <p>
+   * SK: 28.09.2007 Da ein Rasterlayer auch mehrere Baender haben kann, ist es sinnvoll, nicht
+   * <code>Hashtable MapLayer,Double </code> sondern
+   * <code>Hashtable MapLayer,Double[] </code> zurueckzugeben.
+   *
+   * @param point Geo-Referenzgeop
+   * @param mode Suchmodus
+   * @return eine leere {@link Hashtable} falls keine Werte ermittelt werden konnten
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  protected Hashtable<MapLayer,double[]> findGridCoverageValues(Point2D point, int mode) {
+    Hashtable<MapLayer,double[]> result = new Hashtable<MapLayer,double[]>();
+    if ( point == null )
+      return result;
+
+    MapContext context = getContext();
+    // Je nach Modus: Alle oder nur das oberste Layer
+    MapLayer[] layerList = context.getLayers();
+    for (int i=layerList.length-1; i>=0; i--) {
+      MapLayer layer = layerList[i];
+      Object   layerObj = getLayerSourceObject(layer);
+      if ( !layer.isVisible() )
+        continue;
+
+//	SK 29.9.2007 Vorher:
+//      // Bei einem Nicht-Raster-Layer, das den Punkt beinhaltet, abbrechen, wenn nur im
+//      // obersten (sichtbaren) Layer gesucht wird.
+//      // Ansonsten Nicht-Raster-Layer einfach uebergehen.
+//      if (  !(layerObj instanceof GridCoverage2D) ) {
+//    	  if ( mode == SELECT_TOP && featureLayerIntersectsEnvelope(layer,new Envelope(point.getX(),point.getX(),point.getY(),point.getY())) )
+//    		  break;
+//    	  continue;
+//      }
+
+      // Bei einem Nicht-Raster-Layer, das den Punkt beinhaltet, abbrechen, wenn nur im
+      // obersten (sichtbaren) Layer gesucht wird.
+      // Ansonsten Nicht-Raster-Layer einfach uebergehen.
+      // SK 29.9.07: Ein AbstractGridCoverage2DReader ist auch ein Raster
+      if (!(layerObj instanceof GridCoverage2D)
+					&& !(layerObj instanceof AbstractGridCoverage2DReader)) {
+				final Envelope pointAsEnvelope = new Envelope(point.getX(),
+						point.getX(), point.getY(), point.getY());
+				if (mode == SELECT_TOP
+						&& featureLayerIntersectsEnvelope(layer,
+								pointAsEnvelope))
+					break;
+				continue;
+      }
+
+      GridCoverage2D sourceGrid;
+
+      if (layerObj instanceof AbstractGridCoverage2DReader) {
+				AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) layerObj;
+				Parameter readGG = new Parameter(
+						AbstractGridFormat.READ_GRIDGEOMETRY2D);
+
+				ReferencedEnvelope mapExtend = new org.geotools.geometry.jts.ReferencedEnvelope(
+						mapArea, context.getCoordinateReferenceSystem());
+
+				readGG.setValue(new GridGeometry2D(new GeneralGridRange(
+						getBounds()), mapExtend));
+
+				try {
+					sourceGrid = (GridCoverage2D) reader
+							.read(new GeneralParameterValue[] { readGG });
+				} catch (Exception e) {
+					LOGGER.error(e);
+					continue;
+				}
+			}
+      else {
+		// Ein instanceof ist nicht noetig, da sonst schon break oder continue aufgerufen worden waere
+		sourceGrid = (GridCoverage2D) layerObj;
+	  }
+
+      // vorher:      double[] value = new double[2];
+
+      // getNumSampleDimensions gibt die Anzahl der Baender des Rasters zurueck.
+      double[] values = new double[ sourceGrid.getNumSampleDimensions() ];
+
+      try {
+        // Grid an Geo-Position auswerten
+        sourceGrid.evaluate(point,values);
+      } catch (Exception err) {
+        // z.B. Punkt ausserhalb des Rasters --> Layer uebergehen
+        continue;
+      }
+
+      // SK: voher wurde nur der erste Wert zurueckgegeben
+      //   result.put(layer,value[0]);
+      // jetzt werden alle werte zueuckgegeben
+
+      result.put(layer,values);
+      // Beim Modus "oberstes Layer selektieren" die Schleife beenden
+      if ( mode == SELECT_TOP )
+        break;
+    }
+    return result;
+  }
+
+  /**
+   * Ermittelt die Raster-Werte, die an einem Punkt liegen und erzeugt entsprechende
+   * {@link GridCoverageValueSelectedEvent GridCoverageValueSelectedEvents}. Beim Modus
+   * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht.
+   * Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+   * <p>
+   * SK: 28.09.2007 Da ein Rasterlayer auch mehrere Baender haben kann, ist es sinnvoll, nicht
+   * <code>Hashtable MapLayer,Double </code> sondern
+   * <code>Hashtable MapLayer,Double[] </code> zurueckzugeben.
+   *
+   * @param point Geo-Referenz
+   * @param mode  Suchmodus
+   * @return eine leere {@link Hashtable} falls der Punkt {@code null} ist
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  protected boolean findGridCoverageValuesAndFireEvents(Point2D point, int mode) {
+    Hashtable<MapLayer,double[]> result = findGridCoverageValues(point,mode);
+
+    // Events ausloesen fuer jedes Layer
+    for (Enumeration<MapLayer> e = result.keys(); e.hasMoreElements(); ) {
+      MapLayer layer = e.nextElement();
+      double[]   values = result.get(layer);
+      fireMapPaneEvent( new GridCoverageValueSelectedEvent(this,layer,point,values) );
+    }
+    return !result.isEmpty();
+  }
+
+  /**
+   * Bereitet einen BoundingBox-Filter vor. Das "linke" Attribut der Expression
+   * (das Feature-Attribut, auf das der Filter angewendet wird), wird dabei noch
+   * nicht belegt. Dies geschieht erst bei der Auswertung entsprechend des
+   * jeweiligen Layers
+   * @param env Bounding-Box
+   */
+  private static GeometryFilterImpl createBoundingBoxFilter(Envelope env) {
+    // Filter fuer Envelope zusammenstellen
+    Expression     bbox       = ff.createBBoxExpression( env );
+    GeometryFilterImpl bboxFilter = (GeometryFilterImpl)ff.createGeometryFilter(AbstractFilter.GEOMETRY_BBOX);
+//    GeometryFilterImpl bboxFilter = (GeometryFilterImpl)ff.createGeometryFilter(AbstractFilter.GEOMETRY_WITHIN);
+    bboxFilter.setExpression2( bbox );
+    return bboxFilter;
+  }
+
+  /**
+   * Bereitet einen Punkt-Filter vor. Das "linke" Attribut der Expression
+   * (das Feature-Attribut, auf das der Filter angewendet wird), wird dabei noch
+   * nicht belegt. Dies geschieht erst bei der Auswertung entsprechend des
+   * jeweiligen Layers
+   * @param point Geo-Koordinate
+   */
+  private static GeometryFilterImpl createPointFilter(Point2D point) {
+    // Filter fuer Envelope zusammenstellen
+    Geometry geometry = gf.createPoint(new Coordinate(point.getX(), point.getY()));
+    GeometryFilterImpl pointFilter = (GeometryFilterImpl)ff.createGeometryFilter(GeometryFilterImpl.GEOMETRY_CONTAINS);
+    pointFilter.setExpression2( ff.createLiteralExpression(geometry) );
+    return pointFilter;
+  }
+  
+  /**
+   * Bereitet einen "InDerNaehe von" Distance-Filter vor. Das "linke" Attribut der Expression
+   * (das Feature-Attribut, auf das der Filter angewendet wird), wird dabei noch
+   * nicht belegt. Dies geschieht erst bei der Auswertung entsprechend des
+   * jeweiligen Layers
+   *
+   * Wird benoetigt, um mit der Maus einen Punkt zu treffen.
+   *
+   * @param point Geo-Koordinate
+   * @param dist Distanz zum Punkt in Einheiten des Layers
+   * 
+   * TODO SK Auf FilterFactory2 ändern... Beispiel von http://docs.codehaus.org/display/GEOTDOC/Filter+Examples funktioniert erst ab 2.5 ?? Vor dem Distcheck einen BBOX check sollte die geschwindigkeit erhöhen.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  private static GeometryFilterImpl createNearPointFilter(final Point2D point, final Double dist) {
+	  // Filter fuer Envelope zusammenstellen
+	  final Geometry geometry = gf.createPoint(new Coordinate(point.getX(), point.getY()));
+
+	  final DWithinImpl dwithinFilter = (DWithinImpl) ff.createGeometryDistanceFilter(DWithinImpl.GEOMETRY_DWITHIN);
+
+	  dwithinFilter.setDistance(dist);
+
+	  dwithinFilter.setExpression2( ff.createLiteralExpression(geometry) );
+
+	  return dwithinFilter;
+  }
+
+  /**
+   * Prueft, ob es sich bei einem Layer um ein Raster-Layer handelt.
+   * Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+   * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein Attribut
+   * mit dem Namen "GridCoverage" hat.
+   *
+   * SK: Pyramidenlayer aka AbstractGridCoverage2DReader geben auch true zurück.
+   * @param layer zu ueberpruefendes Layer
+   */
+  public static boolean isGridCoverageLayer(MapLayer layer) {
+    final Object layerSourceObject = getLayerSourceObject(layer);
+	return ( layerSourceObject instanceof GridCoverage2D ) || (layerSourceObject  instanceof AbstractGridCoverage2DReader) ;
+//    try {
+//      FeatureCollection fc = layer.getFeatureSource().getFeatures();
+//      // Layer muss genau ein Feature beinhalten
+//      if ( fc == null || fc.size() != 1 )
+//        return false;
+//      // Feature muss genau 1 Attribut mit dem Namen "GridCoverage" haben
+//      FeatureType ftype = fc.getFeatureType();
+//      if ( ftype == null || ftype.getAttributeCount() != 1 || !"GridCoverage".equalsIgnoreCase(ftype.getAttributeType(0).getName()) )
+//        return false;
+//    } catch (Exception err) {
+//    }
+//    return true;
+  }
+
+
+  /**
+   * Liefert das Objekt ({@link GridCoverage2D} oder {@link FeatureCollection}
+   * auf dem ein Layer basiert.
+   * Ein Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+   * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein Attribut
+   * mit Namen "GridCoverage" und Typ {@code GridCoverage2D} hat.
+   * Sind diese Bedingungen erfuellt, wird das {@link GridCoverage2D}
+   * zurueckgegeben, ansonsten die {@link FeatureCollection}.
+   * @param layer ein Layer
+   * @return {@code null}, falls das Objekt nicht ermittelt werden kann (da ein
+   *                       Fehler aufgetreten ist).
+   */
+  public static Object getLayerSourceObject(MapLayer layer) {
+    try {
+      final FeatureSource featureSource = layer.getFeatureSource();
+	FeatureCollection fc = featureSource.getFeatures();
+      // RasterLayer muss genau ein Feature beinhalten
+      // Ist dies nicht der Fall wird die FeatureCollection zurueckgegeben
+      if ( fc == null || fc.size() != 1 )
+        return fc;
+      // FeatureType des RasterLayer muss genau 1 Attribut mit Namen
+      // "GridCoverage"
+      // Ist dies nicht der Fall wird die FeatureCollection zurueckgegeben
+      FeatureType ftype = fc.getFeatureType();
+      if ( ftype == null ||
+           ftype.getAttributeCount() != 1 ||
+           !"GridCoverage".equalsIgnoreCase(ftype.getAttributeType(0).getLocalName()) )
+        return fc;
+      // (Einziges) Feature muss genau 2 Attribute besitzen, wobei das erste vom
+      // Typ Geometry ist und das zweite vom Typ GridCoverage2D
+      // sonst: FeatureCollextion zurueckgeben
+      Feature     f  = fc.features().next();
+      if (  (f.getFeatureType().getTypeName().equals("GridCoverage")) && (f.getNumberOfAttributes() == 3) ) {
+    	  return (AbstractGridCoverage2DReader)f.getAttribute(1);
+      }
+      if ( f.getNumberOfAttributes() != 2 || // TODO Geaendert, da es bei AbstractGridOverageReader 3 sind (SK, 19.08.07)
+           !( f.getAttribute(0) instanceof com.vividsolutions.jts.geom.Geometry ) ||
+           !( f.getAttribute(1) instanceof GridCoverage2D ) )
+        return fc;
+      // Objekt ist ein Raster!
+      return (GridCoverage2D)f.getAttribute(1);
+    } catch (Exception err) {
+    	LOGGER.warn(err);
+      return null;
+    }
+  }
+
+  /**
+   * Should be called when the {@link JMapPane} is not needed no more to help the GarbageCollector
+   *
+   * Removes all {@link JMapPaneListener}s that are registered
+   *
+   *  @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+
+  public void dispose() {
+    if (isDisposed())return;
+
+    this.removeMouseListener(mia);
+    this.removeMouseMotionListener(mia);
+    this.removeMouseWheelListener(mouseWheelZoomListener);
+
+    // Alle mapPaneListener entfernen
+    mapPaneListeners.clear();
+
+    getContext().clearLayerList();
+
+    removeAll();
+    disposed = true;
+  }
+
+  /**
+   * A flag indicating if dispose() has already been called. If true, then further use of this {@link JMapPane} is undefined.
+   */
+  private boolean isDisposed() {
+    return disposed;
+  }
+
+  /**
+   * Werden Rasterlayer waehrend einer PAN Aktion versteckt?
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public boolean isHideRasterLayersDuringPan() {
+    return hideRasterLayersDuringPan;
+  }
+
+  /**
+   * Bestimmt, ob Rasterlayer waehrend einer PAN Aktion versteckt werden soll. Default ist false.
+   * @param hideRasterLayersDuringPan
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setHideRasterLayersDuringPan(boolean hideRasterLayersDuringPan) {
+    this.hideRasterLayersDuringPan = hideRasterLayersDuringPan;
+  }
+
+  public boolean isSetWaitCursorDuringNextRepaint() {
+    return setWaitCursorDuringNextRepaint;
+  }
+
+  /**
+   * When setting this to true, the next repaint of this component will be accompanied by a WAIT Cursor
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setSetWaitCursorDuringNextRepaint(
+      boolean setWaitCursorDuringNextRepaint) {
+    this.setWaitCursorDuringNextRepaint = setWaitCursorDuringNextRepaint;
+  }
+
+  /**
+   * Gibt den "normalen" Cursor zurueck. Dieser kann neben dem "pointer" auch ein Sanduhr-Wartecursor sein.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public Cursor getNormalCursor() {
+    return normalCursor;
+  }
+
+  /**
+   * Setzt den "normalen" Cursor. Dieser kann neben dem default "pointer" z.B. auch ein Sanduhr-Wartecursor sein.
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public void setNormalCursor(Cursor normalCursor) {
+    this.normalCursor = normalCursor;
+  }
+
+//  /**
+//   * Prueft, ob es sich bei einem Layer um ein Raster-Layer handelt.
+//   * Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+//   * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein Attribut
+//   * vom Type {@link org.geotools.feature.type.FeatureAttributeType} hat, welches
+//   * wiederum genau zwei Attribute hat:<br>
+//   * Eines vom Typ {@link org.opengis.spatialschema.geometry.geometry.Polygon}
+//   * und eines vom Typ {@link org.opengis.coverage.grid.GridCoverage}.
+//   * @param layer zu ueberpruefendes Layer
+//   */
+//  public static boolean isGridCoverageLayer(MapLayer layer) {
+//    try {
+//      FeatureCollection fc = layer.getFeatureSource().getFeatures();
+//      // Layer muss genau ein Feature beinhalten
+//      if ( fc == null || fc.size() != 1 )
+//        return false;
+//      // Feature muss genau 1 Attribut vom Typ FeatureAttributeType haben
+//      FeatureType ftype = fc.getFeatureType();
+//      if ( ftype == null || ftype.getAttributeCount() != 1 || !(ftype.getAttributeType(0) instanceof org.geotools.feature.type.FeatureAttributeType) )
+//        return false;
+//      // FeatureAttribute muss genau 2 Atrribute haben
+//      org.geotools.feature.type.FeatureAttributeType atype = (org.geotools.feature.type.FeatureAttributeType)ftype.getAttributeType(0);
+//      if ( atype == null || atype.getAttributeCount() != 2 )
+//        return false;
+//      // Typ des ersten Attributs muss Polygon sein
+//      if ( !com.vividsolutions.jts.geom.Polygon.class.isAssignableFrom( atype.getAttributeType(0).getType() ) )
+//        return false;
+//      // Typ des zweiten Attributs muss GridCoverage sein
+//      if ( !org.opengis.coverage.grid.GridCoverage.class.isAssignableFrom( atype.getAttributeType(1).getType() ) )
+//        return false;
+//
+//    } catch (Exception err) {
+//    }
+//    return true;
+//  }
+
+	/**
+	 * Nuetzlich wenn die Componente gedruckt wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	@Override
+    public void print(Graphics g) {
+        Color orig = getBackground();
+		setBackground(Color.WHITE);
+
+		// wrap in try/finally so that we always restore the state
+		try {
+			super.print(g);
+		} finally {
+			setBackground(orig);
+		}
+	}
+
+}

Added: trunk/src/schmitzm/geotools/gui/JMapPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/JMapPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/JMapPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,2252 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.LayoutManager;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JList;
+import javax.swing.event.MouseInputAdapter;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.grid.GeneralGridRange;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+import org.geotools.coverage.grid.io.AbstractGridFormat;
+import org.geotools.coverage.io.AbstractGridCoverageReader;
+import org.geotools.data.FeatureSource;
+import org.geotools.data.memory.MemoryFeatureCollection;
+import org.geotools.factory.GeoTools;
+import org.geotools.feature.Feature;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureType;
+import org.geotools.filter.AbstractFilter;
+import org.geotools.filter.FilterFactoryImpl;
+import org.geotools.filter.GeometryFilterImpl;
+import org.geotools.filter.spatial.DWithinImpl;
+import org.geotools.geometry.GeneralEnvelope;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.gui.swing.MouseSelectionTracker_Public;
+import org.geotools.gui.swing.event.GeoMouseEvent;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.parameter.Parameter;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.renderer.lite.RendererUtilities;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.resources.image.ImageUtilities;
+import org.geotools.styling.Style;
+import org.opengis.coverage.CannotEvaluateException;
+import org.opengis.filter.Filter;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.expression.Expression;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.FilterUtil;
+import schmitzm.geotools.GTUtil;
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.grid.GridUtil;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import schmitzm.geotools.map.event.GeneralSelectionEvent;
+import schmitzm.geotools.map.event.GridCoverageSelectedEvent;
+import schmitzm.geotools.map.event.GridCoverageValueSelectedEvent;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.map.event.ScaleChangedEvent;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+/**
+ * Diese Klasse erweitert die Geotools-Klasse
+ * {@link org.geotools.gui.swing.JMapPane} um folgende Features:
+ * <ul>
+ * <li>zusaetzliche Maus-Steuerungen:
+ * <ul>
+ * <li><b>Linksklick:</b> ueber {@link #setState(int)} eingestellte Aktion</li>
+ * <li><b>Rechtsklick:</b> Zoom-Out um Faktor 2 (nur wenn Linksklick auf Zoom-In
+ * eingestellt ist)</li>
+ * <li><b>Drag mit linker Maustaste:</b> neuen Karten-Bereich selektieren oder
+ * Features selektieren (siehe {@link #setWindowSelectionState(int)})</li>
+ * <li><b>Drag mit rechter Maustaste:</b> Karten-Bereich verschieben</li>
+ * <li><b>Mausrad:</b> Zoom-In/Out ueber aktueller Position (Faktor 1.2)</li>
+ * </ul>
+ * </li>
+ * <li>Ankoppeln von {@link JMapPaneListener} und Ausloesung diverser
+ * Ereignisse:
+ * <ul>
+ * <li><b>{@link ScaleChangedEvent}:</b> Wird ausgeloest, wenn sich die
+ * Aufloesung der angezeigten Karte aendert</li>
+ * <li><b>{@link MapAreaChangedEvent}:</b> Wird ausgeloest, wenn sich die
+ * Aufloesung angezeigte Karte-Ausschnitt aendert</li>
+ * <li><b>{@link GeneralSelectionEvent}:</b> Wird ausgeloest, wenn der Anwender
+ * einen Bereich aus der Karte ausgewaehlt hat (egal ob dabei gezoomt wurde,
+ * Features/Raster selektiert wurden, oder nicht selektiert wurde)</li>
+ * <li><b>{@link FeatureSelectedEvent}:</b> Wird ausgeloest, wenn der Anwender
+ * Features aus der Karte ausgewaehlt hat</li>
+ * <li><b>{@link GridCoverageSelectedEvent}:</b> Wird ausgeloest, wenn der
+ * Anwender Raster-Bereiche aus der Karte ausgewaehlt hat</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * Sofern eingeschaltet, erfolgt {@linkplain #setHighlight(boolean)
+ * Highlighting} immer auf dem obersten sichtbaren Nicht-Raster-Layer.<br>
+ * Darueberhinaus besteht ueber {@link #getTransform()} Zugriff auf eine
+ * {@linkplain AffineTransform affine Transformation} mit der die aktuellen
+ * Fenster-Koordinaten (z.B. eines <code>MouseEvent</code>) in
+ * Karten-Koordinaten (Latitude/Longitude) umgerechnet werden koennen.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ *         (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class JMapPane extends org.geotools.gui.swing.JMapPane {
+
+	/**
+	 * SK: Nach dem Drag, soll die {@link GeoMapPane} erfahren, dass die Area
+	 * veraendert wurde.
+	 */
+	protected void processDrag(int x1, int y1, int x2, int y2, MouseEvent e) {
+		// MS, 26.05.2008: Zoom-Funktion der Oberklasse soll nur erfolgen, wenn
+		// diese auch fuer die WindowSelection-Aktion eingestellt ist!
+		// Wenn z.B. fuer Links-Klick ZOOM_IN eingestellt ist, fuer
+		// Links-Drag aber SELECT_TOP, dann soll kein Zoom beim
+		// Links-Drag erfolgen!!
+		// Ausnahme: Bei Rechts-Drag (Button 3) soll die Methode
+		// trotzdem aufgerufen werden fuer Pan!
+		if (getState() == ZOOM_IN && getWindowSelectionState() != ZOOM_IN
+				&& e.getButton() != 3)
+			return;
+		super.processDrag(x1, y1, x2, y2, e);
+		fireMapPaneEvent(new MapAreaChangedEvent(this, oldMapArea, mapArea));
+	}
+
+	private static final Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
+
+	/** Logger for debug messages. */
+	protected static final Logger LOGGER = Logger.getLogger(JMapPane.class.getName());
+
+	/** @deprecated ersetzt durch {@link #ZOOM_IN} */
+	public static final int ZoomIn = org.geotools.gui.swing.JMapPane.ZoomIn;
+	/** @deprecated ersetzt durch {@link #ZOOM_OUT} */
+	public static final int ZoomOut = org.geotools.gui.swing.JMapPane.ZoomOut;
+	/** @deprecated ersetzt durch {@link #PAN} */
+	public static final int Pan = org.geotools.gui.swing.JMapPane.Pan;
+	/** @deprecated ersetzt durch {@link #RESET} */
+	public static final int Reset = org.geotools.gui.swing.JMapPane.Reset;
+	/** @deprecated ersetzt durch {@link #SELECT_TOP} */
+	public static final int Select = org.geotools.gui.swing.JMapPane.Select;
+
+	/**
+	 * Flag fuer Modus "Nichts machen".
+	 *
+	 * @see #setWindowSelectionState(int)
+	 * @see #setState(int)
+	 */
+	public static final int NONE = 100;
+	/**
+	 * Flag fuer Modus "Zuruecksetzen". Nicht fuer Window-Auswahl moeglich!
+	 *
+	 * @see #setState(int)
+	 */
+	public static final int RESET = org.geotools.gui.swing.JMapPane.Reset;
+	/**
+	 * Flag fuer Modus "Kartenausschnitt bewegen". Nicht fuer Window-Auswahl
+	 * moeglich!
+	 *
+	 * @see #setState(int)
+	 */
+	public static final int PAN = org.geotools.gui.swing.JMapPane.Pan;
+	/**
+	 * Flag fuer Modus "Heran zoomen".
+	 *
+	 * @see #setWindowSelectionState(int)
+	 * @see #setState(int)
+	 */
+	public static final int ZOOM_IN = org.geotools.gui.swing.JMapPane.ZoomIn;
+	/**
+	 * Flag fuer Modus "Heraus zoomen". Nicht fuer Window-Auswahl moeglich!
+	 *
+	 * @see #setState(int)
+	 */
+	public static final int ZOOM_OUT = org.geotools.gui.swing.JMapPane.ZoomOut;
+	/**
+	 * Flag fuer Modus "Feature-Auswahl auf dem obersten (sichtbaren) Layer".
+	 *
+	 * @see #setWindowSelectionState(int)
+	 * @see #setState(int)
+	 */
+	public static final int SELECT_TOP = org.geotools.gui.swing.JMapPane.Select;
+	/**
+	 * Flag fuer Modus "Feature-Auswahl auf allen (sichtbaren) Layern".
+	 *
+	 * @see #setWindowSelectionState(int)
+	 * @see #setState(int)
+	 */
+	public static final int SELECT_ALL = 103;
+	/**
+	 * Flag fuer Modus
+	 * "Auswahl nur eines Features, das erste sichtbare von Oben".
+	 *
+	 * @see #setWindowSelectionState(int)
+	 * @see #setState(int)
+	 */
+	public static final int SELECT_ONE_FROM_TOP = 104;
+
+	/** Modus fuer Window-Selektion (Default: {@link #ZOOM_IN}). */
+	protected int selState = NONE;
+
+	/**
+	 * Transformation zwischen Fenster-Koordinaten und Karten-Koordinaten
+	 * (lat/lon)
+	 */
+	protected AffineTransform transform = null;
+
+	/**
+	 * Liste der angeschlossenen Listener, die auf Aktionen des MapPanes
+	 * lauschen.
+	 */
+	protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
+
+	protected MouseSelectionTracker_Public selTracker = new MouseSelectionTracker_Public() {
+	  protected void selectionPerformed(int ox, int oy, int px, int py) {
+			// MS, 20.05.2008: In performSelectionEvent(..) wurde das Zoomen
+			// wieder
+			// reingenommen, damit bei Fenster-Auswahl auch gezoomt
+			// wird, wenn der Klick-Zoom (setState(.)) deaktiviert
+			// ist. Wenn dieser jedoch ebenfalls aktiviert ist,
+			// darf an dieser Stelle nicht nochmal gezoomt werden,
+			// da sonst 2x gezoomt wird!!
+			if (getState() != ZOOM_IN)
+				performSelectionEvent(ox, oy, px, py);
+		}
+	};
+
+	private static final FilterFactoryImpl ff = FilterUtil.FILTER_FAC;
+	private static final FilterFactory2 ff2 = FilterUtil.FILTER_FAC2;
+	private static final GeometryFactory gf = FilterUtil.GEOMETRY_FAC;
+
+	/**
+	 * A flag indicating if dispose() was already called. If true, then further
+	 * use of this {@link JMapPane} is undefined.
+	 */
+	private boolean disposed = false;
+
+	/** Listener, der auf das Mausrad lauscht und mit Zoom reagiert */
+	private MouseWheelListener mouseWheelZoomListener;
+
+	// /** Wenn true, dann werden RasterLayer waehrend des Panning auf
+	// setVisible(false) gesetzt **/
+	// protected boolean hideRasterLayersDuringPan = false;
+	// /** Remebers the layers that are hidden during a PAN action **/
+	// protected List<MapLayer> hiddenForPanning = new LinkedList<MapLayer>();
+
+	/**
+	 * Wenn true, dann wird der Cursor waehrend des naechsten Repaint auf die
+	 * WAIT gesetzt.
+	 **/
+	private boolean setWaitCursorDuringNextRepaint;
+	/**
+	 * Defines which Component to change the MouseCursor if in WAIT STATE. If
+	 * unset, only THIS component is used
+	 **/
+	private Component mouseWaitCursorComponent;
+
+	/** Cursor wenn kein Mausbutton gedrueckt wird. default oder SwingUtil.PAN **/
+	private Cursor normalCursor = Cursor
+			.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+
+	/**
+	 * Manuell gesetzter statischer Cursor, unabhaengig von der aktuellen
+	 * MapPane-Funktion
+	 */
+	protected Cursor staticCursor = null;
+
+	private MouseInputAdapter dragWaitCursorListener;
+
+	/** An (transparent) image to paint over the map in the lower right corner **/
+	private BufferedImage mapImage = null;
+
+    /**
+     * Erzeugt ein neues MapPane.
+     * @param layout Layout-Manager fuer die GUI-Komponente (z.B. {@link BorderLayout})
+     * @param isDoubleBuffered siehe Konstruktor der {@linkplain org.geotools.gui.swing.JMapPane#JMapPane(LayoutManager,boolean,GTRenderer,MapContext) Oberklasse}
+     * @param renderer Renderer fuer die graphische Darestellung (z.B. {@link StreamingRenderer})
+     * @param context  Verwaltung der einzelnen Layer (z.B. {@link DefaultMapContext}).
+     */
+     public JMapPane() {
+       this(null, true, null, null);
+     }
+
+      /**
+       * Erzeugt ein neues MapPane.
+	   * @param layout Layout-Manager fuer die GUI-Komponente (z.B. {@link BorderLayout})
+	   * @param isDoubleBuffered siehe Konstruktor der {@linkplain org.geotools.gui.swing.JMapPane#JMapPane(LayoutManager,boolean,GTRenderer,MapContext) Oberklasse}
+	   * @param renderer Renderer fuer die graphische Darestellung (z.B. {@link StreamingRenderer})
+	   * @param context  Verwaltung der einzelnen Layer (z.B. {@link DefaultMapContext}).
+	   */
+	   public JMapPane(LayoutManager layout, boolean isDoubleBuffered, GTRenderer renderer, MapContext context) {
+	     super(
+	         layout != null ? layout : new BorderLayout(),
+	         isDoubleBuffered,
+	         renderer != null ? renderer : new StreamingRenderer(),
+	         context != null ? context : new DefaultMapContext( GTUtil.WGS84 )
+	     );
+
+	     // Dieser Hint sorgt wohl dafuer, dass die Rasterpixel nicht
+	     // interpoliert werden
+	     // Ueber die Methode enableAntiAliasing(boolean) kann das
+	     // rechenintensive AntiAliasing fuer Text un Vectoren eingeschaltet
+	     // werden
+	     RenderingHints hints = ImageUtilities.NN_INTERPOLATION_HINT;
+	     getRenderer().setJava2DHints(hints);
+
+	     // hints.add( new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+	     // RenderingHints.VALUE_ANTIALIAS_OFF ) );
+	     // hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION,
+	     // RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ) );
+	     // hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION,
+	     // RenderingHints.VALUE_INTERPOLATION_BICUBIC ) );
+	     // hints.add( new RenderingHints(RenderingHints.KEY_INTERPOLATION,
+	     // RenderingHints.VALUE_INTERPOLATION_BILINEAR ) );
+	     // hints.add( new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION,
+	     // RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED ) );
+	     // hints.add( new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION,
+	     // RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY ) );
+	     // Map rendererParams = new HashMap();
+	     // rendererParams.put("optimizedDataLoadingEnabled",new Boolean(true) );
+	     // renderer.setRendererHints( rendererParams );
+
+	     setWindowSelectionState(ZOOM_IN);
+	     setState(ZOOM_IN);
+
+	     // Listener, der auf das Mausrad lauscht und mit Zoom reagiert
+	     mouseWheelZoomListener = new MouseWheelListener() {
+	       public void mouseWheelMoved(MouseWheelEvent e) {
+	         performMouseWheelEvent(e);
+	       }
+	     };
+	     this.addMouseWheelListener(mouseWheelZoomListener);
+
+	     /**
+	      * Dieser Listener setzt nach dem Panning (aka Drag) den Maus-Cursor auf
+	      * Wait. Der Rest des Panning wird in der Uberklasse abgewickelt
+	      */
+	     dragWaitCursorListener = new MouseInputAdapter() {
+	       public void mouseReleased(MouseEvent e) {
+	         setWaitCursorDuringNextRepaint = true;
+	       };
+	     };
+	     this.addMouseListener(dragWaitCursorListener);
+
+	     // Hightlight immer auf dem obersten sichtbaren Nicht-Raster-Layer
+	     // MS-01.sc: Der GT-Highlight-Manager arbeitet zu langsam und ohnehin
+	     // nur
+	     // fuer unprojizierte Layer korrekt
+	     // this.setHighlight(true);
+	     this.setHighlight(false);
+	     // MS-01.ec
+
+	     getContext().addMapLayerListListener(new schmitzm.geotools.map.event.MapLayerListAdapter() {
+	       private void resetHighlightLayer() {
+	         if (isHighlight())
+	           setHighlightLayer(getTopVisibleNonGridCoverageLayer());
+	       }
+
+	       public void layerAdded(org.geotools.map.event.MapLayerListEvent e) {
+	         resetHighlightLayer();
+	       }
+
+	       public void layerChanged(org.geotools.map.event.MapLayerListEvent e) {
+	         resetHighlightLayer();
+	       }
+
+	       public void layerMoved(org.geotools.map.event.MapLayerListEvent e) {
+	         resetHighlightLayer();
+	       }
+
+	       public void layerRemoved(org.geotools.map.event.MapLayerListEvent e) {
+	         resetHighlightLayer();
+	       }
+	     });
+
+	     // CRS wird immer vom ersten in die Karte eingefuegten Layer uebernommen
+	     // Wenn noch keine MapArea gesetzt wurde, wird diese vom Layer
+	     // uebernommen
+	     getContext().addMapLayerListListener(new schmitzm.geotools.map.event.MapLayerListAdapter() {
+	       public void layerAdded(org.geotools.map.event.MapLayerListEvent e) {
+	         if (getContext().getLayerCount() == 1) {
+	           CoordinateReferenceSystem crs = null;
+	           // CRS aus Layer ermitteln
+	           try {
+	             crs = e.getLayer().getFeatureSource().getSchema().getDefaultGeometry().getCoordinateSystem();
+	             // wenn noch keine MapArea gesetzt wurde, den
+	             // Ausdehnungsbereich des ersten Layers verwenden, so dass die erste
+	             // Karte komplett angezeigt wird
+	             if (getMapArea() == null) {
+	               Envelope newMapArea = new Envelope(e.getLayer().getFeatureSource().getBounds());
+	               // Kartenbereich um 10% vergroessern, damit
+	               // z.B. auch ein Punkt-Layer, welches nur aus 2 Punnkten
+	               // besteht, sichtbar ist (Punkte liegen sonst
+	               // genau auf dem Rand der angezeigten Flaeche)
+	               newMapArea.expandBy(
+	                   newMapArea.getWidth() * 0.1,
+	                   newMapArea.getHeight() * 0.1);
+	               setMapArea(newMapArea);
+	               // in layerAdded(.) der Oberklasse wird
+	               // mapArea nochmal neu gesetzt, wenn das erste Layer
+	               // eingefuegt wird
+	               // >> hier nur die AreaOfInterest setzen
+	               getContext().setAreaOfInterest(newMapArea,crs);
+	             }
+	           } catch (Exception err) {
+	             LOGGER.warn("CRS could not be determined from map layer. WGS84 used.");
+	             // err.printStackTrace();
+	             crs = DefaultGeographicCRS.WGS84;
+	           }
+	           // CRS dem MapContext zuweisen
+	           try {
+	             getContext().setCoordinateReferenceSystem(crs);
+	           } catch (Exception err) {
+	             LOGGER.error("CRS could not be assigned to map context.");
+	           }
+	         }
+	       }
+	     });
+	    }
+
+	/**
+	 * Get the BufferedImage to use as a flaoting icon in the lower right corner.
+	 * @returns <code>null</code> if the feature is deactivated.
+	 */
+	public BufferedImage getMapImage() {
+		return mapImage;
+	}
+
+	/**
+	 * Set the BufferedImage to use as a flaoting icon in the lower right corner
+	 * @param mapImageIcon <code>null</code> is allowed and deactivates this icon.
+	 */
+	public void setMapImage(BufferedImage mapImage) {
+		this.mapImage = mapImage;
+	}
+
+	/**
+	 * Gibt die optional gesetzte {@link Component} zurueck, deren Cursor auch
+	 * auf WAIT gesetzt werden soll
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 *
+	 * @return null oder {@link Component}
+	 */
+	public Component getWaitCursorComponent() {
+		return mouseWaitCursorComponent;
+	}
+
+	/**
+	 * Setzt eine Componente, deren Cursor zusaetzlich zu THIS noch auf WAIT
+	 * gesetzt wird falls durch dies ueberhaupt durch
+	 * setSetWaitCursorDuringNextRepaint(true) veranlasst wurde
+	 *
+	 * @param parentComponent
+	 *            z.b. der Frame, der diese {@link JMapPane} enhaelt
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void setWaitCursorComponent(Component parentComponent) {
+	  this.mouseWaitCursorComponent = parentComponent;
+	}
+
+
+
+	/**
+	 * Aktualisiert die Karten-Anzeige vollstaendig. Ruft
+	 * {@link JMapPane#setReset(boolean) JMapPane#setReset(true)} auf und
+	 * anschliessend {@link #repaint()}.
+	 *
+	 * <br>
+	 * SK: Der Mauszeiger wird waehrend des repaints auf WAIT gesetzt mittels
+	 * {@link #setWaitCursorDuringNextRepaint(boolean)}
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void refresh() {
+
+		// SK: Added by SK, 27.09.2007
+		// Durch den reset ist das repaint immer etwas aufwaendiger. Der Cursor
+		// wechselt dann auf WAIT
+		setWaitCursorDuringNextRepaint(true);
+
+		setReset(true);
+		repaint();
+	}
+
+	/**
+	 * Aktiviert oder Deaktiviert das AntiAliasing for diese {@link JMapPane}.
+	 * AntiALiasing ist besonders fuer Textbeschriftung sehr schoen, verbraucht
+	 * aber auch mehr Performance.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void setAntiAliasing(final boolean aa) {
+		LOGGER.info("Setting AntiAliasing for this JMapPane to " + aa);
+		RenderingHints java2DHints = getRenderer().getJava2DHints();
+		if (java2DHints == null)
+			java2DHints = GeoTools.getDefaultHints();
+		java2DHints.put(RenderingHints.KEY_ANTIALIASING,
+				aa ? RenderingHints.VALUE_ANTIALIAS_ON
+						: RenderingHints.VALUE_ANTIALIAS_OFF);
+		java2DHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
+				aa ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
+						: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+		java2DHints.put(RenderingHints.KEY_RENDERING,
+				aa ? RenderingHints.VALUE_RENDER_QUALITY
+						: RenderingHints.VALUE_RENDER_SPEED);
+		getRenderer().setJava2DHints(java2DHints);
+	}
+
+	/**
+	 * Setzt den Kartenausschnitt auf die Ausdehnung eines bestimmten Layers.
+	 * Macht nichts, wenn {@code null} uebergeben wird.
+	 *
+	 * <br>
+	 * A refresh of the map is NOT called.
+	 *
+	 * @param layer
+	 *            ein Layer
+	 */
+	public void zoomToLayer(MapLayer layer) {
+		if (layer == null)
+			return;
+		// This action ususally takes some time..
+		setWaitCursorDuringNextRepaint = true;
+		try {
+
+			// BB umrechnen von Layer-CRS in Map-CRS
+			final Envelope mapAreaNew = JTSUtil.transformEnvelope(layer
+					.getFeatureSource().getBounds(), layer.getFeatureSource()
+					.getSchema().getDefaultGeometry().getCoordinateSystem(),
+					getContext().getCoordinateReferenceSystem());
+			// Kartenbereich um 10% vergroessern, damit z.B. auch ein
+			// Punkt-Layer,
+			// welches nur aus 2 Punnkten besteht, sichtbar ist (Punkte liegen
+			// sonst
+			// genau auf dem Rand der angezeigten Flaeche)
+			mapAreaNew.expandBy(mapAreaNew.getWidth() * 0.1, mapAreaNew
+					.getHeight() * 0.1);
+			setMapArea(mapAreaNew);
+		} catch (Exception err) {
+			LOGGER.warn("Zoom to layer did not terminate correctly", err);
+		}
+	}
+
+	/**
+	 * Zooms the {@link JMapPane} to the {@link Envelope} of a layer.
+	 *
+	 * <br>
+	 * A refresh of the map is not done automatically
+	 *
+	 * @param index
+	 *            Index of the {@link MapLayer} in the {@link MapContext} (from
+	 *            back to top)
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void zoomToLayer(int index) {
+		zoomToLayer(getContext().getLayer(index));
+	}
+
+	/**
+	 * Zooms the {@link JMapPane} to the {@link Envelope} of the selected layer.
+	 * The layer is selected by the idx, counting from front to back, like
+	 * humans would expect in a {@link JList}
+	 *
+	 * <br>
+	 * A refresh of the map is not done automatically
+	 *
+	 *
+	 *
+	 * @param index
+	 *            Reverse index of the {@link MapLayer} in the
+	 *            {@link MapContext}
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void zoomToLayerIdxReverse(int index) {
+		zoomToLayer(getContext().getLayerCount() - 1 - index);
+	}
+
+	/**
+	 * Liefert die Anzahl der Einheiten, die ein Bildschirm-Pixel darstellt. Die
+	 * Einheit ist die Grundeinheit des CRS
+	 */
+	public double getScale() {
+		if (getWidth() == 0 || getMapArea() == null)
+			return 0.0;
+		return getMapArea().getWidth() / getWidth();
+	}
+
+	/**
+	 * Liefert oberste Layer (sichtbar oder unsichtbar).
+	 */
+	public MapLayer getTopLayer() {
+		int count = getContext().getLayerCount();
+		return count > 0 ? getContext().getLayer(count - 1) : null;
+	}
+
+	/**
+	 * Liefert oberste sichtbare Layer.
+	 */
+	public MapLayer getTopVisibleLayer() {
+		for (int i = getContext().getLayerCount() - 1; i >= 0; i--) {
+			MapLayer layer = getContext().getLayer(i);
+			if (layer.isVisible())
+				return layer;
+		}
+		return null;
+	}
+
+	/**
+	 * Liefert oberste sichtbare Raster-Layer.
+	 */
+	public MapLayer getTopVisibleGridCoverageLayer() {
+		for (int i = getContext().getLayerCount() - 1; i >= 0; i--) {
+			MapLayer layer = getContext().getLayer(i);
+			if (layer.isVisible() && isGridCoverageLayer(layer))
+				return layer;
+		}
+		return null;
+	}
+
+	/**
+	 * Liefert oberste sichtbare Nicht-Raster-Layer.
+	 */
+	public MapLayer getTopVisibleNonGridCoverageLayer() {
+		for (int i = getContext().getLayerCount() - 1; i >= 0; i--) {
+			MapLayer layer = getContext().getLayer(i);
+			if (layer.isVisible() && !isGridCoverageLayer(layer))
+				return layer;
+		}
+		return null;
+	}
+
+	/**
+	 * Liefert unterste Layer (sichtbar oder unsichtbar).
+	 */
+	public MapLayer getBottomLayer() {
+		return getContext().getLayerCount() > 0 ? getContext().getLayer(0)
+				: null;
+	}
+
+	/**
+	 * Setzt den Modus fuer Window-Selektion. Default ist {@link #ZOOM_IN}.
+	 *
+	 *
+	 * <ul>
+	 * <li>{@link #ZOOM_IN}: Zoom auf selektierten Bereich</li>
+	 * <li>{@link #SELECT_TOP}: Auswahl der Features im selektierten Bereich des
+	 * <b>obersten</b> (sichtbaren) Layers</li>
+	 * <li>{@link #SELECT_ALL} Auswahl der Features im selektierten ueber alle
+	 * Layer</li>
+	 * <li>{@link #NONE} Nichts machen</li>
+	 * </ul>
+	 *
+	 * @param newSelState
+	 *            Modus fuer Window-Selektion
+	 */
+	public void setWindowSelectionState(final int newSelState) {
+		if (newSelState != NONE && newSelState != ZOOM_IN
+				&& newSelState != SELECT_TOP && newSelState != SELECT_ALL
+				&& newSelState != SELECT_ONE_FROM_TOP)
+			throw new IllegalArgumentException(
+					"Unknown selection state for window selection!");
+
+		// Den selTracker bei Wechsel zu NONE deaktivieren (SK)
+		if ((newSelState == NONE) && (selState != NONE)) {
+			this.removeMouseListener(selTracker);
+		} else
+		// Den selTracker bei Wechsel von NONE aktivieren (SK)
+		if ((newSelState != NONE) && (selState == NONE)) {
+			this.addMouseListener(selTracker);
+		}
+
+		this.selState = newSelState;
+
+		// Je nach Aktion den Cursor umsetzen
+		updateCursor();
+	}
+
+	/**
+	 * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
+	 * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
+	 * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
+	 * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
+	 * {@code null} als Parameter uebergeben werden
+	 *
+	 * @param cursor
+	 *            Cursor
+	 */
+	public void setStaticCursor(Cursor cursor) {
+		this.staticCursor = cursor;
+		if (cursor != null)
+			super.setCursor(cursor);
+	}
+
+	/**
+	 * Liefert den statisch eingestellten Cursor, der unabhaengig von der
+	 * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
+	 *
+	 * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
+	 *         Cursor automatisch je nach MapPane-Aktion eingestellt wird.
+	 */
+	public Cursor getStaticCursor() {
+		return this.staticCursor;
+	}
+
+	/**
+	 * Abhaengig von selState wird der Cursor gesetzt
+	 */
+	public void updateCursor() {
+		// wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
+		// von der aktuellen Aktion
+		if (this.staticCursor != null) {
+			setCursor(staticCursor);
+			return;
+		}
+		// Je nach Aktion den Cursor umsetzen
+		switch (this.selState) {
+		case SELECT_TOP:
+		case SELECT_ONE_FROM_TOP:
+		case SELECT_ALL:
+			setCursor(SwingUtil.CROSSHAIR_CURSOR);
+			break;
+		case ZOOM_IN:
+			setCursor(SwingUtil.ZOOMIN_CURSOR);
+			break;
+		case ZOOM_OUT:
+			setCursor(SwingUtil.ZOOMOUT_CURSOR);
+			break;
+		default:
+			setCursor(getNormalCursor());
+			break;
+		}
+	}
+
+	/**
+	 * Liefert den Modus fuer Window-Selektion.
+	 *
+	 * @see #setWindowSelectionState(int)
+	 */
+	public int getWindowSelectionState() {
+		return this.selState;
+	}
+
+	/**
+	 * Fuegt der Map einen Listener hinzu.
+	 *
+	 * @param l
+	 *            neuer Listener
+	 */
+	public void addMapPaneListener(JMapPaneListener l) {
+		mapPaneListeners.add(l);
+	}
+
+	/**
+	 * Entfernt einen Listener von der Map.
+	 *
+	 * @param l
+	 *            zu entfernender Listener
+	 */
+	public void removeMapPaneListener(JMapPaneListener l) {
+		mapPaneListeners.remove(l);
+	}
+
+	/**
+	 * Propagiert ein Ereignis an alle angeschlossenen Listener.
+	 *
+	 * @param e
+	 *            Ereignis
+	 */
+	protected void fireMapPaneEvent(JMapPaneEvent e) {
+		for (JMapPaneListener l : mapPaneListeners)
+			l.performMapPaneEvent(e);
+	}
+
+	/**
+	 * Konvertiert die Maus-Koordinaten (relativ zum <code>JMapPane</code>) in
+	 * Karten-Koordinaten.
+	 *
+	 * @param e
+	 *            Maus-Ereignis
+	 */
+	public static Point2D getMapCoordinatesFromEvent(MouseEvent e) {
+		// aktuelle Geo-Position aus GeoMouseEvent ermitteln
+		if (e != null && e instanceof GeoMouseEvent)
+			try {
+				return ((GeoMouseEvent) e).getMapCoordinate(null).toPoint2D();
+			} catch (Exception err) {
+				err.printStackTrace();
+			}
+
+		// aktuelle Geo-Position ueber Transformation des JMapPane berechnen
+		if (e != null && e.getSource() instanceof JMapPane) {
+			AffineTransform at = ((JMapPane) e.getSource()).getTransform();
+			if (at != null)
+				return at.transform(e.getPoint(), null);
+		}
+
+		return null;
+	}
+
+	/**
+	 * Verarbeitet die Selektion eines Karten-Ausschnitts. Erzeugt immer ein
+	 * {@link GeneralSelectionEvent} fuer den ausgewaehlten Bereich. Wurden
+	 * Features oder Raster selektiert, werden zudem
+	 * {@link FeatureSelectedEvent FeatureSelectedEvents} (bzw.
+	 * GridCoverageSelectedEvents GridCoverageSelectedEvents) ausgeloest.
+	 *
+	 * @param ox
+	 *            X-Koordinate der VON-Position
+	 * @param oy
+	 *            Y-Koordinate der VON-Position
+	 * @param px
+	 *            X-Koordinate der BIS-Position
+	 * @param py
+	 *            Y-Koordinate der BIS-Position
+	 */
+	protected void performSelectionEvent(int ox, int oy, int px, int py) {
+		if (getContext().getLayerCount() == 0)
+			return;
+
+		// keine wirkliche Selektion, sondern nur ein Klick
+		if (ox == px || oy == py)
+			return;
+
+		////********************************************************************
+		// ********
+		// // SK: Wenn der ausgewaehlte Bereich kleiner als 12x12 pixel ist,
+		// dann war es wohl ein Irrtum ("Verklickt")
+		// // und wird ignoriert. Zu starkes Zoomen (z.B. auf 2x2 pixel) kostet
+		// sehr viel Rechenzeit.
+		////********************************************************************
+		// ********
+		// if ( (Math.abs(ox-px)>12) && (Math.abs(oy-py)>12) ){
+		//
+		////********************************************************************
+		// ********
+		// // Changed by SK:
+		// // performing the zoom and/or pan by recalculating the mapArea
+		// // important and new here: don't zoom in/out more that the min/max
+		// scale!
+		// // n.b.: zoom is not only performed here, but also and with the mouse
+		// wheel and setMapArea
+		////********************************************************************
+		// ********
+		// Coordinate centre = getMapArea().centre();
+		// double zlevel = 2.6;
+		//
+		// Coordinate ll = new Coordinate(centre.x - (getMapArea().getWidth() /
+		// zlevel), centre.y
+		// - (getMapArea().getHeight() / zlevel));
+		// Coordinate ur = new Coordinate(centre.x + (getMapArea().getWidth() /
+		// zlevel), centre.y
+		// + (getMapArea().getHeight() / zlevel));
+		//
+		// final Envelope newMapArea = new Envelope(ll, ur);
+		//
+		// // Hier passiert die Ueberpruefung und ggf Anpassung der Scale
+		// setMapArea( bestAllowedMapArea( newMapArea ));
+		//
+		// repaint();
+		// return;
+		// }
+
+		// Fenster-Koordinaten in Map-Koordinaten umwandeln
+		Envelope env = tranformWindowToGeo(ox, oy, px, py);
+
+		// Generelles Event ausloesen
+		fireMapPaneEvent(new GeneralSelectionEvent(this, env));
+
+		int selectState = getWindowSelectionState();
+		switch (selectState) {
+		case ZOOM_IN: // Karte neu setzen
+			this.setMapArea(env);
+			refresh(); // WICHTIG!! Damit die veraenderte Form beruecksichtigt
+			// wird!?
+			break;
+		case SELECT_TOP:
+		case SELECT_ONE_FROM_TOP:
+		case SELECT_ALL: // Features selektieren
+			boolean featuresFound = findFeaturesAndFireEvents(
+					createBoundingBoxFilter(env), selectState, env);
+			if (selectState == SELECT_ALL || !featuresFound)
+				findGridCoverageSubsetsAndFireEvents(env, selectState);
+			break;
+		}
+	}
+
+	/**
+	 * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
+	 *
+	 * @param e
+	 *            Mausrad-Event
+	 */
+	protected void performMouseWheelEvent(MouseWheelEvent e) {
+		if (getContext().getLayerCount() == 0)
+			return;
+
+		int units = e.getUnitsToScroll();
+		// Positiver Wert --> Zoom in --> Faktor < 1
+		// Negativer Wert --> Zoom out --> Faktir > 1
+
+		// SK: 9.9.2007 zoom jetzt wie bei GoogleEarth
+		double zFactor = units > 0 ? 1.3 : 1 / 1.3;
+		// vorher double zFactor = units > 0 ? 1/1.2 : 1.2;
+
+		// Fenster-Koordinaten zu Karten-Koordinaten transformieren
+		Point2D mapCoord = getTransform().transform(e.getPoint(), null);
+		// Relative Position des Mauszeigers zum Kartenausschnitt
+		// -> Nach Zoom soll dieselbe Kartenposition unterhalb des Mauszeigers
+		// erscheinen, wie vor dem Zoom
+		double relX = (mapCoord.getX() - getMapArea().getMinX())
+				/ getMapArea().getWidth();
+		double relY = (mapCoord.getY() - getMapArea().getMinY())
+				/ getMapArea().getHeight();
+
+		// Neuen Karten-Ausschnitt berechnen
+		Coordinate ll = new Coordinate(mapCoord.getX()
+				- getMapArea().getWidth() * relX * zFactor, mapCoord.getY()
+				- getMapArea().getHeight() * relY * zFactor);
+		Coordinate ur = new Coordinate(mapCoord.getX()
+				+ getMapArea().getWidth() * (1 - relX) * zFactor, mapCoord
+				.getY()
+				+ getMapArea().getHeight() * (1 - relY) * zFactor);
+		setMapArea(new Envelope(ll, ur));
+
+		setWaitCursorDuringNextRepaint(true);
+		repaint();
+	}
+
+	/**
+	 * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
+	 *
+	 * @param ox
+	 *            X-Koordinate der VON-Position
+	 * @param oy
+	 *            Y-Koordinate der VON-Position
+	 * @param px
+	 *            X-Koordinate der BIS-Position
+	 * @param py
+	 *            Y-Koordinate der BIS-Position
+	 */
+	public Envelope tranformWindowToGeo(int ox, int oy, int px, int py) {
+		AffineTransform at = getTransform();
+		Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
+		Point2D geoP = at.transform(new Point2D.Double(px, py), null);
+		return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
+	}
+
+	/**
+	 * Transformiert eine Fenster-Koordinate in eine Geo-Koordinate.
+	 *
+	 * @param x
+	 *            X-Koordinate
+	 * @param y
+	 *            Y-Koordinate
+	 */
+	public Point2D tranformWindowToGeo(int x, int y) {
+		AffineTransform at = getTransform();
+		return at.transform(new Point2D.Double(x, y), null);
+	}
+
+	/**
+	 * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
+	 * neu.
+	 */
+	protected void resetTransform() {
+		if (getMapArea() == null || getWidth() == 0 || getHeight() == 0)
+			return;
+		this.transform = new AffineTransform(
+		// Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
+				// nach rechts (Osten) hin groesser
+				// --> positive Verschiebung
+				getMapArea().getWidth() / getWidth(),
+				// keine Verzerrung
+				0.0, 0.0,
+				// Waehrend die Fenster-Koordinaten nach unten hin groesser
+				// werden,
+				// werden Latitude-Koordinaten nach Sueden hin keiner
+				// --> negative Verschiebung
+				-getMapArea().getHeight() / getHeight(),
+				// Die Longitude-Koordinaten werden nach Osten hin groesser
+				// --> obere linke Ecke des Fensters hat also den Minimalwert
+				getMapArea().getMinX(),
+				// Die Latitude-Koordinaten werden nach Norden hin groesser
+				// --> obere linke Ecke des Fensters hat also den Maximalwert
+				getMapArea().getMaxY());
+	}
+
+	/**
+	 * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
+	 * Karten-Koordinaten (Lat/Lon) umzurechnen.
+	 *
+	 * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
+	 *         noch keine Karte angezeigt wird
+	 */
+	public AffineTransform getTransform() {
+		// Workaround: Obwohl eine Karte gesetzt ist, kann es sein, dass die
+		// Transformation noch nicht gesetzt ist (da noch kein
+		// setMapArea(.)-Aufruf stattgefunden hat!)
+		if (transform == null)
+			resetTransform();
+		// nur Kopie der Transformation zurueckgeben!
+		if (transform != null)
+			return new AffineTransform(transform);
+		return null;
+	}
+
+	/**
+	 * Setzt die sichtbare Karte. Danach wird die {@linkplain AffineTransform
+	 * Transformation} zwischen Fenster-Koordinaten und Karten-Koordinaten neu
+	 * berechnet.<br>
+	 * Loest ein {@link ScaleChangedEvent aus}
+	 *
+	 * {@link #setMapArea(Envelope)} wird ignoriert, falls durch die neue
+	 * MapArea ein nicht gueltiger Massstab entstehen wuerde UND die bisherige
+	 * maparea != null ist
+	 *
+	 * @param env
+	 *            neuer Kartenausschnitt
+	 * @see #resetTransform()
+	 * @see #getTransform()
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	@Override
+	public void setMapArea(Envelope env) {
+		//**********************************************************************
+		// ******
+		// Ueber die Funktionen setMaxZoomScale und setMinZoomScale kann der
+		// geotools JMapPane ein gueltiger Massstabsbereich gesetzt werden.
+		// Dieser
+		// wird hier ueberprueft. (SK)
+		//**********************************************************************
+		// ******
+		if (super.getMapArea() != null) {
+			env = bestAllowedMapArea(env);
+		}
+
+		double oldScale = getScale();
+		Envelope oldEnv = getMapArea();
+
+		super.setMapArea(env);
+
+		resetTransform();
+		double newScale = getScale();
+		Envelope newEnv = getMapArea();
+
+		if (oldScale != newScale)
+			fireMapPaneEvent(new ScaleChangedEvent(this, oldScale, newScale));
+		if (oldEnv == null && newEnv != null || oldEnv != null
+				&& !oldEnv.equals(newEnv))
+			fireMapPaneEvent(new MapAreaChangedEvent(this, oldEnv, newEnv));
+	}
+
+	/**
+	 * Reagiert auf Linksklick mit der ueber {@link #setState(int)}eingestellten
+	 * Aktion und auf Rechtsklick mit Zoom-Out (sofern {@link #ZOOM_IN}-State
+	 * fuer Linksklick eingestellt). Alle anderen Klicks werden ignoriert.
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void mouseClicked(MouseEvent e) {
+		// wenn noch kein Layer dargestellt wird, nichts machen
+		if (getContext().getLayerCount() == 0)
+			return;
+
+		// double oldScale = getScale();
+		// Envelope oldEnv = getMapArea();
+
+		switch (e.getButton()) {
+		// Linksklick --> Eingestellte Funktion
+		case MouseEvent.BUTTON1: // Feature-Auswahl nicht ueber die
+			// super-Funktion
+			int state = getState();
+
+			if (state == SELECT_TOP || state == SELECT_ONE_FROM_TOP
+					|| state == SELECT_ALL) {
+
+				/**
+				 * BEGIN StefanChange Dieser Block findet sichtbare Features im
+				 * Umkreis des Mausklicks und kümmert sich selbst um das
+				 * verschicken der Events. Dabei wird z.Zt. immer eine
+				 * FeaterCollection mit nur einem Feature verschickt. Und zwar
+				 * jenes Feature, welches am nächsten liegt.
+				 */
+
+				// Fenster-Koordinate in Geo-Koordinate umwandelt
+				Point2D geoCoord = getTransform().transform(e.getPoint(), null);
+				com.vividsolutions.jts.geom.Point mousePoint = new GeometryFactory()
+						.createPoint(new Coordinate(geoCoord.getX(), geoCoord
+								.getY()));
+
+				// Rechnet 10 Pixel in die Einheit der Karte um.
+				final Point pAtDistance = new Point(
+						(int) e.getPoint().getX() + 10, (int) e.getPoint()
+								.getY());
+				final Point2D geoCoordAtDistance = getTransform().transform(
+						pAtDistance, null);
+				final Double dist = Math.abs(geoCoordAtDistance.getX()
+						- geoCoord.getX());
+
+				final Envelope envelope = new Envelope(geoCoord.getX(),
+						geoCoord.getX(), geoCoord.getY(), geoCoord.getY());
+
+				Hashtable<MapLayer, FeatureCollection> result = findVisibleFeatures(
+						createNearPointFilter(geoCoord, dist), state, envelope);
+
+				boolean featuresFound = false;
+				// Events ausloesen fuer jedes Layer
+				for (Enumeration<MapLayer> element = result.keys(); element
+						.hasMoreElements();) {
+
+					MapLayer layer = element.nextElement();
+					FeatureCollection fc = result.get(layer);
+					FeatureCollection fcOne;
+
+					if (fc != null && !fc.isEmpty()) {
+
+						if (fc.size() > 1) {
+							// Hier werden alle Features weggeschmissen, bis auf
+							// das raeumlich naechste.
+
+							Feature nearestFeature = null;
+							Double nearestDist = 0.0;
+
+							Iterator<Feature> fcIt = fc.iterator();
+							while (fcIt.hasNext()) {
+								Feature f = fcIt.next();
+								Object obj = f.getAttribute(0);
+
+								if (obj instanceof Geometry) {
+									// Bei Punkten ja noch ganz einfach:
+									Geometry featureGeometry = (Geometry) obj;
+									double distance = featureGeometry
+											.distance(mousePoint);
+
+									if ((nearestFeature == null)
+											|| (distance < nearestDist)) {
+										nearestFeature = f;
+										nearestDist = distance;
+									}
+								} else {
+									LOGGER
+											.info("!obj instanceof Geometry      obj = "
+													+ obj.getClass()
+															.getSimpleName());
+								}
+
+							}
+							fc.close(fcIt);
+
+							fcOne = new MemoryFeatureCollection(fc
+									.getFeatureType());
+							fc.clear();
+							fcOne.add(nearestFeature);
+						} else {
+							fcOne = fc;
+						}
+						fireMapPaneEvent(new FeatureSelectedEvent(
+								JMapPane.this, layer, envelope, fcOne));
+						featuresFound = true;
+					}
+				}
+
+				// TODO TODO TODO SK: DIese trennung: erst Feature layers, dann
+				// grid layers check ist doof!
+				// if (state == SELECT_ALL || !result.isEmpty())
+
+				// LOGGER.info("state= "+state+"     featuresFound = "+
+				// featuresFound);
+				if (state == SELECT_ALL || !featuresFound) {
+					// LOGGER.info("   findGridCoverageValuesAndFireEvents");
+					findGridCoverageValuesAndFireEvents(geoCoord, state);
+				}
+
+				break;
+			}
+			super.mouseClicked(e);
+			return; // was: breka, aber wir brauchen ja nicht das
+			// setMapArea(mapArea)
+
+			// Rechtsklick --> Zoom-Out
+		case MouseEvent.BUTTON3:
+			int oldState = getState();
+			// MS: ist doch eleganter, wenn IMMER mit Rechtsklick raus-gezoomt
+			// werden kann!
+			// // ZOOM_OUT nur wenn Links-Klick auf ZOOM_IN eingestellt ist)
+			// if ( oldState != ZOOM_IN )
+			// return;
+			setState(ZOOM_OUT);
+			super.mouseClicked(e);
+			setState(oldState);
+			break;
+		// Sonst nix
+		default:
+			return;
+		}
+
+		// In super.mouseClicked(.) wird (nach Zoom) die MapArea neu gesetzt,
+		// aber
+		// leider ohne setMapArea(.) aufzurufen, sondern indem einfach die
+		// Variable
+		// 'mapArea' neu belegt wird
+		// --> Transform muss neu berechnet werden
+		// --> Aenderung der Aufloesung propagieren
+
+		setMapArea(mapArea);
+		// resetTransform();
+		// double newScale = getScale();
+		// Envelope newEnv = getMapArea();
+		// if ( oldScale != newScale )
+		// fireMapPaneEvent( new ScaleChangedEvent(this,oldScale,newScale) );
+		// if ( oldEnv == null && newEnv != null || oldEnv != null &&
+		// !oldEnv.equals( newEnv ) )
+		// fireMapPaneEvent( new MapAreaChangedEvent(this,oldEnv,newEnv) );
+	}
+
+	/**
+	 * In <code>super.paintComponent(.)</code> wird unter gewissen Umstaenden
+	 * die MapArea neu gesetzt, aber leider ohne {@link #setMapArea(Envelope)}
+	 * aufzurufen, sondern indem einfach die Variable <code>mapArea</code> neu
+	 * belegt wird. Damit auch die Transformation an den neuen Kartenbereich
+	 * angepasst wird, muss diese Methode ueberschrieben werden.
+	 * <p>
+	 * Neu von SK: Ich habe in der Geotools
+	 * {@link org.geotools.gui.swing.JMapPane} die noetigen variablen protected
+	 * gemacht, um hier schon festzustellen, ob der aufruf von
+	 * super.paintComponent das eigentliche aussehen der Karte veraendern wird.
+	 * Die Methode paintComponent wird naemlich bei jeder Bewegung der Maus
+	 * aufgerufen, und meistens wird super.paintComponent nur ein gebufferted
+	 * Image auf den Ausschnitt zeichnen. Falls die "seriouseChange" zu erwarten
+	 * ist, wird evt. der Mauscursor auf WAIT gesetzt, und auf jeden Fall die
+	 * Methode {@link #resetTransform()} aufgerufen.<br>
+	 * Sinn des Ganzen? Ich habe versucht etwas Performance zu gewinnen, da
+	 * sonst bei jeder Mausbewegung die die {@link AffineTransform} durch
+	 * {@link #resetTransform()} neu berechnet wurde.
+	 *
+	 * @param g
+	 *            Graphics
+	 * @see #resetTransform()
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	protected void paintComponent(Graphics g) {
+		Cursor oldCursor = null;
+		boolean seriouseChange = false;
+
+		if (!getBounds().equals(oldRect) || reset)
+			seriouseChange = true;
+		else if (!mapArea.equals(oldMapArea))
+			seriouseChange = true;
+
+		if (seriouseChange) {
+			if (setWaitCursorDuringNextRepaint) {
+
+				if (getWaitCursorComponent() != null) {
+					// Der Cursor soll auch in einer anderen Component geaendert
+					// werden..
+					oldCursor = getWaitCursorComponent().getCursor();
+					getWaitCursorComponent().setCursor(WAIT_CURSOR);
+				}
+
+				// Den Cursor extra nochmal in dieser COmponente aendern
+				setCursor(WAIT_CURSOR);
+			}
+		}
+
+		try {
+			super.paintComponent(g);
+		} catch (Exception e) {
+			/**
+			 * I only need to catch UnknownHostException.. well...
+			 */
+			LOGGER.info(e);
+			LOGGER
+					.info("Probably we are offline and reference some online SVGs? ");
+		}
+		if (seriouseChange) {
+			resetTransform();
+			if (setWaitCursorDuringNextRepaint) {
+				setWaitCursorDuringNextRepaint = false;
+				if (oldCursor != null)
+					getWaitCursorComponent().setCursor(oldCursor);
+				updateCursor(); // Den Cursor in dieser Componente immer wieder
+				// herstellen
+			}
+
+			/**
+			 * Paint an icon in the lower right corner.
+			 * FeatureRequest: [#717] MapIcon has long been disabled and should come back
+			 */
+			if (mapImage != null && baseImage != null) {
+				baseImage.getGraphics().drawImage(mapImage, baseImage.getWidth()-mapImage.getWidth()-10, baseImage.getHeight()-mapImage.getHeight()-10, this);
+				// Repaint with the cached image (fast) which now contains the logo
+				super.paintComponent(g);
+			}
+		}
+
+
+	}
+
+	/**
+	 * Testet (anhand der Bounding-Box), ob das Objekt eines Layers eine andere
+	 * Bounding-Box schneidet. Die Bounding-Box des Layer-Objekts wird zunaechst
+	 * in das CRS des MapPanes umgerechnets.
+	 *
+	 * @param layer
+	 *            ein Layer
+	 * @param env
+	 *            Bounding-Box in CRS des MapPane
+	 */
+	private boolean gridLayerIntersectsEnvelope(MapLayer layer, Envelope env) {
+		try {
+			// BB des Layers umrechnen von Layer-CRS in Map-CRS
+			Envelope bounds_MapCRS = JTSUtil.transformEnvelope(layer
+					.getFeatureSource().getBounds(), layer.getFeatureSource()
+					.getSchema().getDefaultGeometry().getCoordinateSystem(),
+					getContext().getCoordinateReferenceSystem());
+
+			// TODO warum kann bounds_MapCRS == null sein ?? ?SK fragt martin???
+			if (bounds_MapCRS == null)
+				return false;
+
+			return bounds_MapCRS.intersects(env);
+		} catch (Exception err) {
+			return false;
+		}
+	}
+
+	/**
+	 * Testet (anhand der Features), ob das Objekt eines Layers eine
+	 * Bounding-Box schneidet.
+	 *
+	 * @param layer
+	 *            ein Layer
+	 * @param env
+	 *            Bounding-Box in CRS des MapPane
+	 */
+	private boolean featureLayerIntersectsEnvelope(MapLayer layer, Envelope env) {
+		try {
+			// BB umrechnen von Map-CRS in Layer-CRS
+			Envelope env_LayerCRS = JTSUtil.transformEnvelope(env, getContext()
+					.getCoordinateReferenceSystem(), layer.getFeatureSource()
+					.getSchema().getDefaultGeometry().getCoordinateSystem());
+			GeometryFilterImpl filter = createBoundingBoxFilter(env_LayerCRS);
+			Expression geometry = ff.createAttributeExpression(layer
+					.getFeatureSource().getSchema().getDefaultGeometry()
+					.getLocalName());
+			filter.setExpression1(geometry);
+			return !layer.getFeatureSource().getFeatures(filter).isEmpty();
+		} catch (Exception err) {
+			return false;
+		}
+	}
+
+	/**
+	 * Ermittelt alle sichtbaren Features, die einen Filter erfuellen. Beim
+	 * Modus {@link #SELECT_TOP} wird nur das oberste sichtbare Layer
+	 * durchsucht. Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer
+	 * durchsucht.
+	 *
+	 * @see #findFeatures(GeometryFilterImpl, int, Envelope)
+	 *
+	 * @param distancefilter
+	 *            Filter
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls der Filter {@code null} ist
+	 */
+	protected Hashtable<MapLayer, FeatureCollection> findVisibleFeatures(
+			GeometryFilterImpl distancefilter, int mode, Envelope env) {
+		Hashtable<MapLayer, FeatureCollection> result = new Hashtable<MapLayer, FeatureCollection>();
+		if (distancefilter == null)
+			return result;
+
+		// Je nach Modus: Alle oder nur das oberste Layer
+		MapContext context = getContext();
+		MapLayer[] layerList = context.getLayers();
+		for (int i = layerList.length - 1; i >= 0; i--) {
+			MapLayer layer = layerList[i];
+			if (!layer.isVisible())
+				continue;
+
+			// Bei einem Raster-Layer, das die BB schneidet, abbrechen, wenn nur
+			// im
+			// obersten (sichtbaren) Layer gesucht wird.
+			// Ansonsten Raster-Layer einfach uebergehen.
+			if (isGridCoverageLayer(layer)) {
+				if (mode == SELECT_TOP
+						&& gridLayerIntersectsEnvelope(layer, env))
+					break;
+				continue;
+			}
+
+			// Filter an Layer koppeln
+			// WICHTIG: Dies darf erst geschehen, NACHDEM das
+			// Schleifen-Abbruch-Kriterium
+			// fuer die Raster-Layer geprueft wurde!!
+			// Werden folgende Zeilen naemlich auf das FeatureSource des
+			// Raster-Layers angewandt, dann "bricht" der Filter "irgendwie"
+			// zusammen und auch die ZUVOR gefilterten FeatureCollections,
+			// sind ploetzlich EMPTY!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			final FeatureSource featureSource = layer.getFeatureSource();
+			Expression geometry = ff.createAttributeExpression(featureSource
+					.getSchema().getDefaultGeometry().getLocalName());
+
+			distancefilter.setExpression1(geometry);
+
+			/**
+			 * 8.8.2008: SK Wenn für das Layer auch ein Filter aktiv ist, dann
+			 * soll dieser mit AND and den distanceFilter gebunden werden.
+			 * "Filter filter" ist entweder der distanceFilter oder
+			 * (distanceFilter AND layerFilter)
+			 */
+			Filter filter = distancefilter;
+			if (layer.getQuery() != null) {
+				final Filter layerFilter = layer.getQuery().getFilter();
+				if (layerFilter != null) {
+					filter = distancefilter.and(layerFilter);
+				}
+			}
+			;
+
+			try {
+				// Filter auf Layer des Features anwenden
+				// FeatureCollection fc = layer.getFeatureSource().getFeatures(
+				// FilterUtil.cloneFilter(filter) ); // KLAPPT (NOCH) NICHT
+				// FeatureCollection fc = featureSource.getFeatures(
+				// distancefilter );
+				FeatureCollection fc = featureSource.getFeatures(filter);
+
+				// Liefert eine FeatureCollection zurück, in welcher nur
+				// Features enthalten sind, welche bei der aktuellen
+				// Anzeigeskala aufgrund des Styles gerendert werden.
+				fc = filterSLDVisibleOnly(fc, layer.getStyle());
+
+				if (!fc.isEmpty()) {
+					result.put(layer, fc);
+					// Beim Modus "oberstes Layer selektieren" die Schleife
+					// beenden
+					if (mode == SELECT_TOP || mode == SELECT_ONE_FROM_TOP)
+						break;
+				}
+			} catch (IOException err) {
+				err.printStackTrace();
+			}
+
+			// for ( FeatureCollection fc1 : result.values() )
+			// LOGGER.debug("A  "+fc1+"    "+fc1.isEmpty());
+		}
+		// for ( FeatureCollection fc1 : result.values() )
+		// LOGGER.debug("B   "+fc1+"    "+fc1.isEmpty());
+
+		return result;
+	}
+
+	/**
+	 * Ermittelt alle Features, die einen Filter erfuellen. Beim Modus
+	 * {@link #SELECT_TOP} wird nur das oberste sichtbare Layer durchsucht. Beim
+	 * Modus {@link #SELECT_ALL} werden alle sichtbaren Layer durchsucht.
+	 *
+	 * 17.4.08, Stefan
+	 *
+	 * @param filter
+	 *            Filter
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls der Filter {@code null} ist
+	 */
+	protected Hashtable<MapLayer, FeatureCollection> findFeatures(
+			GeometryFilterImpl filter, int mode, Envelope env) {
+		Hashtable<MapLayer, FeatureCollection> result = new Hashtable<MapLayer, FeatureCollection>();
+		if (filter == null)
+			return result;
+
+		// Je nach Modus: Alle oder nur das oberste Layer
+		MapContext context = getContext();
+		MapLayer[] layerList = context.getLayers();
+		for (int i = layerList.length - 1; i >= 0; i--) {
+			MapLayer layer = layerList[i];
+			if (!layer.isVisible())
+				continue;
+
+			// Bei einem Raster-Layer, das die BB schneidet, abbrechen, wenn nur
+			// im
+			// obersten (sichtbaren) Layer gesucht
+			// wird.AbstractGridCoverage2DReader
+			// Ansonsten Raster-Layer einfach uebergehen.
+			if (isGridCoverageLayer(layer)) {
+				if (mode == SELECT_TOP
+						&& gridLayerIntersectsEnvelope(layer, env))
+					break;
+				continue;
+			}
+
+			// Filter an Layer koppeln
+			// WICHTIG: Dies darf erst geschehen, NACHDEM das
+			// Schleifen-Abbruch-Kriterium
+			// fuer die Raster-Layer geprueft wurde!!
+			// Werden folgende Zeilen naemlich auf das FeatureSource des
+			// Raster-Layers angewandt, dann "bricht" der Filter "irgendwie"
+			// zusammen und auch die ZUVOR gefilterten FeatureCollections,
+			// sind ploetzlich EMPTY!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			final FeatureSource featureSource = layer.getFeatureSource();
+			Expression geometry = ff.createAttributeExpression(featureSource
+					.getSchema().getDefaultGeometry().getLocalName());
+
+			filter.setExpression1(geometry);
+
+			try {
+				// Filter auf Layer des Features anwenden
+				// FeatureCollection fc = layer.getFeatureSource().getFeatures(
+				// FilterUtil.cloneFilter(filter) ); // KLAPPT (NOCH) NICHT
+				FeatureCollection fc = featureSource.getFeatures(filter);
+
+				// Liefert eine FeatureCollection zurück, in welcher nur
+				// Features enthalten sind, welche bei der aktuellen
+				// Anzeigeskala aufgrund des Styles gerendert werden.
+				fc = filterSLDVisibleOnly(fc, layer.getStyle());
+
+				if (!fc.isEmpty()) {
+					result.put(layer, fc);
+					// Beim Modus "oberstes Layer selektieren" die Schleife
+					// beenden
+					if (mode == SELECT_TOP || mode == SELECT_ONE_FROM_TOP)
+						break;
+				}
+			} catch (Exception err) {
+				err.printStackTrace();
+			}
+
+			// for ( FeatureCollection fc1 : result.values() )
+			// LOGGER.debug("A  "+fc1+"    "+fc1.isEmpty());
+		}
+		// for ( FeatureCollection fc1 : result.values() )
+		// LOGGER.debug("B   "+fc1+"    "+fc1.isEmpty());
+
+		return result;
+	}
+
+	/**
+	 * SLD Rules können die Paramter MinScaleDenominator und MaxScaleDenominator
+	 * enthalten. Dadurch können Elemente für manche Zoom-Stufen deaktiviert
+	 * werden.
+	 *
+	 * @param fc
+	 *            Die zu filternde FeatureCollection. Diese wird nicht
+	 *            verändert.
+	 * @param style
+	 *            Der Style, mit dem die Features gerendert werden (z.b.
+	 *            layer.getStyle() )
+	 *
+	 * @return Eine FeatureCollection in welcher nur die Features enthalten
+	 *         sind, welche bei aktuellen Scale mit dem übergebenen Style
+	 *         gerendert werden.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	private MemoryFeatureCollection filterSLDVisibleOnly(
+			final FeatureCollection fc, final Style style) {
+
+		// Der "scaleDenominator" der aktuellen JMapPane
+		Double scaleDenominator = RendererUtilities
+				.calculateOGCScale(new ReferencedEnvelope(getMapArea(),
+						getContext().getCoordinateReferenceSystem()),
+						getSize().width, null);
+
+		return FeatureUtil.filterSLDVisibleOnly(fc, style, scaleDenominator);
+	}
+
+	/**
+	 * Ermittelt alle Features, die in einem Bereich liegen und erzeugt
+	 * entsprechende {@link FeatureSelectedEvent FeatureSelectedEvents}. Beim
+	 * Modus {@link #SELECT_TOP} wird nur das oberste sichtbare Layer
+	 * durchsucht. Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer
+	 * durchsucht.
+	 *
+	 * @param filter
+	 *            Filter
+	 * @param mode
+	 *            Suchmodus
+	 * @param env
+	 *            Bereich der durchsucht wird (fuer das Filtern irrelevant; wird
+	 *            nur fuer Events benoetigt!)
+	 */
+	protected boolean findFeaturesAndFireEvents(GeometryFilterImpl filter,
+			int mode, Envelope env) {
+		Hashtable<MapLayer, FeatureCollection> result = findVisibleFeatures(
+				filter, mode, env);
+
+		// Events ausloesen fuer jedes Layer
+		for (final Enumeration<MapLayer> e = result.keys(); e.hasMoreElements();) {
+			final MapLayer layer = e.nextElement();
+			final FeatureCollection fc = result.get(layer);
+			if (fc != null && !fc.isEmpty())
+				fireMapPaneEvent(new FeatureSelectedEvent(this, layer, env, fc));
+		}
+		return !result.isEmpty();
+	}
+
+	/**
+	 * Ermittelt alle Teil-Raster, die in einem Bereich liegen.
+	 * BefindFeaturesAndFireEventsim Modus {@link #SELECT_TOP} wird nur das
+	 * oberste sichtbare Layer durchsucht. Beim Modus {@link #SELECT_ALL} werden
+	 * alle sichtbaren Layer durchsucht.
+	 *
+	 * @param env
+	 *            Bounding-Box
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls die Bounding-Box {@code null}
+	 *         ist
+	 */
+	protected Hashtable<MapLayer, GridCoverage2D> findGridCoverageSubsets(
+			Envelope env, int mode) {
+		Hashtable<MapLayer, GridCoverage2D> result = new Hashtable<MapLayer, GridCoverage2D>();
+		if (env == null)
+			return result;
+
+		MapContext context = getContext();
+		// Je nach Modus: Alle oder nur das oberste Layer
+		MapLayer[] layerList = context.getLayers();
+		for (int i = layerList.length - 1; i >= 0; i--) {
+			MapLayer layer = layerList[i];
+			Object layerObj = getLayerSourceObject(layer);
+			if (!layer.isVisible())
+				continue;
+
+			// Bei einem Nicht-Raster-Layer, das die BB schneidet, abbrechen,
+			// wenn nur im
+			// obersten (sichtbaren) Layer gesucht wird.
+			// Ansonsten Nicht-Raster-Layer einfach uebergehen.
+			if (!(layerObj instanceof GridCoverage2D)) {
+				if ((mode == SELECT_TOP || mode == SELECT_ONE_FROM_TOP)
+						&& featureLayerIntersectsEnvelope(layer, env))
+					break;
+				continue;
+			}
+
+			GridCoverage2D sourceGrid = (GridCoverage2D) layerObj;
+			com.vividsolutions.jts.geom.Envelope jtsEnv = env;
+			org.geotools.geometry.Envelope2D gtEnv2D = JTS.getEnvelope2D(
+					jtsEnv, sourceGrid.getCoordinateReferenceSystem());
+			// org.opengis.spatialschema.geometry.Envelope gtGenEnv = new
+			// GeneralEnvelope
+			// ((org.opengis.spatialschema.geometry.Envelope)gtEnv2D); //
+			// gt2-2.3.4
+			org.opengis.geometry.Envelope gtGenEnv = new GeneralEnvelope(
+					(org.opengis.geometry.Envelope) gtEnv2D); // gt2-2.4.2
+
+			// GridCoverage2D subsetGrid =
+			// (GridCoverage2D)Operations.DEFAULT.crop(sourceGrid,gtGenEnv);
+			GridCoverage2D subsetGrid = GridUtil.createGridCoverage(sourceGrid,
+					gtGenEnv);
+			if (subsetGrid != null)
+				result.put(layer, subsetGrid);
+			// Beim Modus "oberstes Layer selektieren" die Schleife beenden
+			if (mode == SELECT_TOP || mode == SELECT_ONE_FROM_TOP)
+				break;
+		}
+		return result;
+	}
+
+	/**
+	 * Ermittelt alle Teil-Raster, die in einem Bereich liegen und erzeugt
+	 * entsprechende {@link GridCoverageSelectedEvent
+	 * GridCoverageSelectedEvents}. Beim Modus {@link #SELECT_TOP} wird nur das
+	 * oberste sichtbare Layer durchsucht. Beim Modus {@link #SELECT_ALL} werden
+	 * alle sichtbaren Layer durchsucht.
+	 *
+	 * @param env
+	 *            Bounding-Box
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls die Bounding-Box {@code null}
+	 *         ist
+	 */
+	protected boolean findGridCoverageSubsetsAndFireEvents(final Envelope env,
+			final int mode) {
+		final Hashtable<MapLayer, GridCoverage2D> result = findGridCoverageSubsets(
+				env, mode);
+		// Events ausloesen fuer jedes Layer
+		for (final Enumeration<MapLayer> e = result.keys(); e.hasMoreElements();) {
+			final MapLayer layer = e.nextElement();
+			final GridCoverage2D subset = result.get(layer);
+			if (subset != null)
+				fireMapPaneEvent(new GridCoverageSelectedEvent(this, layer,
+						env, subset));
+		}
+		return !result.isEmpty();
+	}
+
+	/**
+	 * Ermittelt alle Raster-Werte, die an einer bestimmten Geo-Position liegen.
+	 * Beim Modus {@link #SELECT_TOP} wird nur das oberste sichtbare Layer
+	 * durchsucht. Beim Modus {@link #SELECT_ALL} werden alle sichtbaren Layer
+	 * durchsucht.
+	 * <p>
+	 * SK: 28.09.2007 Da ein Rasterlayer auch mehrere Baender haben kann, ist es
+	 * sinnvoll, nicht <code>Hashtable MapLayer,Double </code> sondern
+	 * <code>Hashtable MapLayer,Double[] </code> zurueckzugeben.
+	 *
+	 * @param point
+	 *            Geo-Referenzgeop
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls keine Werte ermittelt werden
+	 *         konnten
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	protected Hashtable<MapLayer, double[]> findGridCoverageValues(
+			Point2D point, int mode) {
+		Hashtable<MapLayer, double[]> result = new Hashtable<MapLayer, double[]>();
+
+		if (point == null)
+			return result;
+
+		MapContext context = getContext();
+		// Je nach Modus: Alle oder nur das oberste Layer
+		MapLayer[] layerList = context.getLayers();
+		for (int i = layerList.length - 1; i >= 0; i--) {
+			MapLayer layer = layerList[i];
+			if (!layer.isVisible())
+				continue;
+			Object layerObj = getLayerSourceObject(layer);
+
+			// LOGGER.info("layerObj = "+layerObj.getClass().getSimpleName());
+
+			// SK 29.9.2007 Vorher:
+			// // Bei einem Nicht-Raster-Layer, das den Punkt beinhaltet,
+			// abbrechen, wenn nur im
+			// // obersten (sichtbaren) Layer gesucht wird.
+			// // Ansonsten Nicht-Raster-Layer einfach uebergehen.
+			// if ( !(layerObj instanceof GridCoverage2D) ) {
+			// if ( mode == SELECT_TOP &&
+			// featureLayerIntersectsEnvelope(layer,new
+			// Envelope(point.getX(),point.getX(),point.getY(),point.getY())) )
+			// break;
+			// continue;
+			// }
+
+			// Bei einem Nicht-Raster-Layer, das den Punkt beinhaltet,
+			// abbrechen, wenn nur im
+			// obersten (sichtbaren) Layer gesucht wird.
+			// Ansonsten Nicht-Raster-Layer einfach uebergehen.
+			// SK 29.9.07: Ein AbstractGridCoverage2DReader ist auch ein Raster
+			if (!(layerObj instanceof GridCoverage2D || layerObj instanceof AbstractGridCoverage2DReader)) {
+				final Envelope pointAsEnvelope = new Envelope(point.getX(),
+						point.getX(), point.getY(), point.getY());
+				if (mode == SELECT_TOP
+						&& featureLayerIntersectsEnvelope(layer,
+								pointAsEnvelope)) {
+				}
+				continue;
+			}
+
+			GridCoverage2D sourceGrid;
+
+			if (layerObj instanceof AbstractGridCoverage2DReader) {
+				//LOGGER.info("layerObj instanceof AbstractGridCoverage2DReader"
+				// );
+				AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) layerObj;
+				Parameter readGG = new Parameter(
+						AbstractGridFormat.READ_GRIDGEOMETRY2D);
+
+				ReferencedEnvelope mapExtend = new org.geotools.geometry.jts.ReferencedEnvelope(
+						mapArea, context.getCoordinateReferenceSystem());
+
+				readGG.setValue(new GridGeometry2D(new GeneralGridRange(
+						getBounds()), mapExtend));
+
+				try {
+					sourceGrid = (GridCoverage2D) reader
+							.read(new GeneralParameterValue[] { readGG });
+				} catch (Exception e) {
+					LOGGER.error(e);
+					continue;
+				}
+			} else {
+				// Ein instanceof ist nicht noetig, da sonst schon break oder
+				// continue aufgerufen worden waere
+				sourceGrid = (GridCoverage2D) layerObj;
+			}
+
+			// vorher: double[] value = new double[2];
+
+			// getNumSampleDimensions gibt die Anzahl der Baender des Rasters
+			// zurueck.
+			double[] values = new double[sourceGrid.getNumSampleDimensions()];
+
+			try {
+				// Grid an Geo-Position auswerten
+				sourceGrid.evaluate(point, values);
+			} catch (CannotEvaluateException err) {
+				// z.B. Punkt ausserhalb des Rasters --> Layer uebergehen
+				continue;
+			} catch (Exception e) {
+				LOGGER.error(e);
+				continue;
+			}
+
+			// SK: voher wurde nur der erste Wert zurueckgegeben
+			// result.put(layer,value[0]);
+			// jetzt werden alle werte zueuckgegeben
+
+			result.put(layer, values);
+			// Beim Modus "oberstes Layer selektieren" die Schleife beenden
+			if (mode == SELECT_TOP)
+				break;
+		}
+		return result;
+	}
+
+	/**
+	 * Ermittelt die Raster-Werte, die an einem Punkt liegen und erzeugt
+	 * entsprechende {@link GridCoverageValueSelectedEvent
+	 * GridCoverageValueSelectedEvents}. Beim Modus {@link #SELECT_TOP} wird nur
+	 * das oberste sichtbare Layer durchsucht. Beim Modus {@link #SELECT_ALL}
+	 * werden alle sichtbaren Layer durchsucht.
+	 * <p>
+	 * SK: 28.09.2007 Da ein Rasterlayer auch mehrere Baender haben kann, ist es
+	 * sinnvoll, nicht <code>Hashtable MapLayer,Double </code> sondern
+	 * <code>Hashtable MapLayer,Double[] </code> zurueckzugeben.
+	 *
+	 * @param point
+	 *            Geo-Referenz
+	 * @param mode
+	 *            Suchmodus
+	 * @return eine leere {@link Hashtable} falls der Punkt {@code null} ist
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	protected boolean findGridCoverageValuesAndFireEvents(Point2D point,
+			int mode) {
+		Hashtable<MapLayer, double[]> result = findGridCoverageValues(point,
+				mode);
+
+		// Events ausloesen fuer jedes Layer
+		for (Enumeration<MapLayer> e = result.keys(); e.hasMoreElements();) {
+			MapLayer layer = e.nextElement();
+			double[] values = result.get(layer);
+			fireMapPaneEvent(new GridCoverageValueSelectedEvent(this, layer,
+					point, values));
+		}
+		return !result.isEmpty();
+	}
+
+	/**
+	 * Bereitet einen BoundingBox-Filter vor. Das "linke" Attribut der
+	 * Expression (das Feature-Attribut, auf das der Filter angewendet wird),
+	 * wird dabei noch nicht belegt. Dies geschieht erst bei der Auswertung
+	 * entsprechend des jeweiligen Layers
+	 *
+	 * @param env
+	 *            Bounding-Box
+	 */
+	private static GeometryFilterImpl createBoundingBoxFilter(Envelope env) {
+		// Filter fuer Envelope zusammenstellen
+		Expression bbox = ff.createBBoxExpression(env);
+		GeometryFilterImpl bboxFilter = (GeometryFilterImpl) ff
+				.createGeometryFilter(AbstractFilter.GEOMETRY_BBOX);
+		// GeometryFilterImpl bboxFilter =
+		// (GeometryFilterImpl)ff.createGeometryFilter
+		// (AbstractFilter.GEOMETRY_WITHIN);
+		bboxFilter.setExpression2(bbox);
+		return bboxFilter;
+	}
+
+	/**
+	 * Bereitet einen Punkt-Filter vor. Das "linke" Attribut der Expression (das
+	 * Feature-Attribut, auf das der Filter angewendet wird), wird dabei noch
+	 * nicht belegt. Dies geschieht erst bei der Auswertung entsprechend des
+	 * jeweiligen Layers
+	 *
+	 * @param point
+	 *            Geo-Koordinate
+	 */
+	private static GeometryFilterImpl createPointFilter(Point2D point) {
+		// Filter fuer Envelope zusammenstellen
+		Geometry geometry = gf.createPoint(new Coordinate(point.getX(), point
+				.getY()));
+		GeometryFilterImpl pointFilter = (GeometryFilterImpl) ff
+				.createGeometryFilter(GeometryFilterImpl.GEOMETRY_CONTAINS);
+		pointFilter.setExpression2(ff.createLiteralExpression(geometry));
+		return pointFilter;
+	}
+
+	/**
+	 * Bereitet einen "InDerNaehe von" Distance-Filter vor. Das "linke" Attribut
+	 * der Expression (das Feature-Attribut, auf das der Filter angewendet
+	 * wird), wird dabei noch nicht belegt. Dies geschieht erst bei der
+	 * Auswertung entsprechend des jeweiligen Layers
+	 *
+	 * Wird benoetigt, um mit der Maus einen Punkt zu treffen.
+	 *
+	 * @param point
+	 *            Geo-Koordinate
+	 * @param dist
+	 *            Distanz zum Punkt in Einheiten des Layers
+	 *
+	 *            TODO SK Auf FilterFactory2 ändern... Beispiel von
+	 *            http://docs.codehaus.org/display/GEOTDOC/Filter+Examples
+	 *            funktioniert erst ab 2.5 ?? Vor dem Distcheck einen BBOX check
+	 *            sollte die geschwindigkeit erhöhen.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	private static GeometryFilterImpl createNearPointFilter(
+			final Point2D point, final Double dist) {
+		// Filter fuer Envelope zusammenstellen
+		final Geometry geometry = gf.createPoint(new Coordinate(point.getX(),
+				point.getY()));
+
+		final DWithinImpl dwithinFilter = (DWithinImpl) ff
+				.createGeometryDistanceFilter(DWithinImpl.GEOMETRY_DWITHIN);
+
+		dwithinFilter.setDistance(dist);
+
+		dwithinFilter.setExpression2(ff.createLiteralExpression(geometry));
+
+		return dwithinFilter;
+	}
+
+	/**
+	 * Prueft, ob es sich bei einem Layer um ein Raster-Layer handelt.
+	 * Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+	 * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein
+	 * Attribut mit dem Namen "GridCoverage" hat.
+	 *
+	 * SK: Pyramidenlayer aka AbstractGridCoverage2DReader geben auch true
+	 * zurück.
+	 *
+	 * @param layer
+	 *            zu ueberpruefendes Layer
+	 */
+	public static boolean isGridCoverageLayer(MapLayer layer) {
+		final Object layerSourceObject = getLayerSourceObject(layer);
+		boolean b = (layerSourceObject instanceof GridCoverage2D)
+				|| (layerSourceObject instanceof AbstractGridCoverage2DReader);
+		// if (!b && layerSourceObject instanceof DefaultFeatureResults){
+		// DefaultFeatureResults dfr = (DefaultFeatureResults)
+		// layerSourceObject;
+		// }
+		// LOGGER.debug(b+"= "+layerSourceObject.getClass().getSimpleName()+" "+
+		// layer.getTitle());
+		return b;
+		// try {
+		// FeatureCollection fc = layer.getFeatureSource().getFeatures();
+		// // Layer muss genau ein Feature beinhalten
+		// if ( fc == null || fc.size() != 1 )
+		// return false;
+		// // Feature muss genau 1 Attribut mit dem Namen "GridCoverage" haben
+		// FeatureType ftype = fc.getFeatureType();
+		// if ( ftype == null || ftype.getAttributeCount() != 1 ||
+		// !"GridCoverage".equalsIgnoreCase(ftype.getAttributeType(0).getName())
+		// )
+		// return false;
+		// } catch (Exception err) {
+		// }
+		// return true;
+	}
+
+	/**
+	 * Liefert das Objekt ({@link GridCoverage2D} oder {@link FeatureCollection}
+	 * oder {@link AbstractGridCoverageReader} auf dem ein Layer basiert. Ein
+	 * Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+	 * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein
+	 * Attribut mit Namen "GridCoverage" und Typ {@code GridCoverage2D} oder
+	 * {@link AbstractGridCoverageReader} hat. Sind diese Bedingungen erfuellt,
+	 * wird das 2. Attribut zurueckgegeben, ansonsten die
+	 * {@link FeatureCollection}.
+	 *
+	 * @param layer
+	 *            ein Layer
+	 * @return {@code null}, falls das Objekt nicht ermittelt werden kann (da
+	 *         ein Fehler aufgetreten ist).
+	 */
+	public static Object getLayerSourceObject(MapLayer layer) {
+		try {
+			final FeatureSource featureSource = layer.getFeatureSource();
+			FeatureCollection fc = featureSource.getFeatures();
+			// RasterLayer muss genau ein Feature beinhalten
+			// Ist dies nicht der Fall wird die FeatureCollection zurueckgegeben
+			if (fc == null || fc.size() != 1) {
+
+				return fc;
+			}
+			// FeatureType des RasterLayer muss genau 1 Attribut mit Namen
+			// "GridCoverage"
+			// Ist dies nicht der Fall wird die FeatureCollection zurueckgegeben
+			FeatureType ftype = fc.getFeatureType();
+			if (ftype == null
+					|| ftype.getAttributeCount() != 1
+					|| !"GridCoverage".equalsIgnoreCase(ftype.getAttributeType(
+							0).getLocalName()))
+				return fc;
+			// (Einziges) Feature muss genau 2 Attribute besitzen, wobei das
+			// erste vom
+			// Typ Geometry ist und das zweite vom Typ GridCoverage2D
+			// sonst: FeatureCollextion zurueckgeben
+
+			/** CHANGE SK 9.8.08 BEGIN */
+			Feature f = fc.features().next();
+			if ((f.getFeatureType().getTypeName().equals("GridCoverage") && f
+					.getNumberOfAttributes() >= 2)) {
+				// I am sure, that it is a raster some. We do not have to cast
+				// it to either cridcoverage or abstractReader, as the results
+				// are tested anyway...
+				return f.getAttribute(1);
+			}
+
+			/** CHANGE SK 9.8.08 END */
+
+			if (f.getNumberOfAttributes() != 2
+					|| // Geaendert, da es bei AbstractGridCoverageReader 3 sind
+					// (SK, 19.08.07)
+					!(f.getAttribute(0) instanceof com.vividsolutions.jts.geom.Geometry)
+					|| !(f.getAttribute(1) instanceof GridCoverage2D))
+				return fc;
+
+			// Objekt ist ein Raster!
+			return (GridCoverage2D) f.getAttribute(1);
+		} catch (Exception err) {
+			LOGGER.warn(err.getMessage(), err);
+			return null;
+		}
+	}
+
+	/**
+	 * Should be called when the {@link JMapPane} is not needed no more to help
+	 * the GarbageCollector
+	 *
+	 * Removes all {@link JMapPaneListener}s that are registered
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+
+	public void dispose() {
+		if (isDisposed())
+			return;
+
+		if (dragWaitCursorListener != null)
+			this.removeMouseListener(dragWaitCursorListener);
+		if (mouseWheelZoomListener != null)
+			this.removeMouseWheelListener(mouseWheelZoomListener);
+
+		// Alle mapPaneListener entfernen
+		mapPaneListeners.clear();
+
+		getContext().clearLayerList();
+
+		removeAll();
+		disposed = true;
+	}
+
+	/**
+	 * A flag indicating if dispose() has already been called. If true, then
+	 * further use of this {@link JMapPane} is undefined.
+	 */
+	private boolean isDisposed() {
+		return disposed;
+	}
+
+	//
+	// /**
+	// * Werden Rasterlayer waehrend einer PAN Aktion versteckt?
+	// * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	// Kr&uuml;ger</a>
+	// */
+	// public boolean isHideRasterLayersDuringPan() {
+	// return hideRasterLayersDuringPan;
+	// }
+	//
+	// /**
+	// * Bestimmt, ob Rasterlayer waehrend einer PAN Aktion versteckt werden
+	// soll. Default ist false.
+	// * @param hideRasterLayersDuringPan
+	// * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	// Kr&uuml;ger</a>
+	// */
+	// public void setHideRasterLayersDuringPan(boolean
+	// hideRasterLayersDuringPan) {
+	// this.hideRasterLayersDuringPan = hideRasterLayersDuringPan;
+	// }
+
+	public boolean isSetWaitCursorDuringNextRepaint() {
+		return setWaitCursorDuringNextRepaint;
+	}
+
+	/**
+	 * When setting this to true, the next repaint of this component will be
+	 * accompanied by a WAIT Cursor
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void setWaitCursorDuringNextRepaint(
+			boolean waitCursorDuringNextRepaint) {
+		this.setWaitCursorDuringNextRepaint = waitCursorDuringNextRepaint;
+	}
+
+	/**
+	 * Gibt den "normalen" Cursor zurueck. Dieser kann neben dem "pointer" auch
+	 * ein Sanduhr-Wartecursor sein.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public Cursor getNormalCursor() {
+		return normalCursor;
+	}
+
+	/**
+	 * Setzt den "normalen" Cursor. Dieser kann neben dem default "pointer" z.B.
+	 * auch ein Sanduhr-Wartecursor sein.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	public void setNormalCursor(Cursor normalCursor) {
+		this.normalCursor = normalCursor;
+	}
+
+	// /**
+	// * Prueft, ob es sich bei einem Layer um ein Raster-Layer handelt.
+	// * Raster-Layer zeichnen sich dadurch aus, dass die zugrunde liegende
+	// * {@link FeatureCollection} nur ein Feature enthaelt, das genau ein
+	// Attribut
+	// * vom Type {@link org.geotools.feature.type.FeatureAttributeType} hat,
+	// welches
+	// * wiederum genau zwei Attribute hat:<br>
+	// * Eines vom Typ {@link
+	// org.opengis.spatialschema.geometry.geometry.Polygon}
+	// * und eines vom Typ {@link org.opengis.coverage.grid.GridCoverage}.
+	// * @param layer zu ueberpruefendes Layer
+	// */
+	// public static boolean isGridCoverageLayer(MapLayer layer) {
+	// try {
+	// FeatureCollection fc = layer.getFeatureSource().getFeatures();
+	// // Layer muss genau ein Feature beinhalten
+	// if ( fc == null || fc.size() != 1 )
+	// return false;
+	// // Feature muss genau 1 Attribut vom Typ FeatureAttributeType haben
+	// FeatureType ftype = fc.getFeatureType();
+	// if ( ftype == null || ftype.getAttributeCount() != 1 ||
+	// !(ftype.getAttributeType(0) instanceof
+	// org.geotools.feature.type.FeatureAttributeType) )
+	// return false;
+	// // FeatureAttribute muss genau 2 Atrribute haben
+	// org.geotools.feature.type.FeatureAttributeType atype =
+	// (org.geotools.feature
+	// .type.FeatureAttributeType)ftype.getAttributeType(0);
+	// if ( atype == null || atype.getAttributeCount() != 2 )
+	// return false;
+	// // Typ des ersten Attributs muss Polygon sein
+	// if ( !com.vividsolutions.jts.geom.Polygon.class.isAssignableFrom(
+	// atype.getAttributeType(0).getType() ) )
+	// return false;
+	// // Typ des zweiten Attributs muss GridCoverage sein
+	// if ( !org.opengis.coverage.grid.GridCoverage.class.isAssignableFrom(
+	// atype.getAttributeType(1).getType() ) )
+	// return false;
+	//
+	// } catch (Exception err) {
+	// }
+	// return true;
+	// }
+
+	/**
+	 * Nuetzlich wenn die Componente gedruckt wird. Dann werden wird der
+	 * Hintergrund auf WEISS gesetzt.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	 *         Kr&uuml;ger</a>
+	 */
+	@Override
+	public void print(Graphics g) {
+		Color orig = getBackground();
+		setBackground(Color.WHITE);
+
+		// wrap in try/finally so that we always restore the state
+		try {
+			super.print(g);
+		} finally {
+			setBackground(orig);
+		}
+	}
+
+}

Added: trunk/src/schmitzm/geotools/gui/LayeredEditorFrame.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/LayeredEditorFrame.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/LayeredEditorFrame.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,97 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import schmitzm.geotools.feature.FeatureUtil.GeometryForm;
+import schmitzm.lang.LangUtil;
+import schmitzm.swing.JPanel;
+
+
+/**
+ * Diese Klasse stellt ein Fenster dar, in dem layer-basiert Geo-Objekte
+ * grafisch dargestellt <b>und neue Vektor-Layer erstellt</b> werden koennen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LayeredEditorFrame extends LayeredMapFrame {
+  /** Toolbar, der die Editier-Funktionen steuert. */
+  protected JEditorToolBar toolBar = null;
+  /** Toolbar, der die Style-Funktionen steuert. */
+  protected StyleToolBar styleBar = null;
+
+  /**
+   * Erzeugt ein neues (leeres) Editor-Fenster.
+   */
+  public LayeredEditorFrame() {
+    this(null,"");
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Editor-Fenster.
+   * @param lmp {@link LayeredMapPane} welches zur Anzeige der Karten verwendet wird (wenn
+   *            {@code null} wird eine neue {@link LayeredMapPane}-Instanz erzeugt)
+   */
+  public LayeredEditorFrame(LayeredMapPane lmp) {
+    this(lmp,"");
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Editor-Fenster.
+   * @param title Titel des Fensters
+   */
+  public LayeredEditorFrame(String title) {
+    this(null,title);
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Editor-Fenster.
+   * @param title Titel des Fensters
+   * @param lmp {@link LayeredMapPane} welches zur Anzeige der Karten verwendet wird (wenn
+   *            {@code null} wird eine neue {@link LayeredMapPane}-Instanz erzeugt)
+   */
+  public LayeredEditorFrame(LayeredMapPane lmp, String title) {
+    super( lmp != null ? lmp : new LayeredMapPane(new GeoMapPane(new JEditorPane(),null,null,null)),title);
+    if ( !(layeredMapPane.geoMapPane.mapPane instanceof JEditorPane) )
+      throw new IllegalArgumentException("LayeredMapPane must contain a JEditorPane to use in LayeredEditorFrame.");
+    this.toolBar  = new JEditorToolBar( (JEditorPane)layeredMapPane.geoMapPane.mapPane );
+    this.styleBar = new StyleToolBar();
+    // Add a listener to the style bar, to apply every style
+    // change to the map
+    this.styleBar.addPropertyChangeListener( new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        JEditorPane editorPane = (JEditorPane)layeredMapPane.geoMapPane.mapPane;
+        if ( e.getSource() == styleBar ) {
+          editorPane.setEditorStyle( GeometryForm.POINT,   styleBar.createPointStyle() );
+          editorPane.setEditorStyle( GeometryForm.LINE,    styleBar.createLineStyle() );
+          editorPane.setEditorStyle( GeometryForm.POLYGON, styleBar.createPolygonStyle() );
+          editorPane.refresh();
+        }
+
+      }
+    });
+
+    JPanel contentPane = new JPanel(new BorderLayout(0,5));
+    JPanel toolPane    = new JPanel(new BorderLayout() );
+    toolPane.add(toolBar, BorderLayout.NORTH);
+    toolPane.add(styleBar, BorderLayout.SOUTH);
+    contentPane.add( getContentPane(), BorderLayout.CENTER );
+    contentPane.add( toolPane, BorderLayout.NORTH );
+    setContentPane( contentPane );
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/LayeredMapFrame.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/LayeredMapFrame.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/LayeredMapFrame.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,251 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.Dimension;
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.geom.Point2D;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JLabel;
+import javax.swing.BorderFactory;
+import javax.swing.SwingConstants;
+
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.gui.swing.event.GeoMouseEvent;
+
+import schmitzm.data.WritableGrid;
+import schmitzm.data.AbstractWritableGrid;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.geotools.gui.GeoPositionLabel;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.LayeredMapPane;
+import schmitzm.geotools.gui.FeatureCollectionFrame;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.GridCoverageSelectedEvent;
+import schmitzm.geotools.feature.FeatureOperationTreeFilter;
+import java.awt.Color;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.map.event.MapLayerListEvent;
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt ein Fenster dar, in dem layer-basiert Objekte
+ * grafisch dargestellt werden koennen. Hinzugefuegt werden die Objekte
+ * direkt in das {@link LayeredMapPane#addLayer(Object,String)}<br>
+ * Welche Objekte dargestellt werden koennen, ist der Beschreibung der
+ * {@link LayeredMapPane#isVisualisable(Object)}-Methode zu entnehmen.<br>
+ * Das Fenster besteht aus 3 Komponenten:
+ * <ol>
+ * <li>Eine Map ({@link JMapPane}) zu grafischen Darstellung der Layer</li>
+ * <li>Eine Liste mit Steuerungskomponenten, ueber die die einzelnen Layer
+ *     angesprochen werden koennen (ein/ausblenden, zoomen, ...).</li>
+ * <li>Eine Status-Zeile, in der die Koordinaten der aktuellen Mausposition
+ *     angezeigt werden, sowie der Raster-Wert des obersten Rasters unterhalb
+ *     des Mauszeigers.</li>
+ * </ol>
+ * Die grafischen Layer (in der Map) koennen wahlweise (de)aktiviert werden. Dies
+ * geschieht durch setzen/entfernen eines Haekchens in der entsprechenden
+ * Steuerungskomponente. Diese enthaelt zudem ein Kontextmenue, ueber welches
+ * <ul>
+ * <li>das jeweilige Layer aus der Map entfernt werden kann</li>
+ * <li>das jeweilige Layer in der Map eine Ebene nach oben/unten verschoben werden kann</li>
+ * <li>die Map sogezoomt werden kann, dass das jeweilige Layer komplett angezeigt wird</li>
+ * </ul>
+ * Um Layer einzufuegen koennen die {@code addLayer(.)}-Methoden des
+ * {@link #getLayeredMapPane() LayeredMapPane} (dabei wird ein Default-Style verwendet)
+ * oder die entsprechenden Methoden des {@link MapContext} ({@code getLayeredMapPane().getMapPane().getContext()}).
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LayeredMapFrame extends JFrame {
+  private   JPanel                     contentPane      = null;
+  /** Karten- und Layer-Kontroll-Bereich. */
+  protected LayeredMapPane             layeredMapPane   = null;
+  private   JMapPane                   mapPane          = null;
+  private   MapContext                 mapContext       = null;
+  /** Status-Balken. */
+  protected MapPaneStatusBar           statusBar        = null;
+  /** Fenster fuer Feature-Details */
+  protected FeatureCollectionFrame     featuresFrame    = null;
+  /** Auswahlfeld fuer das Raster, fuer welches die Koordinaten angezeigt werden. */
+  protected SelectionInputOption<MapLayer> rasterComboBox   = null;
+
+  /**
+   * Erzeugt ein neues (leeres) Map-Fenster.
+   */
+  public LayeredMapFrame() {
+    this(null,"");
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Map-Fenster.
+   * @param lmp {@link LayeredMapPane} welches zur Anzeige der Karten verwendet wird (wenn
+   *            {@code null} wird eine neue {@link LayeredMapPane}-Instanz erzeugt)
+   */
+  public LayeredMapFrame(LayeredMapPane lmp) {
+    this(lmp,"");
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Map-Fenster.
+   * @param title Titel des Fensters
+   */
+  public LayeredMapFrame(String title) {
+    this(null,title);
+  }
+
+  /**
+   * Erzeugt ein neues (leeres) Map-Fenster.
+   * @param title Titel des Fensters
+   * @param lmp {@link LayeredMapPane} welches zur Anzeige der Karten verwendet wird (wenn
+   *            {@code null} wird eine neue {@link LayeredMapPane}-Instanz erzeugt)
+   */
+  public LayeredMapFrame(LayeredMapPane lmp, String title) {
+    super();
+    this.setSize(new Dimension(500, 400));
+    this.setTitle(title);
+    contentPane = (JPanel) this.getContentPane();
+    contentPane.setLayout(new GridBagLayout());
+
+    // Karte und Layer-Kontrolle
+    layeredMapPane = lmp != null ? lmp : new LayeredMapPane();
+    mapPane        = layeredMapPane.getMapPane();
+    mapContext     = mapPane.getContext();
+    MapActionControlPane mapControl = new MapActionControlPane(mapPane,MapActionControlPane.VERTICAL);
+    mapControl.setFloatable(false);
+
+    // unter Layer-Liste eine ComboBox mit Raster-Auswahl einfuegen
+    this.rasterComboBox   = new SelectionInputOption.Combo<MapLayer>("",false);
+    this.mapContext.addMapLayerListListener( new MapLayerListListener() {
+      public void layerChanged(MapLayerListEvent e) {
+        updateRasterComboBox();
+      }
+      public void layerMoved(MapLayerListEvent e) { }
+      public void layerAdded(MapLayerListEvent e) {
+        updateRasterComboBox();
+      }
+      public void layerRemoved(MapLayerListEvent e) {
+        updateRasterComboBox();
+      }
+    } );
+    this.layeredMapPane.horSplitPane.getContainer(0).add(
+        rasterComboBox, BorderLayout.SOUTH);
+
+
+    // Spezielles RasterPositionLabel in dem die Koordinaten des in der
+    // ComboBox ausgewaehlten Rasters angezeigt werden
+    RasterPositionLabel rpLabel = new RasterPositionLabel(1) {
+      protected MapLayer determineRasterLayer(final JMapPane mapPane) {
+        return rasterComboBox.getValue();
+      }
+    };
+
+    // Status-Zeile mit Raster-Auswahlfeld
+    statusBar = new MapPaneStatusBar(mapPane, rpLabel, null);
+    statusBar.setBorder( BorderFactory.createCompoundBorder(
+      BorderFactory.createLoweredBevelBorder(),
+      BorderFactory.createEmptyBorder(2,5,2,5)
+    ) );
+
+    // Status-Zeile mit Raster-Auswahlfeld kombinieren
+    JPanel statusBarContainer = new JPanel();
+    statusBarContainer.setLayout( new BorderLayout() );
+    statusBarContainer.add(rasterComboBox, BorderLayout.WEST);
+    statusBarContainer.add(statusBar, BorderLayout.CENTER);
+
+    // Root-SplitPane in Fenster einfuegen
+    contentPane.add(layeredMapPane, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0
+        ,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+    contentPane.add(mapControl, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0
+        ,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 0, 0, 0), 0, 0));
+    contentPane.add(statusBarContainer, new GridBagConstraints(0, 1, 2, 1, 1.0, 0.0
+        ,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 0, 0, 0), 0, 0));
+
+    // Fenster fuer Feature-Details
+    featuresFrame = new FeatureCollectionFrame(null,true);
+    featuresFrame.setSize( new Dimension(400,200) );
+    // Ausgewaehlte Features werden im Detail-Frame angezeigt
+    mapPane.addMapPaneListener( new JMapPaneListener() {
+      public void performMapPaneEvent(JMapPaneEvent e) {
+        // Wenn Features ueber die Maus aus der Karte ausgewaehlt werden,
+        // oeffnet sich ein Detail-Fenster
+        if ( e instanceof FeatureSelectedEvent && e.getSourceObject() == mapPane ) {
+          FeatureSelectedEvent fse = (FeatureSelectedEvent)e;
+          FeatureCollection    fc  = fse.getSelectionResult();
+          featuresFrame.setFeatureCollection(fc);
+          featuresFrame.setTitle( fse.getSourceLayer().getTitle()+" ["+fse.getSelectionRange()+"]" );
+          if ( !featuresFrame.isVisible() ) {
+              SwingUtil.setRelativeFramePosition(featuresFrame,1,0);
+          }
+          featuresFrame.setVisible(true);
+        }
+
+        if ( e instanceof GridCoverageSelectedEvent ) {
+          // ...
+        }
+      }
+    });
+  }
+
+
+  protected void updateRasterComboBox() {
+    // Letzte Auswahl merken
+    MapLayer         lastSelection   = (MapLayer)rasterComboBox.getValue();
+    // Alle Raster-Layer und Titel ermitteln
+    Vector<MapLayer> rasterLayer     = new Vector<MapLayer>();
+    Vector<String>   rasterLayerDesc = new Vector<String>();
+    for (MapLayer layer : mapPane.getContext().getLayers())
+      if ( mapPane.isGridCoverageLayer(layer) ) {
+        rasterLayer.add(layer);
+        rasterLayerDesc.add( layer.getTitle() );
+      }
+    // Auswahl neu setzen
+    rasterComboBox.setSelectionObjects(
+      rasterLayer.toArray( new MapLayer[0] ),
+      rasterLayerDesc.toArray()
+    );
+    // Wenn nur ein Raster zur Verfuegung steht, dieses autom. auswaehlen
+    if ( rasterLayer.size() == 1 )
+      lastSelection = rasterLayer.firstElement();
+    // Letzte Auswahl setzen
+    rasterComboBox.setSelectedItem(lastSelection);
+  }
+
+
+  /**
+   * Liefert den Karten- und Kontroll-Bereich des Fensters.
+   */
+  public LayeredMapPane getLayeredMapPane() {
+    return this.layeredMapPane;
+  }
+
+  /**
+   * Liefert den Status-Bereich des Fensters.
+   */
+  public MapPaneStatusBar getStatusBar() {
+    return this.statusBar;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/LayeredMapPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/LayeredMapPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/LayeredMapPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,341 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import javax.swing.JSplitPane;
+import javax.swing.JScrollPane;
+import java.util.Hashtable;
+
+import org.geotools.map.MapLayer;
+import org.geotools.map.MapContext;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.map.event.MapLayerListEvent;
+import org.geotools.styling.Style;
+import org.geotools.styling.ColorMap;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.coverage.Category;
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+
+import org.opengis.geometry.Envelope; // gt2-2.4.2
+//import org.opengis.spatialschema.geometry.Envelope; // gt2-2.3.4
+
+import adagios.swing.MultiSplitPane;
+
+import schmitzm.swing.JPanel;
+import schmitzm.geotools.gui.MapContextControlPane;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.grid.GridUtil;
+
+/**
+ * Diese Klasse stellt ein Panel dar, in dem layer-basiert Objekte
+ * grafisch dargestellt werden koennen.<br>
+ * Welche Objekte dargestellt werden koennen, ist der Beschreibung der
+ * {@link #isVisualisable(Object)}-Methode zu entnehmen.<br>
+ * Das Fenster besteht aus 2 durch einen Divider voneinander getrennten
+ * Komponenten:
+ * <ol>
+ * <li>Eine Map ({@link JMapPane}) zu grafischen Darstellung der Layer</li>
+ * <li>Eine Liste mit Steuerungskomponenten, ueber die die einzelnen Layer
+ *     angesprochen werden koennen (ein/ausblenden, zoomen, ...).</li>
+ * </ol>
+ * Die grafischen Layer (in der Map) koennen wahlweise (de)aktiviert werden. Dies
+ * geschieht durch setzen/entfernen eines Haekchens in der entsprechenden
+ * Steuerungskomponente. Diese enthaelt zudem ein Kontextmenue, ueber welches
+ * <ul>
+ * <li>das jeweilige Layer aus der Map entfernt werden kann</li>
+ * <li>das jeweilige Layer in der Map eine Ebene nach oben/unten verschoben werden kann</li>
+ * <li>die Map sogezoomt werden kann, dass das jeweilige Layer komplett angezeigt wird</li>
+ * </ul>
+ * Darueberhinaus verwaltet das {@code LayerMapPane} eine Liste von
+ * {@link ColorMapManager Farbpaletten} fuer Rasterdaten, die durch die
+ * Layer-Kontrolle den Raster-Layern zugewiesen werden koennen.<br>
+ * Um Layer einzufuegen koennen die {@code addLayer(.)}-Methoden dieser Klasse
+ * verwendet werden (dabei wird ein Default-Style verwendet) oder die
+ * entsprechenden Methoden des {@link MapContext} ({@code getMapPane().getContext()}).
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class LayeredMapPane extends JPanel {
+  protected MultiSplitPane             horSplitPane     = new MultiSplitPane(2,JSplitPane.HORIZONTAL_SPLIT);
+  /** Komponente, in der die Karten, der Massstab und das Koordinaten-Raster
+   *  dargestellt werden */
+  protected GeoMapPane geoMapPane = null;
+  private   MapContext mapContext = null;
+  private   JMapPane   mapPane = null;
+  private   Hashtable<MapLayer,Object> layerObjects = new Hashtable<MapLayer,Object>();
+  /** Komponente, in der die Layer-Kontrolle dargestellt ist. */
+  protected MapContextControlPane layerControlList = null;
+  /** Auswahl an Farb-Paletten fuer Raster-Daten */
+  protected ColorMapManager colorMaps = null;
+
+
+  /**
+   * Erzeugt eine neue Komponente.
+   */
+  public LayeredMapPane() {
+    this(null);
+  }
+
+  /**
+   * Erzeugt eine neue Komponente.
+   * @param geoMapPane Komponente, die zum Anzeigen der Karten verwendet wird
+   *                   (wenn {@code null} wird eine neue {@link GeoMapPane}-Instanz
+   *                   erzeugt)
+   */
+  public LayeredMapPane(GeoMapPane geoMapPane) {
+    super();
+    if ( geoMapPane == null )
+      geoMapPane = new GeoMapPane();
+    this.setLayout( new BorderLayout() );
+
+    // rechter Bereich: Map, Grids und Scale
+    this.geoMapPane = geoMapPane != null ? geoMapPane : new GeoMapPane();
+    this.mapPane    = geoMapPane.getMapPane();
+    this.mapContext = mapPane.getContext();
+    this.mapContext.addMapLayerListListener( new MapLayerListListener() {
+      public void layerAdded(MapLayerListEvent e) { }
+      public void layerChanged(MapLayerListEvent e) { }
+      public void layerMoved(MapLayerListEvent e) { }
+      public void layerRemoved(MapLayerListEvent e) {
+        layerObjects.remove( e.getLayer() );
+      }
+    } );
+
+    // linker Bereich: Layer-Liste mit Auswahl der Layer
+    this.colorMaps        = new ColorMapManager();
+    this.layerControlList = new MapContextControlPane( mapPane, colorMaps );
+
+    // horizontales SplitPane initialisieren
+    for (int i=0; i<horSplitPane.getContainerCount(); i++)
+      horSplitPane.getContainer(i).setLayout( new BorderLayout() );
+    horSplitPane.setResizeWeigth( new double[] {0.2,0.8} );
+    horSplitPane.setDividerSize(5);
+    horSplitPane.setInnerBorder(null);
+    horSplitPane.getContainer(0).add(new JScrollPane(layerControlList),BorderLayout.CENTER);
+    horSplitPane.getContainer(1).add(geoMapPane);
+
+    this.add( horSplitPane );
+  }
+
+  /**
+   * Liefert den Karten-Bereich der Komponente.
+   */
+  public JMapPane getMapPane() {
+    return mapPane;
+  }
+
+  /**
+   * Liefert das in einem Layer dargestellte Objekt.
+   * @param layer ein Layer
+   */
+  public Object getMapObject(MapLayer layer) {
+    return layerObjects.get(layer);
+  }
+
+  /**
+   * Liefert den Manager fuer die zur Verfuegung stehenden Farbpaletten.
+   */
+  public ColorMapManager getColorMapManager() {
+    return this.colorMaps;
+  }
+
+  /**
+   * Prueft, ob Instanzen einer bestimmten Klasse von {@link LayeredMapFrame}
+   * visualisiert werden koennen. Dies gilt fuer
+   * <ul>
+   *   <li>{@link GridCoverage2D org.geotools.coverage.grid.GridCoverage2D}</li>
+   *   <li>{@link FeatureCollection org.geotools.feature.FeatureCollection}</li>
+   * </ul>
+   */
+  public boolean isVisualisable(Class c) {
+    return GridCoverage2D.class.isAssignableFrom( c ) ||
+           FeatureCollection.class.isAssignableFrom( c );
+  }
+
+  /**
+   * Prueft, ob eine Objekt-Instanz von {@link LayeredMapFrame}
+   * visualisiert werden kann. Dies gilt fuer Instanzen von
+   * <ul>
+   *   <li>{@link GridCoverage2D org.geotools.coverage.grid.GridCoverage2D}</li>
+   *   <li>{@link FeatureCollection org.geotools.feature.FeatureCollection}</li>
+   * </ul>
+   */
+  public boolean isVisualisable(Object o) {
+    return o instanceof GridCoverage2D ||
+           o instanceof FeatureCollection;
+  }
+
+  /**
+   * Fuegt ein Raster-Layer (als oberstes Layer) ein.
+   * @param gc ein Grid-Coverage
+   * @param desc Beschreibung fuer das Raster
+   * @param style Darstellungs-Style fuer das Layer
+   */
+  public MapLayer addLayer(GridCoverage2D gc, String desc, Style style) {
+//    GridSampleDimension gsd = gc.getSampleDimensions()[0];
+//    for (int i = 0; i < gsd.getCategories().size(); i++) {
+//      Category cat = (Category) gsd.getCategories().get(i);
+//      System.out.print("'" + cat.getName() + "'\t" + cat.getRange().toString() +"\t"+cat.isQuantitative()+"\t");
+//      for (int j = 0; j < cat.getColors().length; j++)
+//        System.out.print(cat.getColors()[j].toString() + ",");
+//      System.out.println("\n           > " + cat.toString() + "  (" + cat.getClass().getName() + ")");
+//    }
+
+//    // Farbiges Grid erzeugen
+//    Category[] c = new Category[0];
+//    try {
+//      c = new Category[] {
+////          (Category) gsd.getCategories().get(0),
+////          (Category) gsd.getCategories().get(1)
+//          CoverageUtil.createGeophysicsCategory( new Category("", new Color[]{Color.WHITE, Color.BLACK}, new NumberRange(0.0,255.0),1,0), true ),
+//          CoverageUtil.createGeophysicsCategory( new Category("No data", Color.YELLOW, Float.NaN), false ),
+////          CoverageUtil.createGeophysicsCategory( new Category("No data", new Color(0, 0, 0), Float.NaN), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("0", Color.BLACK, 0.0f), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("1", Color.BLACK, 1.0f), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("2", Color.BLACK, 2.0f), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("3", Color.BLACK, 3.0f), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("4", Color.BLACK, 4.0f), true ),
+////          CoverageUtil.createGeophysicsCategory( new Category("5", Color.BLACK, 5.0f), true )
+//      };
+//    } catch ( Exception err ) {
+//      err.printStackTrace();
+//    }
+//    gc = recolorGrid(gc,0,c);
+//
+//    gsd = gc.getSampleDimensions()[0];
+//    for (int i = 0; i < gsd.getCategories().size(); i++) {
+//      Category cat = (Category) gsd.getCategories().get(i);
+//      System.out.print("'" + cat.getName() + "'\t" + cat.getRange().toString() +"\t"+cat.isQuantitative()+"\t");
+//      for (int j = 0; j < cat.getColors().length; j++)
+//        System.out.print(cat.getColors()[j].toString() + ",");
+//      System.out.println("\n           > " + cat.toString() + "  (" + cat.getClass().getName() + ")");
+//    }
+
+    // Falls kein Style angegeben ist, aus der Standard-Farbpalete erstellen
+    if ( style == null ) {
+      ColorMap colorMap  = colorMaps.getDefaultColorMap();
+      style = GridUtil.createStyle(colorMap,1.0);
+    }
+    // Layer erzeugen
+    mapContext.addLayer(gc, style);
+    MapLayer newLayer = mapContext.getLayer(mapContext.getLayerCount() - 1);
+    newLayer.setTitle(desc);
+
+    // neuer Anzeigebereich: Das komplette Raster
+    if (mapPane.getMapArea() == null) {
+      Envelope e = gc.getEnvelope();
+      com.vividsolutions.jts.geom.Envelope newArea = new com.vividsolutions.jts.
+          geom.Envelope(
+              e.getUpperCorner().getOrdinate(0), // X1
+              e.getLowerCorner().getOrdinate(0), // X2
+              e.getUpperCorner().getOrdinate(1), // Y1
+              e.getLowerCorner().getOrdinate(1) // Y2
+          );
+      mapPane.setMapArea(newArea);
+    }
+
+    // Anzeige aktualisieren
+    mapPane.setReset(true);
+    mapPane.repaint();
+
+    // Wenn Rendering geklappt hat, zum Layer gehoerendes Objekt merken
+    layerObjects.put(newLayer,gc);
+
+    return newLayer;
+  }
+
+  /**
+   * Fuegt ein FeatureCollection-Layer (als oberstes Layer) ein.
+   * @param fc eine Feature-Collection
+   * @param desc Beschreibung fuer die Feature-Collection
+   * @param style Darstellungs-Style fuer das Layer
+   */
+  public MapLayer addLayer(FeatureCollection fc, String desc, Style style) {
+    if ( style == null )
+      style = FeatureUtil.createDefaultStyle(fc);
+    // Layer erzeugen
+    mapContext.addLayer(fc, style);
+    MapLayer newLayer = mapContext.getLayer(mapContext.getLayerCount() - 1);
+    newLayer.setTitle(desc);
+
+    // neuer Anzeigebereich: Die komplette FeatureCollection
+    if (mapPane.getMapArea() == null)
+      mapPane.setMapArea(fc.getBounds());
+
+    // Anzeige aktualisieren
+    mapPane.setReset(true);
+    mapPane.repaint();
+
+    // Wenn Rendering geklappt hat, zum Layer gehoerendes Objekt merken
+    layerObjects.put(newLayer,fc);
+
+    return newLayer;
+  }
+
+  /**
+   * Fuegt ein Layer (als oberstes Layer) ein.
+   * @param obj ein (darstellbares) Objekt
+   * @param desc Beschreibung fuer das Objekt
+   * @param style Darstellungs-Style fuer das Layer
+   * @exception UnsupportedOperationException falls ein nicht-darstellbares
+   *            Objekt uebergeben wird
+   * @see #isVisualisable(Object)
+   * @see #isVisualisable(Class)
+   */
+  public MapLayer addLayer(Object obj, String desc, Style style) {
+    if ( obj instanceof GridCoverage2D )
+      return addLayer( (GridCoverage2D)obj, desc, style );
+    if ( obj instanceof FeatureCollection )
+      return addLayer( (FeatureCollection)obj, desc, style );
+    throw new UnsupportedOperationException("LayeredMapFrame can not visualise objects of class "+obj.getClass().getName());
+  }
+
+  /**
+   * Fuegt ein Layer (als oberstes Layer) ein. Es wird ein Standard-Style zur
+   * Darstellung verwendet.
+   * @param obj ein (darstellbares) Objekt
+   * @param desc Beschreibung fuer das Objekt
+   * @exception UnsupportedOperationException falls ein nicht-darstellbares
+   *            Objekt uebergeben wird
+   * @see #isVisualisable(Object)
+   * @see #isVisualisable(Class)
+   */
+  public MapLayer addLayer(Object obj, String desc) {
+    return addLayer(obj,desc,null);
+  }
+
+  /**
+   * Erzeugt ein neues {@link GridCoverage2D} mit einer neuen Farb-Zuordnung.
+   * @param grid Basis-Grid
+   * @param band Band, dessen Farbe veraendert werden soll
+   * @param cat  Rasterwert/Farb-Zuordnungen
+   */
+  private static GridCoverage2D recolorGrid(GridCoverage2D grid, int band, Category[] cat) {
+    GridSampleDimension[] gsd = grid.getSampleDimensions();
+    gsd[band] = new GridSampleDimension(cat,gsd[band].getUnits());
+
+    return new GridCoverageFactory().create(
+      grid.getName(),
+      grid.getRenderedImage(),
+      grid.getEnvelope(),
+      gsd,
+      new GridCoverage2D[] {grid},
+      null
+    );
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/MapActionControlPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/MapActionControlPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/MapActionControlPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,399 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.util.Map;
+import javax.swing.Action;
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.JButton;
+import javax.swing.JToggleButton;
+import javax.swing.ButtonGroup;
+
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.CaptionsChangeable;
+import schmitzm.geotools.gui.JMapPane;
+
+// fuer Doku
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+
+/**
+ * Diese Klasse stellt einen {@link JToolBar} dar, mit dem zwischen den
+ * verschiedenen "Klick"- und "Drag"-Aktionen des {@link JMapPane} gewechselt werden
+ * kann:
+ * <ul>
+ *   <li><b>Info:</b>
+ *       <ul>
+ *         <li>Links-Klick:  nichts</li>
+ *         <li>Links-Drag: nichts</li>
+ *         <li>Rechts-Klick: nichts</li>
+ *         <li>Rechts-Drag: Karten-Ausschnitt verschieben</li>
+ *       </ul></li>
+ *   <li><b>Zoom:</b>
+ *       <ul>
+ *         <li>Links-Klick:  Zoom in</li>
+ *         <li>Links-Drag: Zoom in auf selektierten Bereich</li>
+ *         <li>Rechts-Klick: Zoom out</li>
+ *         <li>Rechts-Drag: Karten-Ausschnitt verschieben</li>
+ *       </ul></li>
+ *   <li><b>SelectTop:</b><br>
+ *       Die Auswahl-Aktionen beziehen sich auf das oberste sichtbare Feature
+ *       und fuehren zu einem {@link FeatureSelectedEvent}:
+ *       <ul>
+ *         <li>Links-Klick: einzelnes Feature selektieren</li>
+ *         <li>Links-Drag: alle Features im selektierten Bereich auswaehlen</li>
+ *         <li>Rechts-Klick: nichts</li>
+ *         <li>Rechts-Drag: Karten-Ausschnitt verschieben</li>
+ *       </ul></li>
+ *   <li><b>SelectAll:</b><br>
+ *       Wie <i>SelectTop</i>. Die Auswahl-Aktionen beziehen sich jedoch auf
+ *       auf alle prinzipiell sichtbaren Layer (evt. verdeckte Features unterer
+ *       Layer werden auch ausgewaehlt!)
+ *       </li>
+ * </ul>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MapActionControlPane extends JToolBar implements CaptionsChangeable {
+  /** Konstante um die Aktion "Info" in der Mask der zur Verfuegung stehenden
+   *  Buttons anzusprechen. */
+  public static final int ACTION_INFO = 1;
+  /** Konstante um die Aktion "Zoom" in der Mask der zur Verfuegung stehenden
+   *  Buttons anzusprechen. */
+  public static final int ACTION_ZOOM_IN = 2;
+  /** Konstante um die Aktion "SelectAll" in der Mask der zur Verfuegung stehenden
+   *  Buttons anzusprechen. */
+  public static final int ACTION_SELECT_ALL = 4;
+  /** Konstante um die Aktion "SelectTop" in der Mask der zur Verfuegung stehenden
+   *  Buttons anzusprechen. */
+  public static final int ACTION_SELECT_TOP = 8;
+  /** Konstante um die ALLE Aktionen in der Mask der zur Verfuegung stehenden
+   *  Buttons anzusprechen. */
+  public static final int ACTION_ALL = 0xFFFFFFFF;
+
+  /** Key-Konstante um die Aktion "Info" anzusprechen.
+   *  @see #resetCaptions(Map) */
+  public static final String INFO = MapActionControlPane.class.getName()+".INFO";
+  /** Key-Konstante um die Aktion "Zoom" anzusprechen.
+   *  @see #resetCaptions(Map) */
+  public static final String ZOOM_IN = MapActionControlPane.class.getName()+".ZOOM_IN";
+  /** Key-Konstante um die Aktion "SelectAll" anzusprechen.
+   *  @see #resetCaptions(Map) */
+  public static final String SELECT_ALL = MapActionControlPane.class.getName()+".SELECT_ALL";
+  /** Key-Konstante um die Aktion "SelectTop" anzusprechen.
+   *  @see #resetCaptions(Map) */
+  public static final String SELECT_TOP = MapActionControlPane.class.getName()+".SELECT_TOP";
+
+
+  /** {@link JMapPane} das gesteuert wird. */
+  protected JMapPane mapPane = null;
+  /** Button fuer Info-Aktion. */
+  protected JToggleButton infoState = null;
+  /** Button fuer Zoom-Aktion. */
+  protected JToggleButton zoomState = null;
+  /** Button fuer SelectTop-Aktion. */
+  protected JToggleButton selectTopState = null;
+  /** Button fuer SelectAll-Aktion. */
+  protected JToggleButton selectAllState = null;
+  /** Maske, die die zur Verfuegung stehenden Aktionen codiert */
+  protected int actionMask = 0;
+
+  /**
+   * Erzeugt eine horizontale Steuer-Komponente, die (noch) keinem {@link JMapPane}
+   * zugeordnet ist.
+   */
+  public MapActionControlPane() {
+    this( null, HORIZONTAL );
+  }
+
+  /**
+   * Erzeugt eine horizontale Steuer-Komponente.
+   * @param mapPane {@link JMapPane} das gesteuert wird
+   */
+  public MapActionControlPane(JMapPane mapPane) {
+    this( mapPane, HORIZONTAL );
+  }
+
+  /**
+   * Erzeugt eine Steuer-Komponente.
+   * @param mapPane {@link JMapPane} das gesteuert wird
+   * @param orientation Orientierung der Komponente ({@link #HORIZONTAL}/{@link #VERTICAL})
+   * @param actionMask definiert, welche Aktionen (in Form von Buttons) angezeigt werden
+   *                   (OR-Verknuepfung der {@code ACTION}-Konstanten
+   */
+  public MapActionControlPane(JMapPane mapPane, int orientation, int actionMask) {
+    super(orientation);
+    this.actionMask = actionMask;
+    // Button erzeugen und in Gruppe einfuegen, damit immer nur
+    // eine aktiviert ist
+    infoState      = new JToggleButton( new Action_InfoState() );
+    zoomState      = new JToggleButton( new Action_ZoomState() );
+    selectTopState = new JToggleButton( new Action_SelectOnTopLayerState() );
+    selectAllState = new JToggleButton( new Action_SelectOnAllLayerState() );
+    ButtonGroup bGroup = new ButtonGroup();
+    bGroup.add(infoState);
+    bGroup.add(zoomState);
+    bGroup.add(selectTopState);
+    bGroup.add(selectAllState);
+    // Button dem ToolBar hinzufuegen
+    if ( isActionVisible(ACTION_INFO) )
+      this.add(infoState);
+    if ( isActionVisible(ACTION_ZOOM_IN) )
+      this.add(zoomState);
+    if ( isActionVisible(ACTION_SELECT_TOP) )
+      this.add(selectTopState);
+    if ( isActionVisible(ACTION_SELECT_ALL) )
+      this.add(selectAllState);
+
+    setMapPane(mapPane);
+  }
+
+  /**
+   * Erzeugt eine Steuer-Komponente. Alle Aktionen sind sichtbar.
+   * @param mapPane {@link JMapPane} das gesteuert wird
+   * @param orientation Orientierung der Komponente ({@link #HORIZONTAL}/{@link #VERTICAL})
+   */
+  public MapActionControlPane(JMapPane mapPane, int orientation) {
+    this(mapPane,orientation,ACTION_ALL);
+  }
+
+  /**
+   * Setzt die Aktivierung der Aktionen entsprechend den Einstellungen
+   * des {@code JMapPane}.
+   * @see JMapPane#getWindowSelectionState()
+   */
+  public void resetActions() {
+    for (int i=0; i<getComponentCount(); i++) {
+      Component comp = getComponent(i);
+      if ( comp instanceof JButton )
+        ((JButton)comp).setSelected(false);
+    }
+
+    // Buttons entsprechend dem MapPane-Status einstellen
+    infoState.doClick();
+    if ( mapPane != null ) {
+      switch( mapPane.getWindowSelectionState() ) {
+        case JMapPane.ZOOM_IN:     if ( isActionVisible(ACTION_ZOOM_IN) )
+                                     zoomState.doClick();
+                                   break;
+        case JMapPane.SELECT_TOP:  if ( isActionVisible(ACTION_SELECT_TOP) )
+                                     selectTopState.doClick();
+                                   break;
+        case JMapPane.SELECT_ALL:  if ( isActionVisible(ACTION_SELECT_ALL) )
+                                     selectAllState.doClick();
+                                   break;
+        case JMapPane.NONE:        if ( isActionVisible(ACTION_INFO) )
+                                     infoState.doClick();
+                                   break;
+      }
+    }
+  }
+
+  /**
+   * Prueft, ob eine Aktion (Button) zur Verfuegung steht.
+   * @param action Aktion codiert durch eine {@code ACTION}-Konstante
+   */
+  public boolean isActionVisible(int action) {
+    return (actionMask & action) > 0;
+  }
+
+  /**
+   * Setzt das {@link JMapPane}, das durch diese Komponente gesteuert wird.
+   */
+  public void setMapPane(JMapPane mapPane) {
+    this.mapPane = mapPane;
+    resetActions();
+  }
+
+  /**
+   * Liefert das {@link JMapPane}, das durch diese Komponente gesteuert wird.
+   */
+  public JMapPane getMapPane() {
+    return this.mapPane;
+  }
+
+  /**
+   * Belegt die Beschriftungen der Aktionen neu.
+   * @param newCaptions enhaelt die neuen Beschriftungen
+   * @see #INFO
+   * @see #ZOOM_IN
+   * @see #SELECT_TOP
+   * @see #SELECT_ALL
+   */
+  public void resetCaptions(Map<String,Object> newCaptions) {
+    if ( newCaptions == null )
+      return;
+    Object caption = newCaptions.get(INFO);
+    if ( caption != null )
+      infoState.getAction().putValue(AbstractAction.SHORT_DESCRIPTION,caption);
+    caption = newCaptions.get(ZOOM_IN);
+    if ( caption != null )
+      zoomState.getAction().putValue(AbstractAction.SHORT_DESCRIPTION,caption);
+    caption = newCaptions.get(SELECT_TOP);
+    if ( caption != null )
+      selectTopState.getAction().putValue(AbstractAction.SHORT_DESCRIPTION,caption);
+    caption = newCaptions.get(SELECT_ALL);
+    if ( caption != null )
+      selectAllState.getAction().putValue(AbstractAction.SHORT_DESCRIPTION,caption);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////////   Button Aktionen   /////////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Aktion "Info" des {@link MapActionControlPane}.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected class Action_InfoState extends AbstractAction {
+    /**
+     * Erzeugt einen neue Info-Aktion
+     */
+    public Action_InfoState() {
+      super();
+      Icon icon = SwingUtil.createImageIconFromResourcePath("resource/icons/small/info.gif","Info");
+      this.putValue( SHORT_DESCRIPTION, GeotoolsGUIUtil.RESOURCE.getString( INFO ) );
+      this.putValue( SMALL_ICON, icon );
+      if ( icon == null )
+        this.putValue( NAME, "Info" );
+    }
+
+    /**
+     * Fuehrt die Info-Aktion aus.
+     * <ol>
+     *   <li>{@code JMapPane.setWindowSelectionState( JMapPane.NONE )}</li>
+     *   <li>{@code JMapPane.setState( JMapPane.Select )}</li>
+     *   <li>{@code JMapPane.setHighlight( true )}</li>
+     * </ol>
+     * @see JMapPane
+     * @param e das ActionEvent
+     */
+    public void actionPerformed(ActionEvent e) {
+      getMapPane().setWindowSelectionState( JMapPane.NONE );
+      getMapPane().setState(JMapPane.NONE);
+      getMapPane().setHighlight(true);
+    }
+  }
+
+  /**
+   * Aktion "Zoom" des {@link MapActionControlPane}.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected class Action_ZoomState extends AbstractAction {
+    /**
+     * Erzeugt einen neue Zoom-Aktion
+     */
+    public Action_ZoomState() {
+      super();
+      Icon icon = SwingUtil.createImageIconFromResourcePath("resource/icons/small/zoom.gif","ZoomIn");
+      this.putValue( SHORT_DESCRIPTION, GeotoolsGUIUtil.RESOURCE.getString( ZOOM_IN ) );
+      this.putValue( SMALL_ICON, icon );
+      if ( icon == null )
+        this.putValue( NAME, "ZoomIn" );
+    }
+
+    /**
+     * Fuehrt die Zoom-Aktion aus.
+     * <ol>
+     *   <li>{@code JMapPane.setWindowSelectionState( JMapPane.ZOOM )}</li>
+     *   <li>{@code JMapPane.setState( JMapPane.ZoomIn )}</li>
+     *   <li>{@code JMapPane.setHighlight( false )}</li>
+     * </ol>
+     * @see JMapPane
+     * @param e das ActionEvent
+     */
+    public void actionPerformed(ActionEvent e) {
+      getMapPane().setWindowSelectionState( JMapPane.ZOOM_IN );
+      getMapPane().setState(JMapPane.ZOOM_IN);
+      getMapPane().setHighlight(false);
+    }
+  }
+
+
+  /**
+   * Aktion "SelectTop" des {@link MapActionControlPane}.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected class Action_SelectOnTopLayerState extends AbstractAction {
+    /**
+     * Erzeugt einen neue SelectTop-Aktion
+     */
+    public Action_SelectOnTopLayerState() {
+      super();
+      Icon icon = SwingUtil.createImageIconFromResourcePath("resource/icons/small/select_normal.gif","SelectTop");
+      this.putValue( SHORT_DESCRIPTION, GeotoolsGUIUtil.RESOURCE.getString( SELECT_TOP ) );
+      this.putValue( SMALL_ICON, icon );
+      if ( icon == null )
+        this.putValue( NAME, "SelectTop" );
+    }
+
+    /**
+     * Fuehrt die SelectTop-Aktion aus.
+     * <ol>
+     *   <li>{@code JMapPane.setWindowSelectionState( JMapPane.SELECT_TOP )}</li>
+     *   <li>{@code JMapPane.setState( JMapPane.Select )}</li>
+     *   <li>{@code JMapPane.setHighlight( true )}</li>
+     * </ol>
+     * @see JMapPane
+     * @param e das ActionEvent
+     */
+    public void actionPerformed(ActionEvent e) {
+      getMapPane().setWindowSelectionState( JMapPane.SELECT_TOP );
+      getMapPane().setState(JMapPane.SELECT_TOP);
+      getMapPane().setHighlight(true);
+    }
+  }
+
+  /**
+   * Aktion "SelectAll" des {@link MapActionControlPane}.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected class Action_SelectOnAllLayerState extends AbstractAction {
+    /**
+     * Erzeugt einen neue SelectAll-Aktion
+     */
+    public Action_SelectOnAllLayerState() {
+      super();
+      Icon icon = SwingUtil.createImageIconFromResourcePath("resource/icons/small/select_multi.gif","SelectAll");
+      this.putValue( SHORT_DESCRIPTION, GeotoolsGUIUtil.RESOURCE.getString( SELECT_ALL ) );
+      this.putValue( SMALL_ICON, icon );
+      if ( icon == null )
+        this.putValue( NAME, "SelectAll" );
+    }
+
+    /**
+     * Fuehrt die SelectAll-Aktion aus.
+     * <ol>
+     *   <li>{@code JMapPane.setWindowSelectionState( JMapPane.SELECT_ALL )}</li>
+     *   <li>{@code JMapPane.setState( JMapPane.Select )}</li>
+     *   <li>{@code JMapPane.setHighlight( true )}</li>
+     * </ol>
+     * @see JMapPane
+     * @param e das ActionEvent
+     */
+    public void actionPerformed(ActionEvent e) {
+      getMapPane().setWindowSelectionState( JMapPane.SELECT_ALL );
+      getMapPane().setState(JMapPane.SELECT_ALL);
+      getMapPane().setHighlight(true);
+    }
+  }
+
+}

Added: trunk/src/schmitzm/geotools/gui/MapContextControlPane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/MapContextControlPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/MapContextControlPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,635 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.Window;
+import java.awt.Graphics;
+import java.awt.FlowLayout;
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JDialog;
+import javax.swing.BoxLayout;
+import javax.swing.JScrollPane;
+import javax.swing.JCheckBox;
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.event.MenuListener;
+import javax.swing.event.MenuEvent;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.map.event.MapLayerEvent;
+import org.geotools.map.event.MapLayerListEvent;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.styling.RasterSymbolizer;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.FeatureTypeStyle;
+import org.geotools.feature.FeatureCollection;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import adagios.swing.ConnectedPopupMenu;
+
+import schmitzm.lang.LangUtil;
+import schmitzm.swing.CaptionsChangeable;
+import schmitzm.swing.ButtonGroup;
+import schmitzm.swing.event.WindowEventConnector;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.geotools.grid.GridUtil;
+import schmitzm.geotools.map.event.MapLayerAdapter;
+
+import org.apache.log4j.Logger;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import java.awt.Frame;
+import org.geotools.map.event.MapLayerListener;
+
+
+/**
+ * Diese Komponente ist an ein {@link JMapPane} gekoppelt und stellt die
+ * dargestellten Layer in Form eine Liste dar.
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class MapContextControlPane extends JPanel {
+  protected final Logger LOGGER = LangUtil.createLogger(this);
+
+  /** Key, um den Menue-Eintrag "Move layer up" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_MOVE_LAYER_UP = MapContextControlPane.class.getName()+".Menu.MOVE_UP";
+  /** Key, um den Menue-Eintrag "Move layer down" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_MOVE_LAYER_DOWN = MapContextControlPane.class.getName()+".Menu.MOVE_DOWN";
+  /** Key, um den Menue-Eintrag "Zoom to layer" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_ZOOM_TO_LAYER = MapContextControlPane.class.getName()+".Menu.ZOOM_TO";
+  /** Key, um den Menue-Eintrag "Feature-Filter..." in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_FILTER_LAYER = MapContextControlPane.class.getName()+".Menu.FILTER";
+  /** Key, um den Menue-Eintrag "Recolor..." in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_RECOLOR_LAYER = MapContextControlPane.class.getName()+".Menu.RECOLOR";
+  /** Key, um den Menue-Eintrag "Remove layer" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_REMOVE_LAYER = MapContextControlPane.class.getName()+".Menu.REMOVE";
+  /** Key, um den Menue-Eintrag "Show all layers" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_SHOW_ALL_LAYERS = MapContextControlPane.class.getName()+".Menu.SHOWALL";
+  /** Key, um den Menue-Eintrag "Hide all layers" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_HIDE_ALL_LAYERS = MapContextControlPane.class.getName()+".Menu.HIDEALL";
+  /** Key, um den Menue-Eintrag "Invert all layers" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_INVERT_ALL_LAYERS = MapContextControlPane.class.getName()+".Menu.INVERTALL";
+  /** Key, um den Unter-Menue-Eintrag "Customize color map" in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String MENU_CUSTOMIZE_COLOR = MapContextControlPane.class.getName()+".Menu.CUSTOMIZE";
+  /** Key, um den Titel des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_TITLE = MapContextControlPane.class.getName()+".ColorMapDialog.TITLE";
+  /** Key, um den 1. Tabellenkopf-Eintrag "Quantity" des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_TABLEHEADER_QUANTITY = ColorMapTable.TABLEHEADER_QUANTITY;
+  /** Key, um den 2. Tabellenkopf-Eintrag "Color" des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_TABLEHEADER_COLOR = ColorMapTable.TABLEHEADER_COLOR;
+  /** Key, um den 3. Tabellenkopf-Eintrag "Label" des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_TABLEHEADER_LABEL = ColorMapTable.TABLEHEADER_LABEL;
+  /** Key, um den OK-Button des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_OK = MapContextControlPane.class.getName()+".ColorMapDialog.OK";
+  /** Key, um den ABBRECHEN-Button des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_CANCEL = MapContextControlPane.class.getName()+".ColorMapDialog.CANCEL";
+  /** Key, um den SPEICHERN-Button des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_SAVE = MapContextControlPane.class.getName()+".ColorMapDialog.SAVE";
+  /** Key, um den ÃœBERNEHMEN-Button des Farbpaletten-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_DIALOG_APPLY = MapContextControlPane.class.getName()+".ColorMapDialog.APPLY";
+  /** Key, um den Titel des Farbpaletten-Speichern-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_SAVE_DIALOG_TITLE = MapContextControlPane.class.getName()+".SaveColorMapDialog.TITLE";
+  /** Key, um den Text des Farbpaletten-Speichern-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String CM_SAVE_DIALOG_QUESTION = MapContextControlPane.class.getName()+".SaveColorMapDialog.QUESTION";
+  /** Key, um den Titel des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_TITLE = FeatureLayerFilterDialog.DIALOG_TITLE;
+  /** Key, um das Attribute-Label des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_ATTRIBUTE = FeatureCollectionFilterPanel.ATTRIBUTE_LABEL;
+  /** Key, um das Formel-Label des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_RULE = FeatureCollectionFilterPanel.RULE_LABEL;
+  /** Key, um den Formel-Feld-Tooltip des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_RULE_TOOLTIP = FeatureCollectionFilterPanel.RULE_TOOLTIP;
+  /** Key, um das Operatoren-Label des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_OPERATORS = FeatureCollectionFilterPanel.OPERATOR_LABEL;
+  /** Key, um den Testen-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_TEST = FeatureCollectionFilterPanel.TEST_BUTTON;
+  /** Key, um den OK-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_OK = FeatureLayerFilterDialog.OK_BUTTON;
+  /** Key, um den ABBRECHEN-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_CANCEL = FeatureLayerFilterDialog.CANCEL_BUTTON;
+  /** Key, um den ÃœBERNEHMEN-Button des FeatureCollection-Filter-Dialogs in der {@link CaptionsChangeable}-Map anzusprechen. */
+  public static final String FF_DIALOG_APPLY = FeatureLayerFilterDialog.APPLY_BUTTON;
+
+  private JPanel THIS = this;
+  /** Karte, deren Layer kontrolliert werden. */
+  protected JMapPane        mapPane    = null;
+  /** Farbpaletten, die durch die Kontrolle zugewiesen werden koennen. */
+  private ColorMapManager colorMaps  = null;
+
+  /**
+   * Erzeugt eine neue Layer-Kontrolle.
+   * @param mapPane   Karten-Komponente, deren Layer kontrolliert wird
+   * @param colorMaps Raster-Farbpaletten, die durch die Kontrolle zugewiesen
+   *                  werden koennen
+   */
+  public MapContextControlPane(JMapPane mapPane, ColorMapManager colorMaps) {
+    super();
+//    this.parentFrame = SwingUtil.getParentWindow(this);
+    this.mapPane     = mapPane;
+    this.colorMaps   = colorMaps;
+
+    setLayout( new BoxLayout(this,BoxLayout.Y_AXIS) );
+    // Auf den MapContext lauschen und Steuerungskomponenten hinzufuegen,
+    // loeschen, verschieben
+    mapPane.getContext().addMapLayerListListener( new MapLayerListListener() {
+      public void layerAdded(MapLayerListEvent e) {
+        addLayerControl( getComponentCount()-e.getToIndex(), e.getLayer() );
+      }
+      public void layerChanged(MapLayerListEvent e) {
+        // Bezeichnung der Checkbox aendern
+        ((JCheckBox)getComponent( convertIndex(e.getFromIndex()) )).setText( e.getLayer().getTitle() );
+      }
+      public void layerMoved(MapLayerListEvent e) {
+        int from = convertIndex(e.getFromIndex());
+        int to   = convertIndex(e.getToIndex());
+        moveLayerControl( from, to );
+      }
+      public void layerRemoved(MapLayerListEvent e) {
+        int from = convertIndex(e.getFromIndex());
+        removeLayerControl( from );
+      }
+    });
+  }
+
+  /**
+   * Liefert die Farbpaletten, die durch die Kontroll-Komponente zugewiesen
+   * werden koennen.
+   */
+  public ColorMapManager getColorMapManager() {
+    return this.colorMaps;
+  }
+
+  /**
+   * Konvertiert einen Listen-Index des MapContext zu einem Index der
+   * MapContextControlList (oder layerObjects-Liste).
+   * Dies ist notwendig, da im MapContext der Index 0 fuer das unterste
+   * Layer steht, in der MapContextControlList (und layerObjects-Liste)
+   * jedoch fuer das oberste Layer!
+   */
+  private int convertIndex(int mapContextIdx) {
+    return getComponentCount()-1-mapContextIdx;
+  }
+
+  /**
+   * Fuegt eine neue Checkbox in die Liste der Kontroll-Komponenten ein
+   * @param idx   Stelle an der die Komponente eingefuegt wird (0 ist das oberste Layer)
+   * @param layer das neue Layer
+   */
+  private void addLayerControl(int idx, final MapLayer layer) {
+    MapLayerControl control = new MapLayerControl(layer);
+    add( control, idx );
+    validate();
+  }
+
+  /**
+   * Bewegt eine Kontroll-Komponente
+   * @param from Index der zu verschiebenden Komponente (0 ist die oberste)
+   * @param to   Index der Ziel-Position
+   */
+  private void moveLayerControl(int from, int to) {
+    JCheckBox cb = (JCheckBox)getComponent( from );
+    remove(cb);
+    add( cb, to );
+    validate();
+  }
+
+  /**
+   * Entfernt eine Kontroll-Komponente
+   * @param idx Index der zu loeschenden Komponente (0 ist die oberste)
+   */
+  private void removeLayerControl(int idx) {
+    remove(idx);
+    validate();
+    repaint();
+  }
+
+  //////////////////////////////////////////////////////////////////////////
+  // Kontext-Menue fuer Layer-Aktionen
+  //////////////////////////////////////////////////////////////////////////
+  private class MapLayerControl extends JCheckBox implements CaptionsChangeable, ActionListener {
+    private MapLayer  layer = null;
+    // Flag, ob bereits ein WindowListener im uebergeordneten Fenster
+    // eingetragen wurde (geschieht beim ersten Anzeigen)
+    private boolean windowListenerAdded = false;
+    // Menu-Items
+    private JMenuItem    moveLayerUp = null;
+    private JMenuItem    moveLayerDown = null;
+    private JMenuItem    zoomToLayer = null;
+    private JMenuItem    filterLayer = null;
+    private ColorMapMenu recolorLayer = null;
+    private JMenuItem    removeLayer = null;
+    private JMenuItem    showAllLayers = null;
+    private JMenuItem    hideAllLayers = null;
+    private JMenuItem    invertAllLayers = null;
+    private FeatureLayerFilterDialog filterDialog = null;
+
+    public MapLayerControl(final MapLayer layer) {
+      super( layer.getTitle(), layer.isVisible() );
+      this.layer = layer;
+      this.addActionListener( this ); // (un)check Checkbox
+
+      // Popup-Menue erzeugen, die die Funktionen des Layers steuert
+      final ConnectedPopupMenu menu = new ConnectedPopupMenu();
+      moveLayerUp     = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_MOVE_LAYER_UP));
+      moveLayerDown   = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_MOVE_LAYER_DOWN));
+      zoomToLayer     = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_ZOOM_TO_LAYER));
+      filterLayer     = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_FILTER_LAYER));
+      recolorLayer    = new ColorMapMenu(GeotoolsGUIUtil.RESOURCE.getString(MENU_RECOLOR_LAYER),layer);
+      removeLayer     = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_REMOVE_LAYER));
+      showAllLayers   = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_SHOW_ALL_LAYERS));
+      hideAllLayers   = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_HIDE_ALL_LAYERS));
+      invertAllLayers = new JMenuItem(GeotoolsGUIUtil.RESOURCE.getString(MENU_INVERT_ALL_LAYERS));
+      moveLayerDown.addActionListener(this);
+      moveLayerUp.addActionListener(this);
+      zoomToLayer.addActionListener(this);
+      removeLayer.addActionListener(this);
+      showAllLayers.addActionListener(this);
+      hideAllLayers.addActionListener(this);
+      invertAllLayers.addActionListener(this);
+      filterLayer.addActionListener(this);
+      filterLayer.setEnabled( !JMapPane.isGridCoverageLayer(layer) );
+      if ( filterLayer.isEnabled() )
+        try {
+          filterDialog = new FeatureLayerFilterDialog(null,mapPane,layer);
+          filterDialog.setModal( false );
+        } catch (Exception err) {
+          LangUtil.logDebugError(LOGGER,"Layer "+layer.getTitle()+": FeatureFilterFrame could not be created!",err);
+          filterLayer.setEnabled(false);
+        }
+      menu.add( moveLayerUp );
+      menu.add( moveLayerDown );
+      menu.add( zoomToLayer );
+      menu.add( filterLayer );
+      menu.add( recolorLayer );
+      menu.addSeparator();
+      menu.add( removeLayer );
+      menu.addSeparator();
+      menu.add( showAllLayers );
+      menu.add( hideAllLayers );
+      menu.add( invertAllLayers );
+
+      menu.connectTo(this);
+
+      layer.addMapLayerListener( new MapLayerListener() {
+        public void layerChanged(MapLayerEvent e) {
+        }
+        public void layerHidden(MapLayerEvent e) {
+          setSelected( false );
+        }
+        public void layerShown(MapLayerEvent e) {
+          setSelected( true );
+        }
+      } );
+    }
+
+    /**
+     * Ruft die {@code super}-Methode auf. Zudem wird (beim ersten Anzeigen)
+     * ein {@link WindowEventConnector} in das uebergeordnete Fenster eingetragen,
+     * der dessen Aktionen mit denen des Feature-Filter-Fensters verbindet.
+     * @param g Graphics
+     */
+    public void paint(Graphics g) {
+      super.paint(g);
+      // Wenn das Fenster geschlossen/minimiert wird, soll auch das
+      // Feature-Filter-Fenster geschlossen/minimiert werden
+      if ( !windowListenerAdded && filterDialog != null ) {
+        Window parentFrame = SwingUtil.getParentWindow(this);
+        if ( parentFrame != null) {
+          parentFrame.addWindowListener(new WindowEventConnector(filterDialog));
+          windowListenerAdded = true;
+        }
+      }
+    }
+
+    /**
+     * Initiiert die Checkbox- und Menu-Aktionen, je nachdem, welche Quelle
+     * das ActionEvent hat.
+     * @param e ActionEvent
+     */
+    public void actionPerformed(ActionEvent e) {
+      if (e == null)
+        return;
+      Object source = e.getSource();
+      MapContext context = mapPane.getContext();
+      int        currIdx = context.indexOf(layer);
+
+      // Checkbox (un)checked -> (in)visible layer
+      if ( source == this )
+        layer.setVisible( !layer.isVisible() );
+      // Menu "Move layer up"
+      if ( source == moveLayerUp && currIdx + 1 < context.getLayerCount() )
+        context.moveLayer(currIdx, currIdx + 1);
+      // Menu "Move layer down"
+      if ( source == moveLayerDown && currIdx > 0)
+        context.moveLayer(currIdx, currIdx - 1);
+      // Menu "Zoom to layer"
+      if ( source == zoomToLayer )
+        mapPane.zoomToLayer(layer);
+      // Menu "Remove layer"
+      if ( source == removeLayer )
+        context.removeLayer(layer);
+      // Menu "Show all layer"
+      if ( source == showAllLayers )
+        for (MapLayer layer : context.getLayers() )
+          layer.setVisible( true );
+      // Menu "Hide all layer"
+      if ( source == hideAllLayers )
+        for (MapLayer layer : context.getLayers() )
+          layer.setVisible( false );
+      // Menu "Invert all layers"
+      if ( source == invertAllLayers )
+        for (MapLayer layer : context.getLayers() )
+          layer.setVisible( !layer.isVisible() );
+      // Menu "Filter layer"
+      if ( source == filterLayer ) {
+        filterDialog.toFront();
+        filterDialog.setVisible( true );
+      }
+      mapPane.refresh();
+    }
+
+    /**
+     * Belegt die Bezeichnungen der Menue-Eintraege neu.
+     * @param captionMap Map mit neuen Beschriftungen
+     */
+    public void resetCaptions(Map<String,Object> captionMap) {
+      SwingUtil.resetCaption ( moveLayerUp,     captionMap.get(MENU_MOVE_LAYER_UP) );
+      SwingUtil.resetCaption ( moveLayerDown,   captionMap.get(MENU_MOVE_LAYER_DOWN) );
+      SwingUtil.resetCaption ( zoomToLayer,     captionMap.get(MENU_ZOOM_TO_LAYER) );
+      SwingUtil.resetCaption ( recolorLayer,    captionMap.get(MENU_RECOLOR_LAYER) );
+      SwingUtil.resetCaption ( removeLayer,     captionMap.get(MENU_REMOVE_LAYER) );
+      SwingUtil.resetCaption ( showAllLayers,   captionMap.get(MENU_SHOW_ALL_LAYERS) );
+      SwingUtil.resetCaption ( hideAllLayers,   captionMap.get(MENU_HIDE_ALL_LAYERS) );
+      SwingUtil.resetCaption ( invertAllLayers, captionMap.get(MENU_INVERT_ALL_LAYERS) );
+      SwingUtil.resetCaption ( filterLayer,     captionMap.get(MENU_FILTER_LAYER) );
+      recolorLayer.resetCaptions(captionMap);
+      if ( filterDialog != null )
+        filterDialog.resetCaptions(captionMap);
+    }
+  }
+
+  //////////////////////////////////////////////////////////////////////////
+  // Menue fuer Farb-Paletten
+  //////////////////////////////////////////////////////////////////////////
+  private class ColorMapMenu extends JMenu implements CaptionsChangeable {
+    private MapLayer       layer             = null;
+    private ButtonGroup    menuItemGroup     = null;
+    // Flag, ob bereits ein WindowListener im uebergeordneten Fenster
+    // eingetragen wurde (geschieht beim ersten Anzeigen)
+    private boolean windowListenerAdded = false;
+    // statischer Menue-Eintrag zum Anpassen der Farbtabelle
+    private JMenuItem      customizeMenuItem = null;
+    // Button fuer Farbtabellen-Dialog (Instanzvariablen, damit
+    // Beschriftung geaendert werden kann!!)
+    private JButton okButton = null;
+    private JButton cancelButton = null;
+    private JButton applyButton = null;
+    private JButton saveButton = null;
+    // Farbtabellen-Dialog
+    private ColorMapTable  customiseDialogTable = new ColorMapTable();
+    private JDialog        customiseDialog      = null;
+    private String         cmDialogTitle         = GeotoolsGUIUtil.RESOURCE.getString(CM_DIALOG_TITLE);
+    private String         saveCMDialogTitle     = GeotoolsGUIUtil.RESOURCE.getString(CM_SAVE_DIALOG_TITLE);
+    private String         saveCMDialogQuestion  = GeotoolsGUIUtil.RESOURCE.getString(CM_SAVE_DIALOG_QUESTION);
+
+    /**
+     * Aktion, wenn eine der registrierten Farb-Paletten ausgewaehlt wird.
+     */
+    private ActionListener colorMapAction = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        String   colMapName = ((JCheckBoxMenuItem)e.getSource()).getText();
+        ColorMap colMap = (colMapName!=null) ? StylingUtil.cloneColorMap(colorMaps.get(colMapName)) : null;
+        if ( colMap != null ) {
+          layer.setStyle(GridUtil.createStyle(colMap, 1.0));
+          customiseDialogTable.setColorMap(colMap);
+          mapPane.setReset(true);
+          mapPane.repaint();
+          ((JCheckBoxMenuItem)e.getSource()).setSelected(true);
+        }
+      }
+    };
+
+    /**
+     * Aktion, um die aktuelle Farb-Paletten anzupassen.
+     */
+    private ActionListener customiseMapAction = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if ( customiseDialog.isVisible() ) {
+          customiseDialog.toFront();
+          return;
+        }
+        customiseDialogTable.setColorMap( StylingUtil.cloneColorMap( StylingUtil.getColorMapFromStyle(layer.getStyle()) ) );
+        customiseDialog.setVisible(true);
+      }
+    };
+
+    /**
+     * Erzeugt ein neues Menue.
+     * @param desc  Beschreibung fuer das Menue
+     * @param layer Layer, das durch das Menue gesteuert wird
+     */
+    public ColorMapMenu(String desc, MapLayer layer) {
+      super(desc);
+      this.layer = layer;
+      this.customiseDialog = createColorMapDialog();
+      customizeMenuItem = new JMenuItem( GeotoolsGUIUtil.RESOURCE.getString(MENU_CUSTOMIZE_COLOR) );
+      customizeMenuItem.addActionListener( customiseMapAction );
+      // Initialer Aufbau
+      reorganise();
+      // Sobald das Menue neu angezeigt wird, muss es neu aufgebaut
+      // werden, wenn sich die vorgefertigten ColorMaps geaendert haben
+      this.addMenuListener( new MenuListener() {
+        private ColorMapManager oldColorMaps = (ColorMapManager)colorMaps.clone();
+        public void menuCanceled(MenuEvent e) {}
+        public void menuDeselected(MenuEvent e) {}
+        public void menuSelected(MenuEvent e) {
+          if ( !colorMaps.equals(oldColorMaps) ) {
+            reorganise();
+            oldColorMaps = (ColorMapManager)colorMaps.clone();
+          }
+        }
+      });
+    }
+
+    /**
+     * Baut das Menue neu auf.
+     */
+    public void reorganise() {
+      // Alle bisherigen Eintraege loeschen
+      this.removeAll();
+
+      // nur fuer GridCoverage2D-Objekte kann die Farbe veraendert werden
+//      FeatureTypeStyle[] fts = layer.getStyle().getFeatureTypeStyles();
+//      if ( !(fts[0].getRules()[0].getSymbolizers()[0] instanceof RasterSymbolizer) ) {
+      if ( !JMapPane.isGridCoverageLayer(layer) ) {
+        this.setEnabled(false);
+        return;
+      }
+
+      this.setEnabled(true);
+      this.menuItemGroup = new ButtonGroup();
+
+      // Einen Menue-Eintrag fuer jede registrierte Farb-Palette
+      Iterator<String> keys = colorMaps.keySet().iterator();
+      for (int i=0; keys.hasNext(); i++) {
+        String colMapName = keys.next();
+        JCheckBoxMenuItem item = new JCheckBoxMenuItem(colMapName);
+        item.addActionListener( colorMapAction );
+        menuItemGroup.add( item );
+        // Eintrag selektieren, wenn er vorher selektiert war
+        item.setSelected( StylingUtil.colorMapsEqual(
+            colorMaps.get(colMapName),
+            StylingUtil.getColorMapFromStyle(layer.getStyle())
+         ));
+        this.add( item );
+
+      }
+
+      // Einen Menue-Eintrag, um die Farb-Palette anzupassen
+      if ( this.getItemCount() > 0 )
+        this.addSeparator();
+      this.add( customizeMenuItem );
+    }
+
+    public void resetCaptions(Map<String,Object> captionMap) {
+      SwingUtil.resetCaption ( customizeMenuItem, captionMap.get(MENU_CUSTOMIZE_COLOR) );
+      SwingUtil.resetCaption ( okButton, captionMap.get(CM_DIALOG_OK) );
+      SwingUtil.resetCaption ( cancelButton, captionMap.get(CM_DIALOG_CANCEL) );
+      SwingUtil.resetCaption ( saveButton, captionMap.get(CM_DIALOG_SAVE) );
+      SwingUtil.resetCaption ( applyButton, captionMap.get(CM_DIALOG_APPLY) );
+      SwingUtil.resetCaption ( customiseDialog, captionMap.get(CM_DIALOG_TITLE) );
+
+      Object caption = captionMap.get( CM_DIALOG_TITLE );
+      if ( caption != null ) {
+        cmDialogTitle = caption.toString();
+        // Damit Layer-Bezeichnung in Titel uebernommen wird, ein MapLayerEvent
+        // ausloesen
+        layer.setTitle( layer.getTitle() );
+      }
+      caption = captionMap.get( CM_SAVE_DIALOG_TITLE );
+      if ( caption != null )
+        saveCMDialogTitle = caption.toString();
+      caption = captionMap.get( CM_SAVE_DIALOG_QUESTION );
+      if ( caption != null )
+        saveCMDialogQuestion = caption.toString();
+    }
+
+    /**
+     * Ruft die {@code super}-Methode auf. Zudem wird (beim ersten Anzeigen)
+     * ein {@link WindowEventConnector} in das uebergeordnete Fenster eingetragen,
+     * der dessen Aktionen mit denen des Farbpaletten-Fensters verbindet.
+     * @param g Graphics
+     */
+    public void paint(Graphics g) {
+      super.paint(g);
+      // Wenn das Fenster geschlossen/minimiert wird, soll auch das
+      // Farbpaletten-Fenster geschlossen/minimiert werden
+      if ( !windowListenerAdded ) {
+        Window parentFrame = SwingUtil.getParentWindow(this);
+        if ( parentFrame != null ) {
+          parentFrame.addWindowListener(new WindowEventConnector(customiseDialog));
+          windowListenerAdded = true;
+        }
+      }
+    }
+    /**
+     * Erzeugt einen Dialog, in dem die aktuelle Farb-Palette des (Raster-)
+     * Layers angezeigt wird und veraendert werden kann.
+     */
+    private JDialog createColorMapDialog() {
+      // Optionen des Dialogs (ActionListener folgen spaeter)
+      okButton = new JButton( SwingUtil.RESOURCE.getString("Ok") );
+      cancelButton = new JButton( SwingUtil.RESOURCE.getString("Cancel") );
+      applyButton = new JButton( SwingUtil.RESOURCE.getString("Apply") );
+      saveButton = new JButton( SwingUtil.RESOURCE.getString("Save") );
+      // Dialog aufbauen
+      JOptionPane pane = new JOptionPane(
+        new JScrollPane( customiseDialogTable ),
+        JOptionPane.QUESTION_MESSAGE,
+        JOptionPane.DEFAULT_OPTION,
+        null,
+        new JButton[] { okButton, cancelButton, applyButton, saveButton },
+        okButton
+      );
+      final JDialog dialog = pane.createDialog(SwingUtil.getParentWindow(this), cmDialogTitle + " ["+layer.getTitle()+"]");
+      dialog.setResizable(true);
+      dialog.setModal(false);
+      dialog.setSize(380,200);
+
+      // Aktionen der Farbpaletten-Fenster-Button
+      ActionListener action = new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          // Ok- und Apply-Button > ColorMap uebernehmen
+          if ( e.getSource() == okButton || e.getSource() == applyButton ) {
+            // Farbe nur uebernehmen, wenn sich die Farb-Palette wirklich
+            // geaendert hat
+//            if ( !StylingUtil.colorMapsEqual(
+//                    customiseDialogTable.getColorMap(),
+//                    StylingUtil.getColorMapFromStyle(layer.getStyle()) ) ) {
+              layer.setStyle(GridUtil.createStyle( StylingUtil.cloneColorMap(customiseDialogTable.getColorMap()), 1.0));
+              mapPane.setReset(true);
+              mapPane.repaint();
+              menuItemGroup.setUnselected();
+//            }
+          }
+          // Ok- und Cancel-Button > Dialog schliessen
+          if ( e.getSource() == okButton || e.getSource() == cancelButton ) {
+            dialog.setVisible( false );
+          }
+          // Save-Button > Neue vordefinierte Palette erzeugen
+          if ( e.getSource() == saveButton ) {
+            String name = JOptionPane.showInputDialog(
+                SwingUtil.getParentWindow(THIS),
+                saveCMDialogQuestion,
+                saveCMDialogTitle,
+                JOptionPane.QUESTION_MESSAGE
+            );
+            if ( name != null && !name.trim().equals("") ) {
+              colorMaps.put(name, StylingUtil.cloneColorMap(customiseDialogTable.getColorMap()));
+            }
+          }
+        }
+      };
+      okButton.addActionListener( action );
+      cancelButton.addActionListener( action );
+      applyButton.addActionListener( action );
+      saveButton.addActionListener( action );
+
+      // Wenn sich die die Bezeichnung des Layers aendert, soll auch der
+      // Titel des Farbpaletten-Fensters geaendert werden
+      layer.addMapLayerListener( new MapLayerAdapter() {
+        public void layerChanged(MapLayerEvent e) {
+          // Bezeichnung der Checkbox aendern
+         dialog.setTitle(cmDialogTitle + " ["+layer.getTitle()+"]");
+        }
+      });
+
+      return dialog;
+    }
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/MapPaneStatusBar.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/MapPaneStatusBar.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/MapPaneStatusBar.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,74 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+import org.geotools.map.MapLayer;
+
+import schmitzm.swing.JPanel;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.GeoPositionLabel;
+import schmitzm.geotools.gui.RasterPositionLabel;
+
+/**
+ * Stellt ein {@link BorderLayout}-Panel dar, in dem links ein
+ * {@link RasterPositionLabel} und rechts ein {@link GeoPositionLabel}
+ * dargestellt ist. Beide werden automatisch mit einem {@link JMapPane}
+ * gekoppelt, so dass automatisch die Anzeige der Labels aktualisiert wird,
+ * sobald sich der Cursor ueber die Karte bewegt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MapPaneStatusBar extends JPanel {
+  /** Zeigt die Raster-Koordinaten und den Rasterwert des obersten sichtbaren
+   *  Rasters an */
+  protected RasterPositionLabel rasterPositionLabel = null;
+  /** Zeigt die Welt-Koordinaten an */
+  protected GeoPositionLabel geoPositionLabel = null;
+
+  /**
+   * Erzeugt einen neuen Status-Balken.
+   * @param mapPane Karte mit der die Labels gekoppelt werden
+   */
+  public MapPaneStatusBar(JMapPane mapPane) {
+    this(mapPane,null,null);
+  }
+
+  /**
+   * Erzeugt einen neuen Status-Balken.
+   * @param mapPane Karte mit der die Labels gekoppelt werden
+   * @param rasterPosLabel Label, in dem die Raster-Koordinaten angezeigt
+   *        werden (wenn {@code null}, wird ein neues {@link RasterPositionLabel} erzeugt).
+   * @param geoPosLabel Label, in dem die Geo-Koordinaten angezeigt
+   *        werden (wenn {@code null}, wird ein neues {@link GeoPositionLabel} erzeugt).
+   */
+  public MapPaneStatusBar(JMapPane mapPane, RasterPositionLabel rasterPosLabel, GeoPositionLabel geoPosLabel) {
+    super();
+    setLayout( new BorderLayout() );
+    // rechts: Anzeige der aktuellen Welt-Koordinaten
+    this.geoPositionLabel = (geoPosLabel != null) ? geoPosLabel : new GeoPositionLabel(2);
+    geoPositionLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+    geoPositionLabel.setFractionDigits(1);
+    this.add(geoPositionLabel,BorderLayout.EAST);
+    // links: Anzeige
+    rasterPositionLabel = (rasterPosLabel != null) ? rasterPosLabel : new RasterPositionLabel(2);
+    this.add(rasterPositionLabel,BorderLayout.WEST);
+
+    // Labels mit dem MapPane koppeln
+    mapPane.addMouseMotionListener(rasterPositionLabel);
+    mapPane.addMouseListener(geoPositionLabel);
+    mapPane.addMouseMotionListener(geoPositionLabel);
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/RasterPositionLabel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/RasterPositionLabel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/RasterPositionLabel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,317 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.text.DecimalFormat;
+import java.util.Hashtable;
+import java.util.Map;
+import javax.swing.JLabel;
+
+import org.geotools.map.MapLayer;
+import org.geotools.referencing.CRS;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+import org.geotools.geometry.GeneralDirectPosition;
+
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import schmitzm.data.WritableGrid;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.swing.CaptionsChangeable;
+import schmitzm.geotools.grid.GridUtil;
+
+/**
+ * Diese Klasse stellt ein {@link JLabel} dar, in dem (2dimensionale)
+ * Raster-Koordinaten und der Rasterwert an der entsprechenden Stelle angezeigt
+ * werden.<br>
+ * Die Klasse fungiert als {@link MouseMotionListener} und kann so direkt an ein
+ * {@link JMapPane} gekoppelt werden. Die Koordinaten-Darstellung im Label
+ * aktualisiert sich somit automatisch, sobald sich die Maus ueber die Karte
+ * bewegt.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ *         (University of Bonn/Germany)
+ * @version 1.1
+ *
+ *          SK 01.09.07: finals fuer Variablen eingefuegt... die Funktion
+ *          displayCoordinates() wird oft benutzt und es gab viele Variablen,
+ *          die nie geaendert wurden.
+ */
+public class RasterPositionLabel extends JLabel implements MouseMotionListener,
+		CaptionsChangeable {
+
+	// Faktor mit dem die Koordinaten vor dem Runden multipliziert und nach
+	// dem Runden dividiert werden
+	// --> bestimmt die dargestellten Nachkommastellen
+	private double fracFactor = 0.0;
+
+	/** Speichert das Format fuer die dargstellten Koordinaten */
+	protected DecimalFormat decForm = new DecimalFormat();
+	/**
+	 * Key, um das Label-Praefix in der {@link CaptionsChangeable}-Map
+	 * anzusprechen.
+	 *
+	 * @see #resetCaptions(Map)
+	 */
+	public static final String LABEL_PREFIX = RasterPositionLabel.class
+			.getName()
+			+ ".LABEL_PREFIX";
+	/**
+	 * Praefix, das der Raster-Position vorangestellt wird.
+	 *
+	 * @see #setLabelPrefix(String)
+	 */
+	public static String labelPrefix = GeotoolsGUIUtil.RESOURCE
+			.getString(LABEL_PREFIX);
+
+	/**
+	 * Erzeugt ein neues Label. Der Raster-Wert wird ohne Nachkommastellen
+	 * dargestellt.
+	 */
+	public RasterPositionLabel() {
+		this(0);
+	}
+
+	/**
+	 * Erzeugt ein neues Label.
+	 *
+	 * @param fracDigits
+	 *            Anzahl an Nachkommastellen, auf der Rasterwert gerundet wird
+	 */
+	public RasterPositionLabel(final int fracDigits) {
+		super(" ");
+		// eine Stelle vor dem Komma soll immer dargestellt werden
+		this.decForm.setMinimumIntegerDigits(1);
+		// Nachkommastellen setzen
+		this.setFractionDigits(fracDigits);
+	}
+
+	/**
+	 * Stellt die Koordinaten und den Wert des obersten (sichtbaren) Rasters im
+	 * Label dar, wenn das Event von einem {@link JMapPane} ausgeloest wurde.<br>
+	 * Wird {@link #mouseMoved(MouseEvent)} und
+	 * {@link #mouseDragged(MouseEvent)} aufgerufen.
+	 */
+	protected void displayCoordinates(final MouseEvent e) {
+		if (e == null || !(e.getSource() instanceof JMapPane))
+			return;
+
+		final JMapPane mapPane = (JMapPane) e.getSource();
+		// oberstes dargestelltes Raster suchen
+		final MapLayer layer = determineRasterLayer(mapPane);
+		if (layer == null) {
+			setText("");
+			return;
+		}
+		// Objekt aus Layer herausholen
+		final Object layerObj = JMapPane.getLayerSourceObject(layer);
+		if ((layerObj == null)
+				|| (!(layerObj instanceof GridCoverage2D) && !(layerObj instanceof org.geotools.coverage.grid.io.AbstractGridCoverage2DReader)))
+			return;
+
+		final StringBuffer valueStr = new StringBuffer();
+		if (layerObj instanceof GridCoverage2D) {
+			final GridCoverage2D gc = (GridCoverage2D) layerObj;
+			double[] gcValue = new double[0];
+			final GeneralDirectPosition actPos_GridCRS = new GeneralDirectPosition(
+					2);
+			try {
+				// Welt-Koordinaten der Mausposition ermitteln (in CRS der Map)
+				final Point2D actPos_MapCRS = JMapPane
+						.getMapCoordinatesFromEvent(e);
+				if (actPos_MapCRS == null)
+					return;
+				// Koordinaten in CRS des Rasters umrechnen
+				final MathTransform mapToGrid = CRS.findMathTransform(mapPane
+						.getContext().getCoordinateReferenceSystem(), gc
+						.getCoordinateReferenceSystem());
+				mapToGrid.transform(new GeneralDirectPosition(actPos_MapCRS),
+						actPos_GridCRS);
+				// Wert im Raster ermitteln
+				gcValue = gc.evaluate(actPos_GridCRS.toPoint2D(),
+						(double[]) null);
+			} catch (final Exception err) {
+				// Position out of Raster data
+			}
+
+			valueStr.append(labelPrefix == null ? "" : labelPrefix);
+			// Raster-Koordinaten anzeigen
+			int cellX = 0;
+			int cellY = 0;
+			if (gc instanceof WritableGrid) {
+				// Bei einem WritableGrid koennen diese automatisch ermittelt
+				// werden
+				final WritableGrid wg = (WritableGrid) gc;
+				cellX = wg
+						.convertRealToRaster(actPos_GridCRS.getOrdinate(0), 0);
+				cellY = wg
+						.convertRealToRaster(actPos_GridCRS.getOrdinate(1), 1);
+			} else {
+				// sonst: Raster-Koordinaten ausrechnen
+				final int cell[] = GridUtil.convertRealToRaster(gc,
+						actPos_GridCRS.getCoordinates());
+				cellX = cell[0];
+				cellY = cell[1];
+			}
+			valueStr.append(" (").append(cellX).append("/").append(cellY)
+					.append(")");
+
+			valueStr.append(": ");
+			for (int j = 0; j < gcValue.length; j++) {
+				if (j > 0)
+					valueStr.append(" / ");
+                valueStr.append(Double.isNaN(gcValue[j]) ? gcValue[j] : decForm
+                    .format(gcValue[j]));
+                //Debug: ungerundeten Raster-Wert anzeigen
+                //valueStr.append("   Orig = " + gcValue[j]);
+			}
+		}
+
+		// CHANGE SK 9.8.2008 START
+
+		else if (layerObj instanceof org.geotools.coverage.grid.io.AbstractGridCoverage2DReader) {
+			final GeneralDirectPosition actPos_GridCRS = new GeneralDirectPosition(
+					2);
+			try {
+				// Welt-Koordinaten der Mausposition ermitteln (in CRS der Map)
+				final Point2D actPos_MapCRS = JMapPane
+						.getMapCoordinatesFromEvent(e);
+				if (actPos_MapCRS == null)
+					return;
+				AbstractGridCoverage2DReader gcr = (AbstractGridCoverage2DReader) layerObj;
+				// Koordinaten in CRS des Rasters umrechnen
+				final MathTransform mapToGrid = CRS.findMathTransform(mapPane
+						.getContext().getCoordinateReferenceSystem(), gcr
+						.getCrs());
+				mapToGrid.transform(new GeneralDirectPosition(actPos_MapCRS),
+						actPos_GridCRS);
+				// Wert im Raster ermitteln
+				Hashtable<MapLayer, double[]> foundGridCoverageValues = mapPane
+						.findGridCoverageValues(actPos_GridCRS.toPoint2D(),
+								JMapPane.SELECT_ALL);
+				if (foundGridCoverageValues != null
+						&& foundGridCoverageValues.size() > 0) {
+					double[] gcValue = foundGridCoverageValues.values()
+							.iterator().next();
+					for (int j = 0; j < gcValue.length; j++) {
+						if (j > 0)
+							valueStr.append(" / ");
+						valueStr.append(Double.isNaN(gcValue[j]) ? gcValue[j]
+								: decForm.format(gcValue[j]));
+					}
+				}
+			} catch (final Exception err) {
+				// Position out of Raster data
+			}
+
+			// CHANGE SK 9.8.2008 END
+		}
+
+		// Text im Label anzeigen
+		setText(valueStr.toString());
+	}
+
+	/**
+	 * Ermittelt das Raster-Layer, dessen Koordinaten angezeigt werden.
+	 * Standardmaessig wird das oberste sichtbare Raster-Layer zurueckgegeben.
+	 * Sub-Klassen koennen diese Methode ueberschreiben, um ein anderes Layer zu
+	 * verwenden.
+	 *
+	 * @param mapPane
+	 *            MapPane der angezeigten Layer.
+	 */
+	protected MapLayer determineRasterLayer(final JMapPane mapPane) {
+		return mapPane.getTopVisibleGridCoverageLayer();
+	}
+
+	/**
+	 * Setzt den String, welcher der Rasterposition und dem Rasterwert
+	 * vorangestellt wird.
+	 *
+	 * @param label
+	 *            neues Praefix
+	 */
+	public void setLabelPrefix(final String label) {
+		labelPrefix = (label == null) ? "" : label;
+	}
+
+	/**
+	 * Liefert den String, welcher der Rasterposition und dem Rasterwert
+	 * vorangestellt wird.
+	 */
+	public String getLabelPrefix() {
+		return labelPrefix;
+	}
+
+	/**
+	 * Setzt das Label-Praefix neu, sofern in der Map ein Wert fuer
+	 * {@link #LABEL_PREFIX} hinterlegt ist.
+	 *
+	 * @param captionMap
+	 *            neue Labels
+	 */
+	public void resetCaptions(final Map<String, Object> captionMap) {
+		if (!captionMap.containsKey(LABEL_PREFIX))
+			return;
+		final Object newPrefix = captionMap.get(LABEL_PREFIX);
+		setLabelPrefix(newPrefix == null ? null : newPrefix.toString());
+	}
+
+	/**
+	 * Setzt die Anzahl an Nachkommastellen, die fuer den Rasterwert dargestellt
+	 * werden
+	 *
+	 * @param fracDigits
+	 *            Anzahl an Nachkommastellen
+	 */
+	public void setFractionDigits(final int fracDigits) {
+		this.fracFactor = Math.pow(10.0, fracDigits);
+		this.decForm.setMinimumFractionDigits(fracDigits);
+		this.decForm.setMaximumFractionDigits(fracDigits);
+	}
+
+	/**
+	 * Liefert die Anzahl an Nachkommastellen, die fuer den Rasterwert
+	 * dargestellt werden
+	 */
+	public int getFractionDigits() {
+		return (int) Math.log10(this.fracFactor);
+	}
+
+	/**
+	 * Wird aufgerufen, sobald die Maus bewegt wird. Stellt die Koordinaten und
+	 * den Rasterwert im Label dar, wenn das Event von einem {@link JMapPane}
+	 * ausgeloest wurde.
+	 *
+	 * @see #displayCoordinates(MouseEvent)
+	 */
+	public void mouseMoved(final MouseEvent e) {
+		displayCoordinates(e);
+	}
+
+	/**
+	 * Wird aufgerufen, sobald die Maus bei gedrueckter Taste bewegt wird.
+	 * Stellt die Koordinaten und den Rasterwert im Label dar, wenn das Event
+	 * von einem {@link JMapPane} ausgeloest wurde.
+	 *
+	 * @see #displayCoordinates(MouseEvent)
+	 */
+	public void mouseDragged(final MouseEvent e) {
+		displayCoordinates(e);
+	}
+}

Added: trunk/src/schmitzm/geotools/gui/ScalePane.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/ScalePane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/ScalePane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,86 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import javax.swing.JLabel;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.text.DecimalFormat;
+
+import schmitzm.swing.JPanel;
+import schmitzm.swing.SwingUtil;
+
+/**
+ * Diese Klasse stellt einen Massstab-Balken ({@link ScalePanel}) und die
+ * dazugehoerende Aufloesung in Metern pro Bildschirm-Pixel dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ScalePane extends JPanel{
+  private static final DecimalFormat numFormat = new DecimalFormat("###,###,##0.00");
+
+  /** Massstab-Balken */
+  protected ScalePanel scalePanel  = null;
+  /** Label in dem die Aufloesung in Meter pro Pixel angezeigt wird. */
+  protected JLabel     scaleLabel  = new JLabel("");
+
+  /**
+   * Erzeugt eine Mass-Stab.
+   */
+  public ScalePane() {
+    super();
+    this.scalePanel = new ScalePanel();
+    setScale(1.0);
+    this.setLayout( new GridBagLayout() );
+    this.scaleLabel.setHorizontalAlignment(JLabel.LEFT);
+    this.add( scalePanel, new GridBagConstraints(0,0,2,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0) );
+    this.add( scaleLabel, new GridBagConstraints(0,1,1,1,0.5,0.0,GridBagConstraints.NORTHWEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0) );
+  }
+
+  /**
+   * Liefert die dargestellte Aufloesung in Pixel pro Meter.
+   */
+  public double getScaleInPixels() {
+    return scalePanel.getScaleInPixels();
+  }
+
+  /**
+   * Liefert die dargestellte Aufloesung in Metern pro Pixel.
+   */
+  public double getScaleInMeters() {
+    return scalePanel.getScaleInMeters();
+  }
+
+  /**
+   * Setzt die Aufloesung des Mass-Stabs und aktualisiert die Anzeige der
+   * Komponente.
+   * @param meters Meter, die durch einen Bildschirm-Pixel repraesentiert werden
+   */
+  public void setScale(double meters) {
+    scalePanel.setScale(meters);
+    scaleLabel.setText("1 pixel = "+numFormat.format(meters)+"m");
+    repaint();
+  }
+
+  /**
+   * Setzt die Aufloesung des Mass-Stabs und aktualisiert die Anzeige der
+   * Komponente.
+   * @param pixels Anzahl an Pixeln, die einen Meter repraesentieren werden
+   * @exception Ill
+   */
+  public void setScale(long pixels) {
+    scalePanel.setScale(pixels);
+    scaleLabel.setText(pixels+" pixel = 1m");
+    repaint();
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/ScalePanel.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/ScalePanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/ScalePanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,122 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.gui;
+
+import javax.swing.JLabel;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Color;
+import java.text.DecimalFormat;
+
+import schmitzm.swing.JPanel;
+import schmitzm.swing.SwingUtil;
+
+/**
+ * Stellt einen horizontalen Massstab-Balken dar. Die dargestellte Breite
+ * richtet sich nach der Groesse der Parent-Componente. Der Balken stellt immer
+ * <b>volle</b> 10, 100, 1000, ... Meter dar.<br>
+ * Diese Groesse wird rechts neben dem schwarz/weissen Balken angezeigt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ScalePanel extends JPanel {
+  private static final DecimalFormat numFormat = new DecimalFormat("###,###,##0");
+
+  /** Beinhaltet die aktuell dargestellte Aufloesung. Der Wert entspricht den
+   *  Metern, die durch einen Bildschirm-Pixel dargestellt werden.*/
+  protected double scale = 1;
+
+  /**
+   * Erzeugt eine Mass-Stab.
+   */
+  public ScalePanel() {
+    super();
+    SwingUtil.setPreferredHeight(this,10);
+  }
+
+  /**
+   * Liefert die dargestellte Aufloesung in Pixel pro Meter.
+   */
+  public double getScaleInPixels() {
+    return this.scale!=0 ? 1/this.scale : 0;
+  }
+
+  /**
+   * Liefert die dargestellte Aufloesung in Metern pro Pixel.
+   */
+  public double getScaleInMeters() {
+    return this.scale;
+  }
+
+  /**
+   * Setzt die Aufloesung des Mass-Stabs und aktualisiert die Anzeige der
+   * Komponente.
+   * @param meters Meter, die durch einen Bildschirm-Pixel repraesentiert werden
+   */
+  public void setScale(double meters) {
+    if ( meters <= 0 )
+      throw new IllegalArgumentException("Meters must be greater zero!");
+    this.scale = meters;
+    repaint();
+  }
+
+  /**
+   * Setzt die Aufloesung des Mass-Stabs und aktualisiert die Anzeige der
+   * Komponente.
+   * @param pixels Anzahl an Pixeln, die einen Meter repraesentieren werden
+   * @exception Ill
+   */
+  public void setScale(long pixels) {
+    if ( pixels <= 0 )
+      throw new IllegalArgumentException("Count of pixels must be greater zero!");
+    setScale(1.0/pixels);
+  }
+
+  /**
+   * Zeichnet den Massstab-Balken.
+   * @param g Graphics
+   */
+  public void paint(Graphics g) {
+    if (getParent() == null )
+      return;
+    super.paint(g);
+
+    // maximale Breite fuer Skala (abzgl. 25 Pixel fuer Meter-Angabe)
+    int    maxW_pixel    = getParent().getWidth() - 30;
+    double maxW_meter    = maxW_pixel * getScaleInMeters();
+    // Ausmasse der Skala (stellt immer volle 10/100/1000/... Meter dar)
+    long   scaleW_meter = (long)Math.pow(10, (long)Math.log10(maxW_meter));
+    int    scaleW_pixel = (int)Math.round( scaleW_meter * getScaleInPixels() );
+    int    scaleH_pixel = getHeight()-1;
+
+    // Aufteilung in der Skala Teil-Balken
+    int     tileScaleCount  = 4;
+    int     tileScaleWidth  = scaleW_pixel/tileScaleCount;
+    int     tileScaleHeight = scaleH_pixel;
+    Color[] barColor  = new Color[] { Color.BLACK, Color.WHITE };
+    int     nextColorIdx = 0;
+
+    // Teilbalken malen
+    for (int i = 0; i < tileScaleCount; i++) {
+      g.setColor(barColor[nextColorIdx]);
+      g.fillRect(i * tileScaleWidth, 0, tileScaleWidth, tileScaleHeight);
+      nextColorIdx = 1 - nextColorIdx;
+    }
+    // Rahmen um alle Balken
+    g.setColor(Color.BLACK);
+    g.drawRect(0, 0, tileScaleWidth*tileScaleCount, tileScaleHeight);
+    // Laengen-Angabe
+    g.setFont( new JLabel().getFont() );
+    String scaleString = scaleW_meter >= 1000 ? numFormat.format(scaleW_meter/1000)+"km" : numFormat.format(scaleW_meter)+"m";
+    g.drawString(scaleString,scaleW_pixel+3,tileScaleHeight);
+  }
+}

Added: trunk/src/schmitzm/geotools/gui/StyleToolBar.java
===================================================================
--- trunk/src/schmitzm/geotools/gui/StyleToolBar.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/StyleToolBar.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,298 @@
+package schmitzm.geotools.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import org.apache.log4j.Logger;
+import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffGCSCodes;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.gui.GeoMapPane;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.MapContextControlPane;
+import schmitzm.geotools.gui.MapPaneStatusBar;
+import schmitzm.geotools.gui.JEditorPane.EditorMode;
+import schmitzm.geotools.map.event.FeatureModifiedEvent;
+import schmitzm.geotools.map.event.JEditorPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.LayerEditCanceledEvent;
+import schmitzm.geotools.map.event.LayerEditFinishedEvent;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.geotools.styling.StylingUtil;
+import schmitzm.swing.ButtonGroup;
+import schmitzm.swing.ColorInputOption;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.InputOption;
+import schmitzm.swing.ManualInputOption;
+import schmitzm.swing.MultipleOptionPane;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.event.InputOptionListener;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * A toolbar to define simple parameters of a layer style.
+ * <ul>
+ *   <li>Fill color</li>
+ *   <li>Border color</li>
+ *   <li>Border width</li>
+ *   <li>Mark style (dot, cross, ...)</li>
+ *   <li>Mark size</li>
+ * </ul>.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class StyleToolBar extends JToolBar implements InputOptionListener {
+	protected static final Logger LOGGER = Logger.getLogger(JEditorToolBar.class.getName());
+	
+	private static final String RES_PREFIX = StyleToolBar.class.getName();
+     
+	/** Identifies the "fill color" (value type {@link Color}) for the
+	 *  {@link PropertyChangeListener}.
+	 *  @see #addPropertyChangeListener(PropertyChangeListener) */
+    public static final String FILL_COLOR = "Fill";
+    /** Identifies the "border color" (value type {@link Color}) for the
+     *  {@link PropertyChangeListener}.
+     *  @see #addPropertyChangeListener(PropertyChangeListener) */
+    public static final String BORDER_COLOR = "BorderColor";
+    /** Identifies the "border width" (value type {@code int}) for the
+     *  {@link PropertyChangeListener}.
+     *  @see #addPropertyChangeListener(PropertyChangeListener) */
+    public static final String BORDER_WIDTH = "BorderWidth";
+    /** Identifies the "mark style" (value type {@link String}) for the
+     *  {@link PropertyChangeListener}.
+     *  @see #addPropertyChangeListener(PropertyChangeListener) */
+    public static final String MARK_STYLE = "MarkStyle";
+    /** Identifies the "mark size" (value type {@link int}) for the
+     *  {@link PropertyChangeListener}.
+     *  @see #addPropertyChangeListener(PropertyChangeListener) */
+    public static final String MARK_SIZE = "MarkSize";
+	
+    /** Label next to the fill color {@link ColorInputOption InputOption}. */
+    protected JLabel fillColorLabel = null;
+    /** {@link InputOption} to define the fill color. */
+	protected ColorInputOption fillColor = null;
+    /** Label next to the border color {@link ColorInputOption InputOption}. */
+    protected JLabel borderColorLabel = null;
+    /** {@link InputOption} to define the border color. */
+	protected ColorInputOption borderColor = null;
+	/** Label next to the border width {@link SelectionInputOption InputOption}. */
+	protected JLabel borderWidthLabel = null;
+    /** {@link InputOption} to define the border width. */
+    protected SelectionInputOption.Combo<Integer> borderWidth = null;
+    /** Label next to the mark style and size {@link SelectionInputOption InputOptions}. */
+	protected JLabel markLabel = null;
+    /** {@link InputOption} to define the mark style. */
+	protected SelectionInputOption.Combo<String> markStyle = null;
+    /** {@link InputOption} to define the mark size. */
+    protected SelectionInputOption.Combo<Integer> markSize = null;
+
+    /**
+	 * Creates a new tool bar. Calls {@link #initGUI()}.
+	 */
+	public StyleToolBar() {
+	  super("Defines a style", JToolBar.HORIZONTAL);
+	  setFloatable(false);
+	  setRollover(true);
+	  initGUI();
+	}
+	
+	/**
+	 * Creates the GUI.
+	 */
+	protected void initGUI() {
+	  removeAll();
+	  
+	  //#####  Fill color  #####
+	  fillColorLabel = new JLabel( GeotoolsGUIUtil.RESOURCE.getString(RES_PREFIX+".FillColor") );
+      fillColor = new ColorInputOption(null,true,Color.RED);
+      fillColor.addInputOptionListener(this);
+      SwingUtil.setPreferredWidth(fillColor, 40);
+      SwingUtil.fixComponentSize(fillColor);
+      //##### Border color #####
+      borderColorLabel = new JLabel( GeotoolsGUIUtil.RESOURCE.getString(RES_PREFIX+".BorderColor") );
+      borderColor = new ColorInputOption(null,true,Color.BLACK);
+      borderColor.addInputOptionListener(this);
+      SwingUtil.setPreferredWidth(borderColor, 40);
+      SwingUtil.fixComponentSize(borderColor);
+      //##### Border width #####
+      borderWidthLabel = new JLabel( GeotoolsGUIUtil.RESOURCE.getString(RES_PREFIX+".BorderWidth") );
+      borderWidth = new SelectionInputOption.Combo<Integer>(
+          null,
+          true,
+          new Integer[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20},
+          (Integer)2,
+          null
+      );
+      borderWidth.addInputOptionListener(this);
+      SwingUtil.setPreferredWidth(borderWidth, 50);
+      SwingUtil.fixComponentSize(borderWidth);
+      //##### Mark style #####
+      markLabel = new JLabel( GeotoolsGUIUtil.RESOURCE.getString(RES_PREFIX+".PointMark") );
+      markStyle = new SelectionInputOption.Combo<String>(
+          null,
+          true,
+          new String[] {
+              StyleBuilder.MARK_ARROW, 
+              StyleBuilder.MARK_CIRCLE, 
+              StyleBuilder.MARK_CROSS,
+              StyleBuilder.MARK_SQUARE,
+              StyleBuilder.MARK_STAR,
+              StyleBuilder.MARK_TRIANGLE,
+              StyleBuilder.MARK_X
+          },
+          StyleBuilder.MARK_CIRCLE,
+          new String[] {
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_ARROW"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_CIRCLE"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_CROSS"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_SQUARE"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_STAR"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_TRIANGLE"),
+              GeotoolsGUIUtil.RESOURCE.getString("org.geotools.styling.StyleBuilder.MARK_X")
+          }
+      );
+      markStyle.addInputOptionListener(this);
+      SwingUtil.fixComponentSize(markStyle);
+      markSize = new SelectionInputOption.Combo<Integer>(
+          null,
+          true,
+          new Integer[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20},
+          (Integer)5,
+          null
+      );
+      markSize.addInputOptionListener(this);
+      SwingUtil.setPreferredWidth(markSize, 50);
+      SwingUtil.fixComponentSize(markSize);
+
+      //##### put the components to the tool bar #####
+      add( fillColorLabel );
+      addSeparator( new Dimension(2,0) );
+      add( fillColor );
+      addSeparator( new Dimension(10,0) );
+      add( borderColorLabel );
+      addSeparator( new Dimension(2,0) );
+      add( borderColor );
+      addSeparator( new Dimension(10,0) );
+      add( borderWidthLabel );
+      addSeparator( new Dimension(2,0) );
+      add( borderWidth );
+      addSeparator( new Dimension(10,0) );
+      add( markLabel );
+      addSeparator( new Dimension(2,0) );
+      add( markStyle );
+      addSeparator( new Dimension(2,0) );
+      add( markSize );
+	}
+	
+	/**
+	 * Creates a point style with the currently set point style, size, fill and
+	 * border color.
+	 */
+	public Style createPointStyle() {
+	  return FeatureUtil.createPointStyle(
+	      (String)markStyle.getValue(),
+	      fillColor.getValue(),
+	      borderColor.getValue(),
+	      1.0,
+	      1.0,
+	      ((Number)markSize.getValue()).doubleValue(),
+	      0.0
+	  );
+	}
+
+    /**
+     * Creates a line style with the currently set border color and
+     * width.
+     */
+    public Style createLineStyle() {
+      return FeatureUtil.createLineStyle(
+          borderColor.getValue(),
+          ((Number)borderWidth.getValue()).doubleValue()
+      );
+    }
+
+    /**
+     * Creates a polygon style with the currently set fill, border color and
+     * width.
+     */
+    public Style createPolygonStyle() {
+      return FeatureUtil.createPolygonStyle(
+          fillColor.getValue(),
+          borderColor.getValue(),
+          ((Number)borderWidth.getValue()).doubleValue()
+      );
+    }
+
+    /**
+     * Called whenever one of the input options of this tool bar
+     * has changed its value. Invokes a {@link PropertyChangeEvent}
+     * according to the changed input option.
+     * @see #FILL_COLOR
+     * @see #BORDER_COLOR
+     * @see #BORDER_WIDTH
+     * @see #MARK_STYLE
+     * @see #MARK_SIZE
+     */
+    public void optionChanged(InputOption option, Object oldValue, Object newValue) {
+      String changedProperty = null;
+      if ( option == fillColor )
+        changedProperty = FILL_COLOR;
+      if ( option == borderColor )
+        changedProperty = BORDER_COLOR;
+      if ( option == borderWidth )
+        changedProperty = BORDER_WIDTH;
+      if ( option == markStyle )
+        changedProperty = MARK_STYLE;
+      if ( option == markSize )
+        changedProperty = MARK_SIZE;
+      
+      if ( changedProperty != null )
+        firePropertyChange(changedProperty, oldValue, newValue);
+    }
+
+    /**
+     * Called whenever one of the input options of this tool bar
+     * lost focus. Does nothing.
+     */
+    public void optionLostFocus(InputOption option) {
+    }
+
+    /**
+     * Called whenever one of the input options of this tool bar
+     * gained focus. Does nothing.
+     */
+    public void optionGainedFocus(InputOption option) {}
+
+}

Added: trunk/src/schmitzm/geotools/gui/package.html
===================================================================
--- trunk/src/schmitzm/geotools/gui/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält GUI-Klassen, die auf der <a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek
+	basieren.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/geotools/gui/resource/icons/edit_clear.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/edit_clear.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/edit_finish.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/edit_finish.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/edit_redo.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/edit_redo.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/edit_undo.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/edit_undo.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/layer_cancel.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/layer_cancel.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/layer_finish.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/layer_finish.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/icons/layer_new.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/geotools/gui/resource/icons/layer_new.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle.properties
===================================================================
--- trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,100 @@
+# ---------------------------------------------------------------
+# ------ Default Translations (english) for GUI components ------
+# ------ in Package schmitz.geotools.gui                   ------
+# ---------------------------------------------------------------
+
+Attributes=Attributes
+
+org.geotools.styling.StyleBuilder.MARK_ARROW=Arrow
+org.geotools.styling.StyleBuilder.MARK_CIRCLE=Circle
+org.geotools.styling.StyleBuilder.MARK_CROSS=Cross
+org.geotools.styling.StyleBuilder.MARK_SQUARE=Square
+org.geotools.styling.StyleBuilder.MARK_STAR=Star
+org.geotools.styling.StyleBuilder.MARK_TRIANGLE=Triangle
+org.geotools.styling.StyleBuilder.MARK_X=X
+
+schmitzm.geotools.feature.FeatureTableModel.AttrName=Attribut
+schmitzm.geotools.feature.FeatureTableModel.AttrType=Typ
+schmitzm.geotools.feature.FeatureTableModel.AttrValue=Wert
+schmitzm.geotools.gui.FeatureCollectionFilterPanel.TestButton=Test filter
+schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE=Feature-Filter
+schmitzm.geotools.gui.MapActionControlPane.INFO=Info
+schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN=Zoom in
+schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT=Zoom out
+schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP=Select from top layer
+schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL=Select from all layers
+schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP=Move layer UP
+schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN=Move layer DOWN
+schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO=Zoom to layer
+schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER=Filter layer...
+schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR=Recolor layer
+schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE=Remove layer
+schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL=Show all layers
+schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL=Hide all layers
+schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL=Invert all layers
+schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE=Customize...
+schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE=Color map
+schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE=New color map
+schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION=Enter a name for the new color map...
+schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY=Quantity
+schmitzm.geotools.gui.ColorMapTable.Header.COLOR=Color
+schmitzm.geotools.gui.ColorMapTable.Header.LABEL=Label
+schmitzm.geotools.gui.RasterPositionLabel.LABEL_PREFIX=Raster value
+schmitzm.geotools.gui.GeotoolsGUIUtil.Load=Load
+schmitzm.geotools.gui.GeotoolsGUIUtil.LoadRaster=Load raster
+schmitzm.geotools.gui.GeotoolsGUIUtil.LoadFeature=Load vector
+schmitzm.geotools.gui.GeotoolsGUIUtil.Save=Save
+schmitzm.geotools.gui.GeotoolsGUIUtil.SaveRaster=Save raster
+schmitzm.geotools.gui.GeotoolsGUIUtil.SaveFeature=Save vector
+schmitzm.geotools.gui.GeotoolsGUIUtil.NORTH=North
+schmitzm.geotools.gui.GeotoolsGUIUtil.SOUTH=South
+schmitzm.geotools.gui.GeotoolsGUIUtil.WEST=West
+schmitzm.geotools.gui.GeotoolsGUIUtil.EAST=East
+schmitzm.geotools.gui.GeotoolsGUIUtil.North=Nord
+schmitzm.geotools.gui.GeotoolsGUIUtil.South=South
+schmitzm.geotools.gui.GeotoolsGUIUtil.West=West
+schmitzm.geotools.gui.GeotoolsGUIUtil.East=East
+schmitzm.geotools.gui.GeotoolsGUIUtil.NORTH.Abb=N
+schmitzm.geotools.gui.GeotoolsGUIUtil.SOUTH.Abb=S
+schmitzm.geotools.gui.GeotoolsGUIUtil.WEST.Abb=W
+schmitzm.geotools.gui.GeotoolsGUIUtil.EAST.Abb=E
+schmitzm.geotools.gui.CRSSelectionDialog.init.crs.title=CRS database
+schmitzm.geotools.gui.CRSSelectionDialog.init.crs.mess=Initialize CRS database. Please wait...
+schmitzm.geotools.gui.CRSSelectionDialog.title=Choose a coordinate reference system (CRS)
+schmitzm.geotools.gui.CRSSelectionDialog.mandatory=CRS must be specified!
+schmitzm.geotools.gui.CRSSelectionDialog.button.wgs84=WGS-84 CRS
+schmitzm.geotools.gui.CRSSelectionDialog.button.default=Default CRS (${0})
+schmitzm.geotools.gui.CRSSelectionDialog.button.predefined=Predefined
+schmitzm.geotools.gui.CRSSelectionDialog.button.userDefined=User defined:
+schmitzm.geotools.gui.JEditorPane.Err.MissingMap=First a layer must be displayed (to define CRS and geo-position).
+schmitzm.geotools.gui.JEditorPane.Err.Line.LessPoints=For a line feature at least 2 points must be specified!
+schmitzm.geotools.gui.JEditorPane.Err.Polygon.LessPoints=For a polygon feature at least 3 points must be specified!
+schmitzm.geotools.gui.JEditorToolBar.button.layer.new=Create new layer
+schmitzm.geotools.gui.JEditorToolBar.button.layer.save=Finish layer
+schmitzm.geotools.gui.JEditorToolBar.button.layer.cancel=Abort layer
+schmitzm.geotools.gui.JEditorToolBar.button.edit.undo=Undo last editing operation
+schmitzm.geotools.gui.JEditorToolBar.button.edit.redo=Redo last undone editing operation
+schmitzm.geotools.gui.JEditorToolBar.button.edit.clear=Undo all editing operation
+schmitzm.geotools.gui.JEditorToolBar.button.edit.finish=Start new feature
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.dialog.title=Create new layer...
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.title=Layer title
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.title.default=New Layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type=Layer type
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.point=Point layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.line=Line layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.polygon=Polygon layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.ftype.title=Non-Geometric attributes
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.Err.GeomAttr=Attribute name '${0}' reserverd for default geometry.
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.Err.AutoVal=Auto value not supported for attribut type '${0}'.
+schmitzm.geotools.gui.JEditorToolBar.NewFeature.title=New feature attributes
+schmitzm.geotools.gui.StyleToolBar.FillColor=Fill
+schmitzm.geotools.gui.StyleToolBar.BorderColor=Border
+schmitzm.geotools.gui.StyleToolBar.BorderWidth=Border width
+schmitzm.geotools.gui.StyleToolBar.PointMark=Point style
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrName=Name
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrType=Type
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.Nillable=Nillable
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AutoValue=Auto
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.DefValue=Default
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.NewAttr=Attribute_${0}
+

Added: trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle_de.properties
===================================================================
--- trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle_de.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/gui/resource/locales/GTResourceBundle_de.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,97 @@
+# ----------------------------------------------------
+# ------ German Translations for GUI components ------
+# ------ in Package schmitz.geotools.gui        ------
+# ----------------------------------------------------
+
+Attributes=Attribute
+
+org.geotools.styling.StyleBuilder.MARK_ARROW=Pfeil
+org.geotools.styling.StyleBuilder.MARK_CIRCLE=Kreis
+org.geotools.styling.StyleBuilder.MARK_CROSS=Kreuz
+org.geotools.styling.StyleBuilder.MARK_SQUARE=Quadrat
+org.geotools.styling.StyleBuilder.MARK_STAR=Stern
+org.geotools.styling.StyleBuilder.MARK_TRIANGLE=Dreieck
+org.geotools.styling.StyleBuilder.MARK_X=X
+
+schmitzm.geotools.gui.FeatureCollectionFilterPanel.TestButton=Filter testen
+schmitzm.geotools.gui.FeatureLayerFilterDialog.TITLE=Feature-Filter
+schmitzm.geotools.gui.MapActionControlPane.ZOOM_IN=Heran zoomen
+schmitzm.geotools.gui.MapActionControlPane.ZOOM_OUT=Heraus zoomen
+schmitzm.geotools.gui.MapActionControlPane.SELECT_TOP=Auswählen aus oberstem Layer
+schmitzm.geotools.gui.MapActionControlPane.SELECT_ALL=Auswählen aus allen Layern
+schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_UP=Nach OBEN schieben
+schmitzm.geotools.gui.MapContextControlPane.Menu.MOVE_DOWN=Nach UNTEN schieben
+schmitzm.geotools.gui.MapContextControlPane.Menu.ZOOM_TO=Zu Layer zoomen
+schmitzm.geotools.gui.MapContextControlPane.Menu.FILTER=Layer filtern...
+schmitzm.geotools.gui.MapContextControlPane.Menu.RECOLOR=Färbung ändern
+schmitzm.geotools.gui.MapContextControlPane.Menu.REMOVE=Layer entfernen
+schmitzm.geotools.gui.MapContextControlPane.Menu.SHOWALL=Alle Layer anzeigen
+schmitzm.geotools.gui.MapContextControlPane.Menu.HIDEALL=Alle Layer verbergen
+schmitzm.geotools.gui.MapContextControlPane.Menu.INVERTALL=Alle Layer invertieren
+schmitzm.geotools.gui.MapContextControlPane.Menu.CUSTOMIZE=Anpassen...
+schmitzm.geotools.gui.MapContextControlPane.ColorMapDialog.TITLE=Farbpalette
+schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.TITLE=Farbpalette speichern
+schmitzm.geotools.gui.MapContextControlPane.SaveColorMapDialog.QUESTION=Name für neue Farbpalette...
+schmitzm.geotools.gui.ColorMapTable.Header.QUANTITY=Raster-Wert
+schmitzm.geotools.gui.ColorMapTable.Header.COLOR=Farbe
+schmitzm.geotools.gui.ColorMapTable.Header.LABEL=Label
+schmitzm.geotools.gui.RasterPositionLabel.LABEL_PREFIX=Raster-Wert
+schmitzm.geotools.gui.GeotoolsGUIUtil.Load=Laden
+schmitzm.geotools.gui.GeotoolsGUIUtil.LoadRaster=Raster laden
+schmitzm.geotools.gui.GeotoolsGUIUtil.LoadFeature=Shape laden
+schmitzm.geotools.gui.GeotoolsGUIUtil.Save=Speichern
+schmitzm.geotools.gui.GeotoolsGUIUtil.SaveRaster=Raster speichern
+schmitzm.geotools.gui.GeotoolsGUIUtil.SaveFeature=Shape speichern
+schmitzm.geotools.gui.GeotoolsGUIUtil.NORTH=Nord
+schmitzm.geotools.gui.GeotoolsGUIUtil.SOUTH=Süd
+schmitzm.geotools.gui.GeotoolsGUIUtil.WEST=West
+schmitzm.geotools.gui.GeotoolsGUIUtil.EAST=Ost
+schmitzm.geotools.gui.GeotoolsGUIUtil.North=Norden
+schmitzm.geotools.gui.GeotoolsGUIUtil.South=Süden
+schmitzm.geotools.gui.GeotoolsGUIUtil.West=Westen
+schmitzm.geotools.gui.GeotoolsGUIUtil.East=Osten
+schmitzm.geotools.gui.GeotoolsGUIUtil.NORTH.Abb=N
+schmitzm.geotools.gui.GeotoolsGUIUtil.SOUTH.Abb=S
+schmitzm.geotools.gui.GeotoolsGUIUtil.WEST.Abb=W
+schmitzm.geotools.gui.GeotoolsGUIUtil.EAST.Abb=O
+schmitzm.geotools.gui.CRSSelectionDialog.init.crs.title=CRS datenbank
+schmitzm.geotools.gui.CRSSelectionDialog.init.crs.mess=CRS-Datenbank wird initialisiert. Bitte warten...
+schmitzm.geotools.gui.CRSSelectionDialog.title=Koordinaten-System (CRS) auswählen
+schmitzm.geotools.gui.CRSSelectionDialog.mandatory=CRS must be specified!
+schmitzm.geotools.gui.CRSSelectionDialog.button.wgs84=WGS-84 CRS
+schmitzm.geotools.gui.CRSSelectionDialog.button.default=Standard CRS (${0})
+schmitzm.geotools.gui.CRSSelectionDialog.button.predefined=Vordefiniert
+schmitzm.geotools.gui.CRSSelectionDialog.button.userDefined=Benutzerdefiniert:
+schmitzm.geotools.gui.JEditorPane.Err.MissingMap=Bevor ein neues Layer erstellt werden kann, muss ein Layer angezeigt werden (um CRS und geogr. Lage zu definieren).
+schmitzm.geotools.gui.JEditorPane.Err.Line.LessPoints=Für ein Line-Feature müssen mind. 2 Punkte definiert werden!
+schmitzm.geotools.gui.JEditorPane.Err.Polygon.LessPoints=Für ein Polygon-Feature müssen mind. 3 Punkte definiert werden!
+schmitzm.geotools.gui.JEditorToolBar.button.layer.new=Neues Layer erstellen
+schmitzm.geotools.gui.JEditorToolBar.button.layer.save=Layer abschliessen
+schmitzm.geotools.gui.JEditorToolBar.button.layer.cancel=Layer abbrechen
+schmitzm.geotools.gui.JEditorToolBar.button.edit.undo=Letzte Operation zurücknehmen
+schmitzm.geotools.gui.JEditorToolBar.button.edit.redo=Letzte zurückgenommene Operation wiederholen
+schmitzm.geotools.gui.JEditorToolBar.button.edit.clear=Layer leeren
+schmitzm.geotools.gui.JEditorToolBar.button.edit.finish=Neues Feature starten
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.dialog.title=Neues Layer erzeugen
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.title=Layer-Bezeichnung
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.title.default=Neues Layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type=Layer-Art
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.point=Punkt-Layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.line=Linien-Layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.layer.type.polygon=Polygon-Layer
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.ftype.title=Weitere Attribute
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.Err.GeomAttr=Attribut-Name '${0}' reserviert für das Default-Geometrie-Attribut.
+schmitzm.geotools.gui.JEditorToolBar.NewLayer.Err.AutoVal=Auto-Wert für Attribut-Typ '${0}' nicht möglich.
+schmitzm.geotools.gui.JEditorToolBar.NewFeature.title=Neues Feature
+schmitzm.geotools.gui.StyleToolBar.FillColor=Füllfarbe
+schmitzm.geotools.gui.StyleToolBar.BorderColor=Randfarbe
+schmitzm.geotools.gui.StyleToolBar.BorderWidth=Randbreite
+schmitzm.geotools.gui.StyleToolBar.PointMark=Punkt-Style
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrName=Name
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AttrType=Typ
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.Nillable=Nullable
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.AutoValue=Auto-Wert
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.DefValue=Default
+schmitzm.geotools.gui.FeatureTypeBuilderTableModel.NewAttr=Attribut_${0}
+
+

Added: trunk/src/schmitzm/geotools/io/GeoExportUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/io/GeoExportUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/io/GeoExportUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,235 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.awt.image.Raster;
+import java.awt.image.BufferedImage;
+import java.awt.geom.Rectangle2D;
+import javax.imageio.ImageIO;
+
+import schmitzm.io.IOUtil;
+//import schmitzm.geotools.feature.FeatureCollectionReader;
+
+
+import org.geotools.gce.arcgrid.ArcGridRaster;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.data.FeatureSource;
+import org.geotools.data.FeatureStore;
+import org.geotools.data.Transaction;
+
+import schmitzm.data.WritableGridRaster;
+import schmitzm.geotools.feature.FeatureCollectionReader;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+/**
+ * In dieser Klasse sind Funktionen zum Datenexport von Geo-Daten
+ * zusammengefasst.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeoExportUtil {
+  /**
+   * Diese Methode exportiert eine {@link org.geotools.feature.FeatureCollection}
+   * in das ShapeFile-Format
+   * (<code><i>name</i>.shp <i>name</i>.shx <i>name</i>.dbf</code>).<br>
+   * Baut auf folgenden Geotools-Klassen auf:
+   * <code>
+   * <ul>
+   * <li>{@link ShapefileDataStore       org.geotools.data.shapefile.ShapefileDataStore}</li>
+   * <li>{@link FeatureSource            org.geotools.data.FeatureSource}</li>
+   * <li>{@link FeatureStore             org.geotools.data.FeatureStore}</li>
+   * <li>{@link Transaction              org.geotools.data.Transaction}</li>
+   * <li>{@link FeatureCollection        org.geotools.feature.FeatureCollection}</li>
+   * <li>{@link FeatureCollectionReader  schmitzm.geotools.feature.FeatureCollectionReader}</li>
+   * </ul>
+   * </code>
+   * @param  fc      zu exportierende FeatureCollection
+   * @param  outFile Dateiname (Basisname)
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static void writeFeaturesToShapeFile(FeatureCollection fc, File outFile) throws Exception {
+//      FeatureCollectionReader featureReader = new FeatureCollectionReader(fc);
+
+      // DataStore fuer Ausgabe-Datei oeffnen
+      ShapefileDataStore shpStore = new ShapefileDataStore(outFile.toURI().toURL());
+//      shpStore.createSchema(featureReader.getFeatureType());
+      shpStore.createSchema(fc.getSchema());
+      // FeatureStore aus dem ShapeFile-DataStore ermitteln
+      FeatureSource source       = shpStore.getFeatureSource();
+      FeatureStore  featureStore = (FeatureStore)source;
+      // Features schreiben
+      Transaction transaction = featureStore.getTransaction();
+      featureStore.addFeatures( fc );
+      transaction.commit();
+      transaction.close();
+  }
+
+
+  /**
+   * Diese Methode exportiert ein Raster in eine Datei im ArcInfoASCII-Grid-Format.<br>
+   * Baut auf folgenden Geotools-Klassen auf:
+   * <code>
+   * <ul>
+   * <li>{@link org.geotools.coverage.grid.GridCoverage2D}</li>
+   * <li>{@link org.geotools.gce.arcgrid.ArcGridRaster}</li>
+   * <li>{@link java.awt.image.Raster}</li>
+   * <li>{@link java.awt.geom.Rectangle2D}</li>
+   * </ul>
+   * </code>
+   * @param grid    zu exportierendes Grid
+   * @param outFile Ausgabe-Datei
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static void writeGridToArcInfoASCII(GridCoverage2D grid, File outFile) throws Exception {
+    Raster raster = grid.getRenderedImage().getData();
+    Rectangle2D rect = grid.getEnvelope2D();
+    // Geo-Koordinaten
+    double x = rect.getX();
+    double y = rect.getY();
+    // Zellenbreite
+    double cellsize = rect.getWidth() / raster.getWidth();
+    // Exportieren
+//    throw new UnsupportedOperationException("GeoExportUtil.writeGridToArcInfoASCII(.) has still to be breaked from ArcGridRaster dependency!");
+    ArcGridRaster writer = new ArcGridRaster(new PrintWriter( new FileOutputStream(outFile) ));
+    writer.writeRaster(raster, x, y, cellsize, false);
+    // Projektion schreiben
+    writeProjectionFile( grid.getCoordinateReferenceSystem(),
+                         IOUtil.changeFileExt(outFile,"prj"));
+  }
+
+  /**
+   * Diese Methode exportiert ein Raster in eine Datei im GeoTiff-Format.
+   * Daneben wird ein entsprechendes World-File (.tfw) erstellt, in dem die
+   * Georeferenz und Aufloesung des TIF hinterlegt wird.
+   * @param grid   zu exportierendes Grid
+   * @param output Ausgabe-Datei
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static void writeGridToGeoTiff(GridCoverage2D grid, File output) throws Exception {
+//    for (int i=0; i<ImageIO.getWriterFormatNames().length;i++)
+//      System.out.println(ImageIO.getWriterFormatNames()[i]);
+
+    // GeoTiff-File schreiben
+    ImageIO.write( grid.getRenderedImage(), "tif", output );
+    // World-File schreiben
+    writeWorldFile( grid.getRenderedImage().getWidth(),
+                    grid.getRenderedImage().getHeight(),
+                    grid.getEnvelope2D(),
+                    IOUtil.changeFileExt(output,"tfw"));
+    // Projektion schreiben
+    writeProjectionFile( grid.getCoordinateReferenceSystem(),
+                         IOUtil.changeFileExt(output,"prj"));
+  }
+
+  /**
+   * Diese Methode exportiert ein Raster in eine Datei im ArcInfoASCII-Grid-Format.<br>
+   * Baut auf folgenden Geotools-Klassen auf:
+   * <code>
+   * <ul>
+   * <li>{@link org.geotools.gce.arcgrid.ArcGridRaster}</li>
+   * </ul>
+   * </code>
+   * @param grid    zu exportierendes Grid
+   * @param outFile Ausgabe-Datei
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static void writeGridRasterToArcInfoASCII(WritableGridRaster grid, File outFile) throws Exception {
+    // Exportieren
+    ArcGridRaster writer = new ArcGridRaster(new PrintWriter(new FileOutputStream(outFile)));
+    writer.writeRaster(grid, grid.getX(), grid.getY(), grid.getCellWidth(), false);
+    // Projektion schreiben
+    writeProjectionFile( grid.getCoordinateReferenceSystem(),
+                         IOUtil.changeFileExt(outFile,"prj"));
+  }
+
+  /**
+   * Diese Methode exportiert ein Raster in eine Datei im GeoTiff-Format.
+   * Daneben wird ein entsprechendes World-File (.tfw) erstellt, in dem die
+   * Georeferenz und Aufloesung des TIF hinterlegt wird.
+   * @param grid   zu exportierendes Grid
+   * @param output Ausgabe-Datei
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static void writeGridRasterToGeoTiff(WritableGridRaster grid, File output) throws Exception {
+//    for (int i=0; i<ImageIO.getWriterFormatNames().length;i++)
+//      System.out.println(ImageIO.getWriterFormatNames()[i]);
+
+    // GeoTiff-File schreiben
+    BufferedImage im = new BufferedImage(grid.getWidth(), grid.getHeight(),BufferedImage.TYPE_INT_RGB);
+    im.setData(grid);
+    ImageIO.write( im, "tif", output );
+    // World-File schreiben
+    writeWorldFile(grid.getWidth(), grid.getHeight(), grid.getEnvelope(),IOUtil.changeFileExt(output,"tfw"));
+    // Projektion schreiben
+    writeProjectionFile( grid.getCoordinateReferenceSystem(),
+                         IOUtil.changeFileExt(output,"prj"));
+  }
+
+  /**
+   * Schreibt ein World-File (.tfw) fuer ein Grid. Dabei handelt es sich um
+   * eine zeilenweise ASCII-Datei:<br>
+   * <ol>
+   *    <li>Zellengroesse in X-Richtung (in Meter)</li>
+    *   <li>Koeffizient fuer Rotation in Y-Richtung (<b>wird mit 0.0 befuellt</b>!)</li>
+    *   <li>Koeffizient fuer Rotation in X-Richtung (<b>wird mit 0.0 befuellt</b>!)</li>
+    *   <li>negative Zellengroesse in Y-Richtung (in Meter)</li>
+    *   <li>Horizontale Geo-Referenz (Longitude) der nord-westlichen Ecke</li>
+    *   <li>Vertikale Geo-Referenz (Latitude) der nord-westlichen Ecke</li>
+   * </ol>
+   * @param rasterWidth Breite des Rasters (in Zellen)
+   * @param rasterHeight Hoehe des Rasters (in Zellen)
+   * @param envelope Geo-Referenz des Rasters (Position und Ausdehnung)
+   * @param output Datei in die das World-File geschrieben wird
+   * @exception IOException falls die Datei nicht geschrieben werden kann
+   */
+  public static void writeWorldFile(int rasterWidth, int rasterHeight, Rectangle2D envelope, File output) throws IOException {
+    PrintWriter out = new PrintWriter(output);
+
+    // Zeile 1: Zellengroesse in Meter
+    out.println( envelope.getWidth() / rasterWidth );
+    // Zeile 2: Rotation in Y-Richtung
+    out.println( "0.0" );
+    // Zeile 3: Rotation in X-Richtung
+    out.println( "0.0" );
+    // Zeile 4: ?
+    out.println( - envelope.getHeight() / rasterHeight );
+    // Zeile 5: X-Georeferenz (Longitude) WESTEN
+    out.println( envelope.getX() );
+    // Zeile 6: Y-Georeferenz (Latitude) NORDEN (Im Envelope steht SUEDEN!!)
+    out.println( envelope.getY() + envelope.getHeight() );
+
+    out.flush();
+    out.close();
+  }
+
+  /**
+   * Schreibt ein Projektions-File (.prj) fuer ein {@link CoordinateReferenceSystem}.
+   * @param crs Koordinaten-System
+   * @param output Datei in die die Projektion geschrieben wird
+   * @exception IOException falls die Datei nicht geschrieben werden kann
+   */
+  public static void writeProjectionFile(CoordinateReferenceSystem crs, File output) throws IOException {
+    PrintWriter out = new PrintWriter(output);
+    out.println( crs.toWKT() );
+    out.flush();
+    out.close();
+  }
+
+}
+

Added: trunk/src/schmitzm/geotools/io/GeoImportUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/io/GeoImportUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/io/GeoImportUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,852 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.io;
+
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+import org.geotools.data.DataStore;
+import org.geotools.data.shapefile.Lock;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
+import org.geotools.data.shapefile.shp.ShapefileReader;
+import org.geotools.factory.Hints;
+import org.geotools.feature.DefaultFeatureCollection;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureCollections;
+import org.geotools.gce.arcgrid.ArcGridRaster;
+import org.geotools.gce.geotiff.GeoTiffReader;
+import org.geotools.gce.image.WorldImageReader;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.data.WritableGridRaster;
+import schmitzm.geotools.GTUtil;
+import schmitzm.io.IOUtil;
+
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * In dieser Klasse sind Funktionen zum Datenimport von Geo-Daten
+ * zusammengefasst.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeoImportUtil {
+  private static final Logger LOGGER = Logger.getLogger(GeoImportUtil.class.getName());
+
+  /** These postfixes are associated with Arc/Info ASCII Grid files
+   * TODO .0 habe ich auch schon gesehen. Nut leider kann man das nicht in einen enum packen.
+   * */
+	static public enum ARCASCII_POSTFIXES {
+		arc, dat, ascii, txt, asc
+	};
+
+	/** These postfixes are associated with GeoTiff files */
+	static public enum GEOTIFF_POSTFIXES {
+		tif, tiff
+	};
+
+	/** These postfixes are associated with ESRI SHape files */
+	static public enum SHP_POSTFIXES {
+		shp, sbx, sbn, dbf,
+
+		qix, fix	// Non-ESRI QuadTree Index stuff
+	};
+
+	/** These postfixes are associated with ordenary image files, excluding GeoTIFF endings please */
+	static public enum IMAGE_POSTFIXES {
+		png, gif, jpg, jpeg
+	};
+
+	/** These postfixes are associated with world files */
+	static public enum WORLD_POSTFIXES {
+		wld, jgw, pgw, tfw
+	};
+
+
+  /**
+   * Standard-CRS, welches verwendet wird, wenn beim Import kein CRS ermittelt
+   * werden kann (Default: {@link DefaultGeographicCRS#WGS84}).<br>
+   * <b>Achtung:</b><br>
+   * Anwendungen koennen diese Variable gefahrlos ueberschreiben, um ein fuer
+   * die Anwendung adaequates Standard-CRS zu verwenden.
+   */
+  public static CoordinateReferenceSystem DEFAULT_CRS = DefaultGeographicCRS.WGS84;
+
+  /**
+   * Diese Methode extrahiert saemtliche Features aus einem ShapeFile-Projekt
+   * (<code><i>name</i>.shp <i>name</i>.prj <i>name</i>.dbf ...</code>)
+   * und speichert diese in einer <code>org.geotools.feature.FeatureCollection</code>.<br>
+   *
+   * @param url {@link URL} to Shape-File
+   * @param prjUrl {@link URL} zu .prj Datei des Shape-File
+   *
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   *
+   * @return {@link FeatureCollection} that was read
+   *
+   * @throws IOException
+   */
+  public static FeatureCollection readFeaturesFromShapeURL(URL url,
+			URL prjUrl) throws IOException {
+		ShapefileDataStore store = new ShapefileDataStore(url);
+		try {
+			// Testen, ob Projektion ermittelt werden kann, um vorab das
+			// Standard-CRS
+			// zu setzen
+			LOGGER.debug("  parseWKT on " + prjUrl);
+			CRS.parseWKT(readProjectionString(prjUrl));
+		} catch (Exception err) {
+			LOGGER.warn(" CRS.parseWKT mit Ex\n: ", err);
+			LOGGER.warn(" No projection found for file. Default is used.");
+			LOGGER
+					.warn(" NOT calling forceSchemaCRS now... please provide a URL to an existing .prj file!");
+			// store.forceSchemaCRS(DEFAULT_CRS);
+		}
+		String[] typeNames = store.getTypeNames();
+		FeatureCollection fc = store.getFeatureSource(typeNames[0])
+				.getFeatures();
+
+		// Create a new DefaultFeatureCollection to allow modifying
+	    // operations on the collection ("fc" is a DataFeatureCollection, whose
+	    // add(.) and remove(.) methods do nothing. Furthermore a FIDFeatureReader
+		// is used which returns copies of the features, so modifying the attributes
+		// does not effect the features in the collection!)
+	    FeatureCollection fc1 = FeatureCollections.newCollection( fc.getID() );
+	    fc1.addAll( fc );
+	    fc = fc1;
+
+	    return fc;
+	}
+
+
+  /**
+	 * Diese Methode extrahiert saemtliche Features aus einem ShapeFile-Projekt (<code><i>name</i>.shp <i>name</i>.prj <i>name</i>.dbf ...</code>)
+	 * und speichert diese in einer
+	 * <code>org.geotools.feature.FeatureCollection</code>.<br>
+	 *
+	 * @param file
+	 *            Shape-File
+	 * @throws java.lang.Exception
+	 *             bei irgendeinem Fehler
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+	 *
+	 * TODO Wenn readFeaturesFromShapeURL getestet ist, dann sollte die hiet auf readFeaturesFromShapeURL(file.getURL) umgeleitet werden (SK)
+	 */
+  public static FeatureCollection readFeaturesFromShapeFile(File file) throws Exception {
+    ShapefileDataStore store   = new ShapefileDataStore(file.toURI().toURL());
+    File               prjFile = IOUtil.changeFileExt(file, "prj");
+    boolean            delPrjFile = false;
+    try {
+      // Testen, ob Projektion ermittelt werden kann, um vorab das Standard-CRS
+      // zu setzen
+      String prjString = readProjectionString(prjFile);
+      if ( prjString == null || prjString.trim().equals("") )
+        throw new FileNotFoundException("No proper prj-File exists: "+file.getName());
+      CRS.parseWKT(prjString);
+    } catch (FileNotFoundException err) {
+      store.forceSchemaCRS(DEFAULT_CRS);
+      LOGGER.warn("No projection found for file "+file.getName()+". Default is used.");
+      delPrjFile = true; // von DataStore erzeugte Datei wieder loeschen
+    }
+    String[] typeNames = store.getTypeNames();
+    FeatureCollection fc = store.getFeatureSource(typeNames[0]).getFeatures();
+    if ( delPrjFile )
+      prjFile.delete();
+
+    // Create a new DefaultFeatureCollection to allow modifying
+    // operations on the collection ("fc" is a DataFeatureCollection, whose
+    // add(.) and remove(.) methods do nothing. Furthermore a FIDFeatureReader
+    // is used which returns copies of the features, so modifying the attributes
+    // does not effect the features in the collection!)
+    FeatureCollection fc1 = FeatureCollections.newCollection( fc.getID() );
+    fc1.addAll( fc );
+    fc = fc1;
+
+    return fc;
+  }
+
+
+  /**
+   * TODO DOKU
+   * @param shpURL
+   * @param prjURL
+   * @return
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ * @throws IOException
+   */
+  public static DataStore readDataStoreFromShape(URL shpURL, URL prjURL) throws IOException {
+	  Map map = new HashMap();
+	  map.put( "url", shpURL );
+	  map.put( "create spatial index", true );
+
+	  //DataStore dataStore = DataStoreFinder.getDataStore( map );
+	  DataStore dataStore = new IndexedShapefileDataStore(shpURL, null, false, true, IndexedShapefileDataStore.TREE_QIX);
+	  // DataStore dataStore = new ShapefileDataStore(shpURL);
+	  System.out.println("DataStore = "+dataStore.getClass().toString());
+	  IndexedShapefileDataStore dataStoreIndex = (IndexedShapefileDataStore)dataStore;
+	  System.out.println("indexed = "+dataStoreIndex.isIndexed());
+	  System.out.println("memory = "+dataStoreIndex.isMemoryMapped());
+  	return dataStore;
+  }
+
+
+  /**
+   * Diese Methode extrahiert saemtliche Geometrien aus einem ShapeFile-Projekt
+   * (<code><i>name</i>.shp <i>name</i>.prj <i>name</i>.dbf ...</code>)
+   * und speichert diese in einer Liste (Vector) von {@link com.vividsolutions.jts.geom.Geometry}-Objekten.<br>
+   * Baut auf folgenden Geotools-Klassen auf:
+   * <code>
+   * <ul>
+   * <li>{@link ShapefileReader org.geotools.data.shapefile.shp.ShapefileReader}</li>
+   * <li>{@link Geometry        com.vividsolutions.jts.geom.Geometry}</li>
+   * </ul>
+   * </code>
+   * @param file Shape-File
+   * @throws java.lang.Exception bei irgendeinem Fehler
+   */
+  public static Vector<Geometry> readGeometriesFromShapeFile(File file) throws Exception {
+    FileChannel in = new FileInputStream(file).getChannel();
+    ShapefileReader r = new ShapefileReader( in, new Lock() );
+    Vector<Geometry> geomList = new Vector<Geometry>();
+    for(int i=1; r.hasNext(); i++) {
+        //        org.geotools.renderer.geom.Geometry shape = (org.geotools.renderer.geom.Geometry) r.nextRecord().shape();
+        //        org.geotools.geometry.Geometry shape = (org.geotools.geometry.Geometry) r.nextRecord().shape();
+        Object obj = r.nextRecord().shape();
+        Geometry shape = (Geometry)obj;
+        geomList.add(shape);
+      }
+      r.close();
+      return geomList;
+    }
+
+    /**
+     * Diese Methode importiert ein Raster aus einer Datei im ArcInfoASCII-Grid-Format.
+     * Das CRS wird aus einem prj-File (EPSG-Code "EPSG:..." oder WKT-Definition)
+     * gelesen. Ist dies nicht erfolgreich, wird {@link #DEFAULT_CRS}
+     * als CRS verwendet.
+     * @param file ASCII-File
+     * @throws java.lang.Exception bei irgendeinem Fehler
+     * @return {@link GridCoverage2D}
+     */
+    public static GridCoverage2D readGridFromArcInfoASCII(File file) throws Exception {
+      return readGridFromArcInfoASCII(file,null);
+    }
+
+    /**
+     * Diese Methode importiert ein Raster aus einer Datei im ArcInfoASCII-Grid-Format.
+     * Wenn kein CRS angegeben wird, wird versucht das CRS aus einem prj-File
+     * (EPSG-Code "EPSG:..." oder WKT-Definition) zu lesen. Ist dies nicht
+     * erfolgreich, wird {@link #DEFAULT_CRS} als CRS verwendet.
+     * @param file ASCII-File
+     * @param crs CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+     * @throws java.lang.Exception bei irgendeinem Fehler
+     * TODO
+     */
+    public static GridCoverage2D readGridFromArcInfoASCII(File file, CoordinateReferenceSystem crs) throws Exception {
+    	return readGridFromArcInfoASCII(file.toURI().toURL(), crs);
+
+//      ArcGridRaster  reader = new ArcGridRaster( new BufferedReader( new InputStreamReader( new FileInputStream(file) ) ), false );
+//      WritableRaster raster = reader.readRaster();
+//      if (crs == null)
+//        crs = determineProjection(file);
+//
+////      System.out.println("Position  "+reader.getXlCorner()+" / "+reader.getYlCorner());
+////      System.out.println("Size      "+reader.getNCols()+" / "+reader.getNRows());
+////      System.out.println("Min/Max   "+reader.getMinValue()+" / "+reader.getMaxValue());
+////      System.out.println("NoData    "+reader.getNoData());
+////      System.out.println("CellSize  "+reader.getCellSize());
+//
+//      float x = (float) reader.getXlCorner(); // Suedwestliche Ecke!
+//      float y = (float) reader.getYlCorner(); // Suedwestliche Ecke!
+//      float w = (float) (reader.getNCols() * reader.getCellSize()); // reale Breite = RasterSpalten * Aufloesung
+//      float h = (float) (reader.getNRows() * reader.getCellSize()); // reale Hoehe = RasterZeilen * Aufloesung
+//      Envelope2D envelope = new Envelope2D(crs, new Rectangle2D.Float(x, y, w, h));
+//
+//      // WICHTIG: Name des Rasters sollte Leer-String sein, da ansonsten das
+//      //          Coloring des Rasters nicht klappt.
+//      //          --> Name der Categories muss (warum auch immer) mit dem Namen
+//      //              des Rasters uebereinstimmen!!!
+//      return new GridCoverageFactory().create("", raster, envelope);
+////
+////=== OHNE ArcGridRaster ===
+//////      ArcGridReader reader = new ArcGridReader( new BufferedReader( new InputStreamReader( new FileInputStream(file) ) ));
+////      ArcGridReader reader = new ArcGridReader( file );
+////      return (GridCoverage2D)reader.read(null);
+    }
+
+
+    /**
+     * Diese Methode importiert ein Raster aus einer Datei im ArcInfoASCII-Grid-Format.
+     * Wenn kein CRS angegeben wird, wird versucht das CRS aus einem prj-File
+     * (EPSG-Code "EPSG:..." oder WKT-Definition) zu lesen. Ist dies nicht
+     * erfolgreich, wird {@link #DEFAULT_CRS} als CRS verwendet.
+     * @param url Link to Arc/Info GRID ASCII file
+     * @param crs CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+     * @throws java.lang.Exception bei irgendeinem Fehler
+     */
+    public static GridCoverage2D readGridFromArcInfoASCII(URL url, CoordinateReferenceSystem crs) throws Exception {
+      ArcGridRaster  reader = new ArcGridRaster( new BufferedReader( new InputStreamReader(url.openStream()) ), false );
+      WritableRaster raster = reader.readRaster();
+      if (crs == null)
+        crs = determineProjection( IOUtil.changeUrlExt(url,"prj"));
+
+//      System.out.println("Position  "+reader.getXlCorner()+" / "+reader.getYlCorner());
+//      System.out.println("Size      "+reader.getNCols()+" / "+reader.getNRows());
+//      System.out.println("Min/Max   "+reader.getMinValue()+" / "+reader.getMaxValue());
+//      System.out.println("NoData    "+reader.getNoData());
+//      System.out.println("CellSize  "+reader.getCellSize());
+
+      float x = (float) reader.getXlCorner(); // Suedwestliche Ecke!
+      float y = (float) reader.getYlCorner(); // Suedwestliche Ecke!
+      float w = (float) (reader.getNCols() * reader.getCellSize()); // reale Breite = RasterSpalten * Aufloesung
+      float h = (float) (reader.getNRows() * reader.getCellSize()); // reale Hoehe = RasterZeilen * Aufloesung
+      Envelope2D envelope = new Envelope2D(crs, new Rectangle2D.Float(x, y, w, h));
+
+      // WICHTIG: Name des Rasters sollte Leer-String sein, da ansonsten das
+      //          Coloring des Rasters nicht klappt.
+      //          --> Name der Categories muss (warum auch immer) mit dem Namen
+      //              des Rasters uebereinstimmen!!!
+      return new GridCoverageFactory().create("", raster, envelope);
+//
+//=== OHNE ArcGridRaster ===
+////      ArcGridReader reader = new ArcGridReader( new BufferedReader( new InputStreamReader( new FileInputStream(file) ) ));
+//      ArcGridReader reader = new ArcGridReader( file );
+//      return (GridCoverage2D)reader.read(null);
+    }
+
+
+
+
+
+    /**
+     * Diese Methode erzeugt einen {@link AbstractGridCoverage2DReader} aus einer
+     * Datei im GeoTIFF-Format.
+     * Zunaechst wird versucht, die Geo-Informationen (Referenz+CRS) aus den
+     * TIFF-Metadaten zu ermitteln. Ist dies nicht erfolgreich, werden
+     * ein gleichnamiges World-File (.tfw) und Projection-File (.prj)
+     * herangezogen (siehe {@link WorldImageReader}).
+     * @param file GeoTIFF-File
+     * @param crs erzwungenes CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+     * @throws java.lang.Exception bei irgendeinem Fehler
+     * @deprecated {@link WorldImageReader} klappt noch nicht so 100%ig (Colorisierung schlaegt fehl!)
+     */
+    public static AbstractGridCoverage2DReader createGridReaderFromGeoTiff(File file, CoordinateReferenceSystem crs) throws Exception {
+      // Versuchen Geo-Information aus Tiff zu lesen
+      try {
+        Hints hints = new Hints(null);
+        // Wenn CRS angegeben, dieses durch Hint erzwingen
+        if ( crs != null )
+          hints.put( Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, crs );
+        // Reader (mit Metadaten) erzeugen
+        GeoTiffReader reader = new GeoTiffReader(file,hints);
+        // Wenn kein Referenzsystem vorhanden, versuchen ein prj-File zu verwenden
+        if ( reader.getOriginalEnvelope().getCoordinateReferenceSystem() == null ) {
+          LOGGER.warn("No projection information found in '"+file.getName()+"'. Using prj-file...");
+          hints.put( Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, determineProjection(file) );
+          reader = new GeoTiffReader(file,hints);
+        }
+        return reader;
+      } catch ( UnsupportedOperationException err )  {
+        // erwartet, wenn keine Geo-Information im Tiff vorhanden ist
+      } catch ( NullPointerException err ) {
+        // erwartet, wenn keine Geo-Information im Tiff vorhanden ist
+      }
+
+      // keine Geo-Informationen in Tiff
+      // --> Raster ueber ImageIO einlesen und WorldFile/ProjectionFile verwenden
+      LOGGER.warn("No geo information found in '" + file.getName() +  "'. Using world- and prj-file...");
+      // @todo check the work of WorldImageReader with colorization!
+      throw new UnsupportedOperationException("WorldImageReader does not work well, yet... AbstractGridCoverage2DReader can not be created from this Tiff!");
+//      Hints hints = new Hints(null);
+//      // Wenn CRS angegeben, dieses durch Hint erzwingen
+//      if ( crs != null )
+//        hints.put( Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, crs );
+//      WorldImageReader reader = new WorldImageReader(file);
+//      return reader;
+    }
+
+    /**
+     * Diese Methode erzeugt einen {@link AbstractGridCoverage2DReader} aus einer
+     * Datei im GeoTIFF-Format.
+     * @param file GeoTIFF-File
+     * @throws java.lang.Exception bei irgendeinem Fehler
+     * @see #createGridReaderFromGeoTiff(File, CoordinateReferenceSystem)
+     * @deprecated {@link WorldImageReader} klappt noch nicht so 100%ig (Colorisierung schlaegt fehl!)
+     */
+    public static AbstractGridCoverage2DReader createGridReaderFromGeoTiff(File file) throws Exception {
+      return createGridReaderFromGeoTiff(file,null);
+    }
+
+    /**
+    * Diese Methode importiert ein Raster aus einer Datei im GeoTIFF-Format.
+    * @param file GeoTIFF-File
+    * @param crs erzwungenes CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+    * @throws java.lang.Exception bei irgendeinem Fehler
+    * @see #createGridReaderFromGeoTiff(File, CoordinateReferenceSystem)
+    */
+   public static GridCoverage2D readGridFromGeoTiff(File file, CoordinateReferenceSystem crs) throws Exception {
+//     return (GridCoverage2D)createGridReaderFromGeoTiff(file, crs).read(null);
+     GridCoverage2D gc = null;
+     // Versuchen Geo-Information aus Tiff zu lesen
+     try {
+       Hints hints = new Hints(null);
+
+       // Wenn CRS angegeben, dieses durch Hint erzwingen
+       if ( crs != null )
+         hints.put( Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, crs );
+       // Reader (mit Metadaten) erzeugen
+       GeoTiffReader reader = new GeoTiffReader(file,hints);
+       // Wenn kein Referenzsystem vorhanden, versuchen ein prj-File zu verwenden
+       if ( reader.getOriginalEnvelope().getCoordinateReferenceSystem() == null ) {
+         LOGGER.warn("No projection information found in '"+file.getName()+"'. Using prj-file...");
+         hints.put( Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, determineProjection(file) );
+         reader = new GeoTiffReader(file,hints);
+       }
+       gc = (GridCoverage2D)reader.read(null);
+       return gc;
+     } catch ( UnsupportedOperationException err )  {
+       // erwartet, wenn keine Geo-Information im Tiff vorhanden ist
+     } catch ( NullPointerException err ) {
+       // erwartet, wenn keine Geo-Information im Tiff vorhanden ist
+     }
+
+     // keine Geo-Informationen in Tiff
+     // --> Raster ueber ImageIO einlesen und WorldFile/ProjectionFile verwenden
+     LOGGER.warn("No geo information found in '" + file.getName() +
+                 "'. Using world- and prj-file...");
+     BufferedImage im = ImageIO.read(file);
+     if (im == null)
+       throw new IIOException("No image reader found for this image type!");
+     // World-File einlesen
+     File tfw = IOUtil.changeFileExt(file, "tfw");
+     double[] tfwInfo = readWorldFile(tfw);
+     float w = (float) (im.getWidth() * tfwInfo[0]); // reale Breite = RasterSpalten * hor. Aufloesung
+     float h = (float) (im.getHeight() * ( -tfwInfo[3])); // reale Hoehe = RasterZeilen * vert. Aufloesung
+     float x = (float) tfwInfo[4]; // Suedwestliche Ecke!
+     float y = (float) tfwInfo[5] - h; // Suedwestliche Ecke (im tfw-File steht die Nordwestliche!)
+     // ggf. Projektion einlesen
+     if (crs == null)
+       crs = determineProjection(file);
+     Envelope2D envelope = new Envelope2D(crs, new Rectangle2D.Float(x, y, w, h));
+     // WICHTIG: Name des Rasters sollte Leer-String sein, da ansonsten das
+     //          Coloring des Rasters nicht klappt.
+     //          --> Name der Categories muss (warum auch immer) mit dem Namen
+     //              des Rasters uebereinstimmen!!!
+     gc = new GridCoverageFactory().create("", im.copyData(null), envelope);
+     return gc;
+   }
+
+   /**
+    * Diese Methode importiert ein Raster aus einer Datei im GeoTIFF-Format.
+    * @param file GeoTIFF-File
+    * @throws java.lang.Exception bei irgendeinem Fehler
+    * @see #readGridFromGeoTiff(File, CoordinateReferenceSystem)
+    */
+   public static GridCoverage2D readGridFromGeoTiff(File file) throws Exception {
+     return readGridFromGeoTiff(file,null);
+   }
+
+   /**
+    * Diese Methode importiert ein Raster aus einer Datei im ArcInfoASCII-Grid-Format.
+    * Das CRS wird aus einem prj-File (EPSG-Code "EPSG:..." oder WKT-Definition)
+    * gelesen. Ist dies nicht erfolgreich, wird {@link #DEFAULT_CRS}
+    * als CRS verwendet.
+    * @param file ASCII-File
+    * @throws java.lang.Exception bei irgendeinem Fehler
+    */
+   public static WritableGridRaster readGridRasterFromArcInfoASCII(File file) throws Exception {
+     return readGridRasterFromArcInfoASCII(file,null);
+   }
+
+   /**
+    * Diese Methode importiert ein Raster aus einer Datei im ArcInfoASCII-Grid-Format.
+    * Wenn kein CRS angegeben wird, wird versucht das CRS aus einem prj-File
+    * (EPSG-Code "EPSG:..." oder WKT-Definition) zu lesen. Ist dies nicht
+    * erfolgreich, wird {@link #DEFAULT_CRS} als CRS verwendet.
+    * @param file ASCII-File
+    * @param crs CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+    * @throws java.lang.Exception bei irgendeinem Fehler
+    */
+   public static WritableGridRaster readGridRasterFromArcInfoASCII(File file, CoordinateReferenceSystem crs) throws Exception {
+     ArcGridRaster  reader = new ArcGridRaster(new BufferedReader(new InputStreamReader(new FileInputStream(file))),false);
+     WritableRaster raster = reader.readRaster();
+     float x = (float)reader.getXlCorner(); // Suedwestliche Ecke!
+     float y = (float)reader.getYlCorner(); // Suedwestliche Ecke!
+     float w = (float)(reader.getNCols() * reader.getCellSize()); // reale Breite = RasterSpalten * Aufloesung
+     float h = (float)(reader.getNRows() * reader.getCellSize()); // reale Hoehe = RasterZeilen * Aufloesung
+     // Projektion einlesen
+     if (crs == null)
+       crs = determineProjection(file);
+     return new WritableGridRaster(raster, new Rectangle2D.Float(x,y,w,h), crs );
+
+//=== OHNE ArcGridRaster ===
+//     GridCoverage2D grid   = readGridFromArcInfoASCII(null,input);
+//     WritableRaster raster = grid.getRenderedImage().copyData(null);
+//     return( new WritableGridRaster(raster,grid.getEnvelope2D()) );
+   }
+
+   /**
+    * Diese Methode importiert ein Raster aus einer Datei im GeoTIFF-Format.
+    * Zunaechst wird versucht, die Geo-Informationen (Referenz+CRS) aus den
+    * TIFF-Metadaten zu ermitteln. Ist dies nicht erfolgreich, werden
+    * ein gleichnamiges World-File (.tfw) und Projection-File (.prj)
+    * herangezogen. Als Projektion (.prj) ist sowohl ein EPSG-Code "EPSG:...",
+    * als auch eine WKT-Definition erlaubt. Kann kein CRS ermitteln werden,
+    * wird {@link #DEFAULT_CRS} als CRS verwendet.
+    * @param file GeoTIFF-File
+    * @throws java.lang.Exception bei irgendeinem Fehler
+     */
+   public static WritableGridRaster readGridRasterFromGeoTiff(File file) throws Exception {
+     return readGridRasterFromGeoTiff(file,null);
+   }
+
+   /**
+    * Diese Methode importiert ein Raster aus einer Datei im GeoTIFF-Format.
+    * Zunaechst wird versucht, die Geo-Informationen (Referenz+CRS) aus den
+    * TIFF-Metadaten zu ermitteln. Ist dies nicht erfolgreich, werden
+    * ein gleichnamiges World-File (.tfw) und Projection-File (.prj)
+    * herangezogen. Als Projektion (.prj) ist sowohl ein EPSG-Code "EPSG:...",
+    * als auch eine WKT-Definition erlaubt. Kann kein CRS ermitteln werden,
+    * wird {@link #DEFAULT_CRS} als CRS verwendet.
+    * @param file GeoTIFF-File
+    * @param crs erzwungenes CoordinateReferenceSystem fuer das Raster (kann {@code null} sein)
+    * @throws java.lang.Exception bei irgendeinem Fehler
+    */
+  public static WritableGridRaster readGridRasterFromGeoTiff(File file, CoordinateReferenceSystem crs) throws Exception {
+//    // GeoTiff-File einlesen
+//    BufferedImage im = ImageIO.read( file );
+//    if ( im == null )
+//      throw new IIOException("No image reader found for this image type!");
+//    // World-File einlesen
+//    File tfw = IOUtil.changeFileExt(file,"tfw");
+//    double[] tfwInfo = readWorldFile(tfw);
+//    // ggf. Projektion einlesen
+//    if (crs == null)
+//      crs = determineProjection(file);
+//
+//    float w = (float) (im.getWidth() * tfwInfo[0]); // reale Breite = RasterSpalten * hor. Aufloesung
+//    float h = (float) (im.getHeight() * (-tfwInfo[3])); // reale Hoehe = RasterZeilen * vert. Aufloesung
+//    float x = (float) tfwInfo[4]; // Suedwestliche Ecke!
+//    float y = (float) tfwInfo[5] - h ; // Suedwestliche Ecke (im tfw-File steht die Nordwestliche!)
+//
+//    Rectangle2D        env    = new Rectangle2D.Float(x,y,w,h);
+//    WritableGridRaster raster = new WritableGridRaster(
+//      im.getData().getDataBuffer().getDataType(),
+//      0,
+//      0,
+//      im.getWidth(),
+//      im.getHeight(),
+//      env,
+//      crs
+//    );
+//
+
+    GridCoverage2D gc  = readGridFromGeoTiff(file,crs);
+    Rectangle2D    env = gc.getEnvelope2D();
+    RenderedImage  im  = gc.getRenderedImage();
+    WritableGridRaster raster = new WritableGridRaster(
+      im.getData().getDataBuffer().getDataType(),
+      0,
+      0,
+      im.getWidth(),
+      im.getHeight(),
+      env,
+      crs
+    );
+
+    im.copyData(raster);
+
+    return raster;
+  }
+
+  /**
+   * Liest ein World-File (.tfw) ein und liefert die darin zeilenweise
+   * gespeicherten Werte zurueck. Leer- und Kommentarzeilen werden dabei
+   * ignoriert. Kann ein Zeilen-Wert nicht in einen <code>double</code>
+   * umgewandelt werden, wird diese Zeile ignoriert, als 1.0 interpretiert
+   * und eine Meldung in die Standard-Fehler-Ausgabe geschrieben.<br>
+   * Der zurueckgegebene Array hat mindestens die Groesse 6:<br>
+   * <ol>
+   *    <li>Zellengroesse in X-Richtung (in Meter)</li>
+   *   <li>Koeffizient fuer Rotation in Y-Richtung</li>
+   *   <li>Koeffizient fuer Rotation in X-Richtung</li>
+   *   <li>negative Zellengroesse in Y-Richtung (in Meter)</li>
+   *   <li>Horizontale Geo-Referenz (Longitude) der nord-westlichen Ecke</li>
+   *   <li>Vertikale Geo-Referenz (Latitude) der nord-westlichen Ecke</li>
+   * </ol>
+   * @todo WorldFile-Spec vervollstaendigen
+   * @param file World-File
+   * @exception IOException falls die Datei nicht existiert oder ein unerwarteter
+   *            Fehler beim Einlesen auftritt
+   * @see IOUtil#isCommentLine(String)
+   * @see System#err
+   */
+
+   public static double[] readWorldFile(File file) throws IOException {
+     return readWorldFile(  new FileInputStream(file) ) ;
+   }
+
+
+   /**
+    * Liest ein World-File (.tfw) ein und liefert die darin zeilenweise
+    * gespeicherten Werte zurueck. Leer- und Kommentarzeilen werden dabei
+    * ignoriert. Kann ein Zeilen-Wert nicht in einen <code>double</code>
+    * umgewandelt werden, wird diese Zeile ignoriert, als 1.0 interpretiert
+    * und eine Meldung in die Standard-Fehler-Ausgabe geschrieben.<br>
+    * Der zurueckgegebene Array hat mindestens die Groesse 6:<br>
+    * <ol>
+    *    <li>Zellengroesse in X-Richtung (in Meter)</li>
+    *   <li>Koeffizient fuer Rotation in Y-Richtung</li>
+    *   <li>Koeffizient fuer Rotation in X-Richtung</li>
+    *   <li>negative Zellengroesse in Y-Richtung (in Meter)</li>
+    *   <li>Horizontale Geo-Referenz (Longitude) der nord-westlichen Ecke</li>
+    *   <li>Vertikale Geo-Referenz (Latitude) der nord-westlichen Ecke</li>
+    * </ol>
+    * @todo WorldFile-Spec vervollstaendigen
+    *
+    * @param inputStream Stream of World-File
+    * @exception IOException falls die Datei nicht existiert oder ein unerwarteter
+    *            Fehler beim Einlesen auftritt
+    * @see IOUtil#isCommentLine(String)
+    * @see System#err
+    *
+    * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+    * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+    */
+   public static double[] readWorldFile(InputStream inputStream) throws IOException {
+	   Vector<Double> tfwValues = new Vector<Double>();
+	   BufferedReader in = new BufferedReader( new InputStreamReader( inputStream ) );
+	   String line = null;
+	   for(int lineNo=1; in.ready() && (line=in.readLine().trim()) != null; lineNo++ ) {
+		   if ( IOUtil.isCommentLine(line) || line.equals("") )
+			   continue;
+		   try {
+			   StringTokenizer st = new StringTokenizer(line);
+			   tfwValues.add( Double.parseDouble( st.nextToken(" \n;,#|/") ) );
+		   } catch (Exception err) {
+			   System.err.println("WorldFile-Error in line "+lineNo+": "+err);
+
+//		(SK)   System.err.println("WorldFile-Error in '"+file.getAbsolutePath()+"' (line "+lineNo+"): "+err);
+			   System.err.println("   >> expected value is set to 1.0");
+			   tfwValues.add( 1.0 );
+		   }
+	   }
+
+	   while ( tfwValues.size() < 6 ) {
+		   System.err.println("WorldFile-Error: value "+tfwValues.size()+" missing!");
+//		(SK)   System.err.println("WorldFile-Error in '"+file.getAbsolutePath()+"': value "+tfwValues.size()+" missing!");
+		   System.err.println("   >> expected value is set to 1.0");
+		   tfwValues.add( 1.0 );
+	   }
+
+	   // Vector in Array umwandeln
+	   double[] ret = new double[tfwValues.size()];
+	   for (int i=0; i<ret.length; i++)
+		   ret[i] = tfwValues.elementAt(i);
+	   return ret;
+   }
+
+   /**
+    * Liest das CRS aus einer Datei. Zunaechst wird versucht das CRS aus der
+    * uebergebene Datei zu lesen. Ist dies nicht erfolgreich wird das zur
+    * Datei korrespondierende prj-File herangezogen.
+    * @param file Datei
+    * @return {@code null}, wenn kein CRS gelesen werden konnte
+    */
+   public static CoordinateReferenceSystem determineProjection(File file) throws IOException {
+
+	//****************************************************************************
+	// schonmal teilweise migriert... das geht direkt an die URL methode weiter...
+	//****************************************************************************
+
+//     CoordinateReferenceSystem crs = null;
+//     // Versuchen CRS aus angegebener Datei zu lesen
+//     crs = readProjectionFile(file);
+//     // Wenn nicht erfolgreich versuchen die zur Datei korrespondierende
+//     // prj-Datei einzulesen
+//     if ( crs == null )
+//       crs = readProjectionFile( IOUtil.changeFileExt(file,"prj") );
+//     // Wenn nicht erfolgreich, Default verwenden
+//     if ( crs == null ) {
+//       LOGGER.warn("No projection found for file '"+file.getName()+"'. Default CRS used.");
+//       crs = DEFAULT_CRS;
+//     }
+     return determineProjection(file.toURI().toURL());
+   }
+
+
+	/**
+	 * Liest das CRS aus einer URL. Wenn keine CRS gelesen werden kann, dann wird die Endung der URL gegen .prj gewechselt und nochmal versucht bevor das {@link #DEFAULT_CRS} benutzt wird.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static CoordinateReferenceSystem determineProjection(URL prjUrl) {
+		CoordinateReferenceSystem crs = null;
+		// Versuchen CRS aus angegebener Datei zu lesen
+
+		LOGGER.debug("determineProjection: Try to read a projection from URL "
+				+ prjUrl);
+		try {
+			crs = GeoImportUtil.readProjectionFile(prjUrl);
+		} catch (IOException e) {
+		}
+
+		if (crs == null){
+			try {
+				crs = GeoImportUtil.readProjectionFile( IOUtil.changeUrlExt(prjUrl,"prj"));
+			} catch (IllegalArgumentException e) {
+			} catch (IOException e) {
+			}
+		}
+
+		// Wenn nicht erfolgreich, Default verwenden
+		if (crs == null) {
+			LOGGER.warn("No projection found in URL. Default CRS used.");
+			crs = DEFAULT_CRS;
+		}
+		return crs;
+	}
+
+
+   /**
+    * Liest ein CRS aus einer URL. Als Dateiinhalt wird entweder ein
+    * EPSG-Code oder eine WKT-Definition des CRS akzeptiert.
+    * @param prjURL prj-Datei in der die Projektion hinterlegt ist
+    * @return {@code null}, wenn kein CRS gelesen werden konnte
+    * @see GTUtil#createCRS(String)
+    *
+    * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+    * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+    * @throws IOException falls die URL nicht wie erwartet gelesen werden konnte.
+    */
+   public static CoordinateReferenceSystem readProjectionFile(URL prjURL) throws IOException {
+	   String crsDefinition = readProjectionString(prjURL);
+	   CoordinateReferenceSystem crs = GTUtil.createCRS( crsDefinition );
+	   return crs;
+   }
+
+   /**
+    * Liest ein CRS aus einer Datei. Als Dateiinhalt wird entweder ein
+    * EPSG-Code oder eine WKT-Definition des CRS akzeptiert.
+    * @param prjFile prj-Datei in der die Projektion hinterlegt ist
+    * @return {@code null}, wenn kein CRS gelesen werden konnte
+    * @see GTUtil#createCRS(String)
+    *
+    * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+    */
+   public static CoordinateReferenceSystem readProjectionFile(File prjFile) throws IOException {
+     String crsDefinition = readProjectionString(prjFile);
+     CoordinateReferenceSystem crs = GTUtil.createCRS( crsDefinition );
+//     if ( crs == null )
+//       LOGGER.warn("CRS can not be read from file '"+(prjFile == null ? "null" : prjFile.getName())+"'");
+     return crs;
+   }
+
+   /**
+    * Liest die ersten Zeilen der angegebenen prj-Datei und fuegt sie zu einem
+    * String zusammen. Wenn die Datei zu gross ist, wird ein Leerstring zurueckgegeben,
+    * da es sich dann nicht um eine Projektionsdefinition handelt.
+    * @param prjFile prj-Datei
+    * @return Leerstring, falls die Datei nicht existiert oder groesser als 10KB ist
+    *
+    * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+    */
+   private static String readProjectionString(File prjFile) throws IOException {
+     // Wenn Datei groesser als 10KB, dann ist es kein prj-File
+     if ( !prjFile.exists() || !prjFile.isFile() ||
+          prjFile.length() == 0 || prjFile.length() > 10240 )
+       return "";
+
+     // Alle Zeilen der Datei aneinander haengen
+     BufferedReader input = new BufferedReader( new InputStreamReader( new FileInputStream(prjFile) ) );
+     StringBuffer buffer = new StringBuffer();
+     String line = null;
+     while( input.ready() && (line = input.readLine()) != null )
+       if ( !IOUtil.isCommentLine(line) )
+         buffer.append(" ").append(line);
+     input.close();
+     return buffer.toString().trim();
+
+   }
+
+   /**
+    * Liest alle Zeilen aus der prj-Datei URL und fuegt diese zu einem
+    * String zusammen
+    *
+    * Kommentarzeilen werden ignoriert.
+    *
+    * Wenn mehr als 3000 Zeichen (ohne Kommentare) gelesen werden, wird davon
+    * ausgegangen, dass es sich nicht um eine .PRJ Datei handelt, und "" wird
+    * zurückgegeben.
+    *
+    * @param url {@link URL} auf prj-Datei
+    *
+    * @return Leerstring, wenn url == null
+    *
+    * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+    */
+   public static String readProjectionString(URL url) throws IOException {
+     if (url == null) {
+       LOGGER.debug("readProjectionString returned empty String for url==null");
+       return "";
+     }
+
+     BufferedReader input = new BufferedReader(new InputStreamReader(url.openStream()));
+     try {
+         // Alle Zeilen der Datei aneinander haengen
+         StringBuffer buffer = new StringBuffer();
+         String line = null;
+         while (input.ready() && (line = input.readLine()) != null) {
+           if (!IOUtil.isCommentLine(line))
+             buffer.append(" ").append(line);
+           if (buffer.length() > 3000) {
+//             LOGGER.warn("The URL " + url + " doesn't seem point to a .prj file, because size is > 3000 characters. Returning \"\"");
+             return "";
+           }
+         }
+         final String prjString = buffer.toString().trim();
+         // LOGGER.debug("This is the PRJ String from URL = \n"+prjString);
+         return prjString;
+       } finally {
+         input.close();
+       }
+     }
+
+   }

Added: trunk/src/schmitzm/geotools/io/package.html
===================================================================
--- trunk/src/schmitzm/geotools/io/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/io/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen für den Datenim- und export von GeoObjekten. Diese basieren auf
+	der <a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/geotools/map/event/FeatureModifiedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/FeatureModifiedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/FeatureModifiedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,49 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.feature.Feature;
+import org.geotools.map.MapLayer;
+
+import schmitzm.geotools.gui.JEditorPane;
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JEditorPane} ausloest,
+ * wenn der Anwender Features in der Karte veraendert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureModifiedEvent extends JEditorPaneEvent {
+  /** Feature das veraendert wurde. */
+  protected Feature feature = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Veraenderung vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param feature        veraendertes Features
+   * @param sourceObject   Objekt, das die Aenderung initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public FeatureModifiedEvent(JEditorPane sourceMap, MapLayer sourceLayer, Feature feature, Object sourceObject) {
+    super(sourceMap, sourceLayer, sourceObject);
+    this.feature = feature;
+  }
+
+  /**
+   * Liefert das veraenderte Feature.
+   */
+  public Feature getModifiedFeature() {
+    return feature;
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,58 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.MapLayer;
+import org.geotools.feature.FeatureCollection;
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn der Anwender Features in der Karte ausgewaehlt hat.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FeatureSelectedEvent extends ObjectSelectionEvent<FeatureCollection> {
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierte Features
+   * @param sourceObject   Objekt, das die Selektion initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public FeatureSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, FeatureCollection result, Object sourceObject) {
+    super(sourceMap, sourceLayer, envelope, result, sourceObject);
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierte Features
+   */
+  public FeatureSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, FeatureCollection result) {
+    this(sourceMap, sourceLayer, envelope, result,null);
+  }
+
+//  /**
+//   * Liefert die selektierten Features.
+//   */
+//  public FeatureCollection getSelectionResult() {
+//    return result;
+//  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,58 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.MapLayer;
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn der Anwender (irgendeinen) einen Bereich in der Karte ausgewaehlt hat,
+ * auch wenn dort keine Objekte selektiert wurden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GeneralSelectionEvent extends JMapPaneEvent {
+  /** Bereich der selektiert wurde. */
+  protected Envelope envelope    = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param source Karte in der die Selektion vorgenommen wurde
+   * @param envelope  Bereich der selektiert wurde
+   * @param sourceObject Objekt, das die Selektion initiiert hat (wenn {@code null},
+   *                     wird das MapPane als Ausloeser gesetzt)
+   */
+  public GeneralSelectionEvent(JMapPane source, Envelope envelope, Object sourceObject) {
+    super(source, sourceObject);
+    this.envelope = envelope;
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap Karte in der die Selektion vorgenommen wurde
+   * @param envelope  Bereich der selektiert wurde
+   */
+  public GeneralSelectionEvent(JMapPane sourceMap, Envelope envelope) {
+    this(sourceMap,envelope,null);
+  }
+
+
+  /**
+   * Liefert den Bereich ueber den selektiert wurde.
+   */
+  public Envelope getSelectionRange() {
+    return envelope;
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,59 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.MapLayer;
+import org.geotools.coverage.grid.GridCoverage2D;
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn der Anwender ein Raster in der Karte ausgewaehlt hat.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class GridCoverageSelectedEvent extends ObjectSelectionEvent<GridCoverage2D> {
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierters Teil-Raster
+   * @param sourceObject   Objekt, das die Selektion initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public GridCoverageSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, GridCoverage2D result, Object sourceObject) {
+    super(sourceMap,sourceLayer,envelope,result,sourceObject);
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierters Teil-Raster
+   */
+  public GridCoverageSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, GridCoverage2D result) {
+    this(sourceMap,sourceLayer,envelope,result,null);
+  }
+
+//  /**
+//   * Liefert das selektierte Teil-Raster.
+//   */
+//  public GridCoverage2D getSelectionResult() {
+//    return result;
+//  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/GridCoverageValueSelectedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/GridCoverageValueSelectedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/GridCoverageValueSelectedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,75 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import java.awt.geom.Point2D;
+
+import org.geotools.map.MapLayer;
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn der Anwender den Wert eines Rasters in der Karte ausgewaehlt hat.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ *
+ * @version 1.0
+ *
+ *
+ * Da Raster auch mehrere Baender haben kann, habe ich von ObjectSelectionEvent
+ * Double auf ObjectSelectionEvent double[] geaendert. Die laenge des double array
+ * entspricht der Anzahl der Baender.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ * @version 1.1
+ *
+ */
+public class GridCoverageValueSelectedEvent extends ObjectSelectionEvent<double[]> {
+  /** Punkt auf den geklickt wurde */
+  private Point2D point = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param point          Punkt der selektiert wurde
+   * @param result         selektierters Teil-Raster
+   * @param sourceObject   Objekt, das die Selektion initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public GridCoverageValueSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Point2D point, double[] result, Object sourceObject) {
+    super(sourceMap,sourceLayer,new Envelope(point.getX(),point.getX(),point.getY(),point.getY()),result, sourceObject);
+    this.point = point;
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param point          Punkt der selektiert wurde
+   * @param result         selektierters Teil-Raster
+   */
+  public GridCoverageValueSelectedEvent(JMapPane sourceMap, MapLayer sourceLayer, Point2D point, double[] result) {
+    this(sourceMap,sourceLayer,point,result,null);
+  }
+
+  /**
+   * Liefert den selektierten Punkt.
+   */
+  public Point2D getSelectionPoint() {
+    return point;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/JEditorPaneEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/JEditorPaneEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/JEditorPaneEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,66 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.feature.Feature;
+import org.geotools.map.MapLayer;
+
+import schmitzm.geotools.gui.JEditorPane;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.JEditorPane.EditorMode;
+
+/**
+ * Diese Klasse stellt ein allgemeines Ereignis dar, das ein
+ * {@link JEditorPane} ausloest.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class JEditorPaneEvent extends JMapPaneEvent {
+  /** Das bearbeitete Layer. */
+  protected MapLayer layer = null;
+  /** Bearbeitungs-Modus (zum Zeitpunkt des Events) */
+  protected EditorMode editorMode = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Veraenderung vorgenommen wurde
+   * @param sourceLayer    bearbeitetes Layer
+   * @param sourceObject   Objekt, das die Aenderung initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public JEditorPaneEvent(JEditorPane sourceMap, MapLayer sourceLayer, Object sourceObject) {
+    super(sourceMap, sourceObject);
+    this.layer = sourceLayer;
+    this.editorMode = sourceMap.getEditorMode();
+  }
+
+  /**
+   * Liefert das {@link JEditorPane}, das das Ereignis ausgeloest hat.
+   */
+  public JEditorPane getSource() {
+    return(JEditorPane)super.getSource();
+  }
+
+  /**
+   * Liefert das bearbeitete Layer.
+   */
+  public MapLayer getEditedLayer() {
+    return layer;
+  }
+
+  /**
+   * Liefert den Bearbeitungsmodus zum Zeitpunkt des Events.
+   */
+  public EditorMode getEditorMode() {
+    return editorMode;
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/JMapPaneEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/JMapPaneEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/JMapPaneEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,60 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das von einem {@link JMapPane}
+ * ausgeloest wurde (z.B. Aenderung der Aufloesung).
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class JMapPaneEvent {
+  /** Beinhaltet das {@link JMapPane}, das das Ereignis ausgeloest hat */
+  protected JMapPane source = null;
+  /** Beinhaltet ein Objekt, das das Ereignis initiiert hat */
+  protected Object sourceObject = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param source {@link JMapPane}, das das Ereignis ausgeloest hat
+   * @param sourceObject Objeckt, das das Ereignis initiiert hat (wenn {@code null},
+   *        wird das MapPane als Ausloeser gesetzt)
+   */
+  public JMapPaneEvent(JMapPane source, Object sourceObject) {
+    this.source       = source;
+    this.sourceObject = (sourceObject != null) ? sourceObject : source;
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param source {@link JMapPane}, das das Ereignis ausgeloest hat
+   */
+  public JMapPaneEvent(JMapPane source) {
+    this(source,null);
+  }
+
+  /**
+   * Liefert das {@link JMapPane}, das das Ereignis ausgeloest hat.
+   */
+  public JMapPane getSource() {
+    return source;
+  }
+
+  /**
+   * Liefert das Object, das das Ereignis initiiert hat.
+   */
+  public Object getSourceObject() {
+    return sourceObject;
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/JMapPaneListener.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/JMapPaneListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/JMapPaneListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,30 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+// fuer Doku
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt einen Listener fuer Ereignisse des {@link JMapPane}
+ * dar (z.B. Aenderung der Aufloesung).
+ * @see JMapPaneEvent
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface JMapPaneListener {
+  /**
+   * Verarbeitet ein JMapPane-Ereignis.
+   * @param e Ereignis
+   */
+  public void performMapPaneEvent(JMapPaneEvent e);
+}

Added: trunk/src/schmitzm/geotools/map/event/LayerEditCanceledEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/LayerEditCanceledEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/LayerEditCanceledEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,38 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.feature.Feature;
+import org.geotools.map.MapLayer;
+
+import schmitzm.geotools.gui.JEditorPane;
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JEditorPane} ausloest,
+ * wenn der Anwender die Bearbeitung eines Layers abgebrochen hat.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LayerEditCanceledEvent extends JEditorPaneEvent {
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Veraenderung vorgenommen wurde
+   * @param sourceLayer    Layer dessen Bearbeitung abgebrochen wurde ({@code null}, wenn
+   *                       ein komplett neues Layer abgebrochen wurde)
+   * @param sourceObject   Objekt, das die Aenderung initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public LayerEditCanceledEvent(JEditorPane sourceMap, MapLayer sourceLayer, Object sourceObject) {
+    super(sourceMap, sourceLayer, sourceObject);
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/LayerEditFinishedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/LayerEditFinishedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/LayerEditFinishedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,37 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.feature.Feature;
+import org.geotools.map.MapLayer;
+
+import schmitzm.geotools.gui.JEditorPane;
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JEditorPane} ausloest,
+ * wenn der Anwender die Bearbeitung eines Layers (erfolgreich) beendet hat.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LayerEditFinishedEvent extends JEditorPaneEvent {
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Veraenderung vorgenommen wird
+   * @param sourceLayer    Layer dessen Bearbeitung beendet wurde
+   * @param sourceObject   Objekt, das die Aenderung initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public LayerEditFinishedEvent(JEditorPane sourceMap, MapLayer sourceLayer, Object sourceObject) {
+    super(sourceMap, sourceLayer, sourceObject);
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/LayerEditStartedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/LayerEditStartedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/LayerEditStartedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,37 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.feature.Feature;
+import org.geotools.map.MapLayer;
+
+import schmitzm.geotools.gui.JEditorPane;
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JEditorPane} ausloest,
+ * wenn der Anwender die Bearbeitung eines Layers beginnt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LayerEditStartedEvent extends JEditorPaneEvent {
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Veraenderung vorgenommen wird
+   * @param sourceLayer    Layer dessen Bearbeitung gestartet wird
+   * @param sourceObject   Objekt, das die Aenderung initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public LayerEditStartedEvent(JEditorPane sourceMap, MapLayer sourceLayer, Object sourceObject) {
+    super(sourceMap, sourceLayer, sourceObject);
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/MapAreaChangedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/MapAreaChangedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/MapAreaChangedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,52 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn sich die Ausdehnung der dargestellte Karten-Ausschnitt aendert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MapAreaChangedEvent extends JMapPaneEvent {
+  private Envelope oldEnvelope = null;
+  private Envelope newEnvelope = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param oldEnvelope vormalige Ausdehnung
+   * @param newEnvelope neue Ausdehnung
+   */
+  public MapAreaChangedEvent(JMapPane source, Envelope oldEnvelope, Envelope newEnvelope) {
+    super(source);
+    this.oldEnvelope = oldEnvelope;
+    this.newEnvelope = newEnvelope;
+  }
+
+  /**
+   * Liefert die alte Karten-Ausdehnung.
+   */
+  public Envelope getOldMapArea() {
+    return oldEnvelope;
+  }
+
+  /**
+   * Liefert die neue Karten-Ausdehnung.
+   */
+  public Envelope getNewMapArea() {
+    return newEnvelope;
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/MapContextSynchronizer.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/MapContextSynchronizer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/MapContextSynchronizer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,69 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.geotools.map.event;
+
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.map.MapContext;
+import org.geotools.map.event.MapBoundsEvent;
+import org.geotools.map.event.MapBoundsListener;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import com.sun.xml.internal.messaging.saaj.soap.Envelope;
+
+/**
+ * This {@link MapBoundsListener} listens to {@link MapBoundsEvent MapBoundsEvents}
+ * and transfers the change of {@link CoordinateReferenceSystem} and "AreaOfInterest"
+ * to the connected {@link MapContext}, so this {@link MapContext} has always the
+ * same CRS and AreaOfInterest like the {@link MapContex} this Listener is added to.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a>
+ */
+public class MapContextSynchronizer implements MapBoundsListener {
+  /** {@link MapContext} the changes by a {@link MapBoundsEvent} are
+   *  transfered to. */
+  protected MapContext mapContext = null;
+
+  /**
+   * Creates a new synchronizer.
+   * @param mapContext the {@link MapContext} the changes by a
+   *                   {@link MapBoundsEvent} are transfered to
+   */
+  public MapContextSynchronizer(MapContext mapContext) {
+    this.mapContext = mapContext;
+  }
+
+  /**
+   * Invoked when the CRS or AreaOfInterest of a connected {@link MapContext}
+   * changes.
+   * @param e an event
+   */
+  public void mapBoundsChanged(MapBoundsEvent e) {
+    if ( mapContext == null )
+      return;
+
+    MapContext                eventContext = (MapContext)e.getSource();
+    ReferencedEnvelope        eventAOI     = eventContext.getAreaOfInterest();
+    CoordinateReferenceSystem eventCRS     = eventContext.getCoordinateReferenceSystem();
+    ReferencedEnvelope        currAOI      = mapContext.getAreaOfInterest();
+    CoordinateReferenceSystem currCRS      = mapContext.getCoordinateReferenceSystem();
+
+    try {
+      // check the CRS for changes
+      if ( currCRS == null || !currCRS.equals(eventCRS) )
+        mapContext.setCoordinateReferenceSystem( eventCRS );
+      // check the AreaOfInterst for changes
+      if ( currAOI == null || !currAOI.equals(eventAOI) )
+        mapContext.setAreaOfInterest( eventAOI );
+    } catch (Exception err) {
+      throw new RuntimeException(err);
+    }
+  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/MapLayerAdapter.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/MapLayerAdapter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/MapLayerAdapter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,42 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.event.MapLayerListener;
+import org.geotools.map.event.MapLayerEvent;
+
+/**
+ * Diese Klasse bildet eine Basis-Implementierung von
+ * {@link MapLayerListener org.geotools.map.event.MapLayerListener}, deren
+ * Methoden nichts machen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class MapLayerAdapter implements MapLayerListener {
+  /**
+   * Wird aufgerufen, wenn ein Layer verborgen wird. Macht nichts.
+   */
+  public void layerHidden(MapLayerEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn sich ein Layer veraendert. Macht nichts.
+   */
+  public void layerChanged(MapLayerEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Layer angezeigt wird. Macht nichts.
+   */
+  public void layerShown(MapLayerEvent e) {
+  }
+}

Added: trunk/src/schmitzm/geotools/map/event/MapLayerListAdapter.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/MapLayerListAdapter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/MapLayerListAdapter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,54 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.map.event.MapLayerListEvent;
+// nur fuer Doku
+import org.geotools.map.MapContext;
+
+/**
+ * Diese Klasse bildet eine Basis-Implementierung von
+ * {@link MapLayerListListener org.geotools.map.event.MapLayerListListener}, deren
+ * Methoden nichts machen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class MapLayerListAdapter implements MapLayerListListener {
+  /**
+   * Wird aufgerufen, wenn ein Layer dem {@link MapContext} hinzugefuegt wurde.
+   * Macht nichts.
+   */
+  public void layerAdded(MapLayerListEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn sich ein Layer veraendert. Macht nichts.
+   */
+  public void layerChanged(MapLayerListEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Layer in eine andere Ebene verschoben wurde.
+   * Macht nichts.
+   */
+  public void layerMoved(MapLayerListEvent e) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Layer aus dem {@link MapContext} entfernt wurde.
+   * Macht nichts.
+   */
+  public void layerRemoved(MapLayerListEvent e) {
+  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,69 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import org.geotools.map.MapLayer;
+import com.vividsolutions.jts.geom.Envelope;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt eine Oberklasse fuer die Ereignisse dar, die ein
+ * {@link JMapPane} ausloest, wenn der Anwender einen Bereich in der Karte ausgewaehlt hat
+ * und dabei Objekte selektiert wurde.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ObjectSelectionEvent<E> extends GeneralSelectionEvent {
+  private MapLayer          sourceLayer = null;
+  private E                 result      = null;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierte Objekte
+   * @param sourceObject   Objekt, das die Selektion initiiert hat (wenn {@code null},
+   *                       wird das MapPane als Ausloeser gesetzt)
+   */
+  public ObjectSelectionEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, E result, Object sourceObject) {
+    super(sourceMap,envelope,sourceObject);
+    this.sourceLayer = sourceLayer;
+    this.result      = result;
+  }
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param sourceMap      Karte in der die Selektion vorgenommen wurde
+   * @param sourceLayer    Layer aus dem die Features stammen
+   * @param envelope       Bereich der selektiert wurde
+   * @param result         selektierte Objekte
+   */
+  public ObjectSelectionEvent(JMapPane sourceMap, MapLayer sourceLayer, Envelope envelope, E result) {
+    this(sourceMap,sourceLayer,envelope, result, null);
+  }
+  /**
+   * Liefert das Layer aus dem die Features stammen.
+   */
+  public MapLayer getSourceLayer() {
+    return sourceLayer;
+  }
+
+  /**
+   * Liefert die selektierten Objekte.
+   */
+  public E getSelectionResult() {
+    return result;
+  }
+
+}

Added: trunk/src/schmitzm/geotools/map/event/ScaleChangedEvent.java
===================================================================
--- trunk/src/schmitzm/geotools/map/event/ScaleChangedEvent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/map/event/ScaleChangedEvent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,50 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.map.event;
+
+import schmitzm.geotools.gui.JMapPane;
+
+/**
+ * Diese Klasse stellt ein Ereignis dar, das ein {@link JMapPane} ausloest,
+ * wenn sich die Skalierung/Aufloesung der Karte aendert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ScaleChangedEvent extends JMapPaneEvent {
+  private double oldScale = 0;
+  private double newScale = 0;
+
+  /**
+   * Erzeugt ein neues Ereignis.
+   * @param oldScale vormalige Aufloesung
+   * @param newScale neue Aufloesung
+   */
+  public ScaleChangedEvent(JMapPane source, double oldScale, double newScale) {
+    super(source);
+    this.oldScale = oldScale;
+    this.newScale = newScale;
+  }
+
+  /**
+   * Liefert die alte Aufloesung.
+   */
+  public double getOldScale() {
+    return oldScale;
+  }
+
+  /**
+   * Liefert die neue Aufloesung.
+   */
+  public double getNewScale() {
+    return newScale;
+  }
+}

Added: trunk/src/schmitzm/geotools/package.html
===================================================================
--- trunk/src/schmitzm/geotools/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,7 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen, die auf der
+	<a href="http://www.geotools.org" target=_blank>GeoTools</a>-Bibliothek basieren. Diese
+	sind allgemein verwendbar und <u>nicht</u> Xulu-spezifisch!
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/geotools/styling/ColorMapManager.java
===================================================================
--- trunk/src/schmitzm/geotools/styling/ColorMapManager.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/styling/ColorMapManager.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,70 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.styling;
+
+import org.geotools.styling.ColorMap;
+import java.util.HashMap;
+
+/**
+ * Diese Klasse verwaltet eine Liste von {@linkplain ColorMap Farbpaletten}
+ * fuer Rasterdaten als Tupel ({@code Name}, {@link ColorMap}).
+ * Zudem kann eine Farbpalette als Standard deklariert werden.
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class ColorMapManager extends HashMap<String,ColorMap> {
+  /** Name der Standard-Farbtabelle. */
+  protected String defaultColorMap = null;
+
+  /**
+   * Erzeugt einen neuen {@code ColorMapManager}.
+   */
+  public ColorMapManager() {
+    super();
+  }
+
+  /**
+   * Fuegt dem Manager eine Farbpalette hinzu.
+   * @param name Bezeichnung der Farbpalette
+   * @param map  Farbpalette
+   * @param setDefault  ist dieses Flag gesetzt, wird diese Farbpalette als
+   *                    Standard festgelegt
+   */
+  public void put(String name, ColorMap map, boolean setDefault) {
+    put(name,map);
+    if ( setDefault )
+      setDefaultColorMap( name );
+  }
+
+  /**
+   * Setzt die Standard-Farbpalette.
+   * @param name Bezeichnung der Farbpalette
+   */
+  public void setDefaultColorMap(String name) {
+    defaultColorMap = name;
+  }
+
+  /**
+   * Liefert die Bezeichnung der Standard-Farbpalette.
+   */
+  public String getDefaultColorMapName() {
+    return defaultColorMap;
+  }
+
+  /**
+   * Liefert die Standard-Farbpalette.
+   */
+  public ColorMap getDefaultColorMap() {
+    return get(defaultColorMap);
+  }
+
+}

Added: trunk/src/schmitzm/geotools/styling/StylingUtil.java
===================================================================
--- trunk/src/schmitzm/geotools/styling/StylingUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/geotools/styling/StylingUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,831 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.geotools.styling;
+
+import java.awt.Color;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.units.Unit;
+import javax.xml.transform.TransformerException;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.Category;
+import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.data.FeatureSource;
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.factory.Hints;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.GeometryAttributeType;
+import org.geotools.resources.i18n.Vocabulary;
+import org.geotools.resources.i18n.VocabularyKeys;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.ColorMapEntry;
+import org.geotools.styling.ColorMapEntryImpl;
+import org.geotools.styling.ColorMapImpl;
+import org.geotools.styling.FeatureTypeStyle;
+import org.geotools.styling.FeatureTypeStyleImpl;
+import org.geotools.styling.RasterSymbolizer;
+import org.geotools.styling.RasterSymbolizerImpl;
+import org.geotools.styling.Rule;
+import org.geotools.styling.RuleImpl;
+import org.geotools.styling.SLDParser;
+import org.geotools.styling.SLDTransformer;
+import org.geotools.styling.Style;
+import org.geotools.styling.StyleBuilder;
+import org.geotools.styling.StyleFactory;
+import org.geotools.styling.Symbolizer;
+import org.geotools.util.NumberRange;
+import org.jdom.Element;
+import org.jdom.output.XMLOutputter;
+import org.opengis.coverage.grid.Grid;
+import org.opengis.coverage.grid.GridCoverage;
+import org.opengis.filter.expression.Expression;
+
+import schmitzm.geotools.feature.FeatureUtil;
+import schmitzm.geotools.grid.GridUtil;
+
+/**
+ * Diese Klasse enthaelt Hilfsfunktionen zum GeoTools-Styling
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ * @version 1.1
+ */
+public class StylingUtil {
+  private static final Logger LOGGER = Logger.getLogger(StylingUtil.class);
+  /** Transparente Farbe */
+  public static final Color TRANSPARENT_COLOR = new Color(0,0,0,0);
+
+  /** Standard-Instanz eines {@link StyleBuilder}. */
+  public static final StyleBuilder STYLE_BUILDER = new StyleBuilder();
+  /** Standard-Instanz eines {@link StyleBuilder}.
+   *  @deprecated wurde ersetzt durch {@link #STYLE_BUILDER} */
+  public static final StyleBuilder builder = STYLE_BUILDER;
+
+  /** Standard-Instanz einer {@link StyleFactory} */
+  public static final StyleFactory STYLE_FACTORY = CommonFactoryFinder.getStyleFactory( new Hints( new HashMap()));
+  /** Standard-Instanz einer {@link StyleFactory}
+   *  @deprecated wurde ersetzt durch {@link #STYLE_FACTORY} */
+  public static final StyleFactory styleFactory = STYLE_FACTORY;
+
+  /**
+   * Erstellt einen Default-Style fuer ein Geo-Objekt.
+   * @param object {@link GridCoverage2D}, {@link org.geotools.coverage.grid.io.AbstractGridCoverage2DReader} oder
+   *               {@link FeatureCollection}
+   * @return {@code null} falls kein Style generiert werden kann
+   *
+   */
+  public static Style createDefaultStyle(Object object) {
+    Style style = STYLE_FACTORY.createStyle(); //SK.. nicer default than null !
+
+    if ( object instanceof GridCoverage2D || object instanceof org.geotools.coverage.grid.io.AbstractGridCoverage2DReader )
+      style = GridUtil.createDefaultStyle();
+
+    if ( object instanceof FeatureCollection )
+      style = FeatureUtil.createDefaultStyle( (FeatureCollection)object );
+
+    // SK.cb
+    if (object instanceof GeometryAttributeType)
+    	style = FeatureUtil.createDefaultStyle( (GeometryAttributeType)object );
+
+    if (object instanceof FeatureSource)
+    	style = FeatureUtil.createDefaultStyle( ((FeatureSource)object).getSchema().getDefaultGeometry() );
+    // SK.ce
+
+    return style;
+  }
+
+  /**
+   * Erzeugt eine {@link Category} fuer die NoData-Werte transparent dargestellt
+   * werden.
+   * @param geoValue Geo-Wert, der NoData repraesentiert
+   */
+  public static Category createNoDataCategory(double geoValue) {
+    return createNoDataCategory(0, geoValue);
+  }
+
+  /**
+   * Erzeugt eine {@link Category} fuer die NoData-Werte transparent dargestellt
+   * werden.
+   * @param value Sample-Wert der Category
+   * @param geoValue Geo-Wert, der NoData repraesentiert
+   */
+  public static Category createNoDataCategory(int value, double geoValue) {
+    return new Category(
+        Vocabulary.formatInternational(VocabularyKeys.NODATA),
+        new Color[] { new Color(0,0,0,0) },
+        new NumberRange(value, value),
+        new NumberRange(geoValue, geoValue)
+    );
+  }
+
+  /**
+   * Erzeugt eine {@link GridSampleDimension} aus einer {@link ColorMap}.
+   * @param name         Name fuer die {@link GridSampleDimension}
+   * @param colorMap     fuer jeden Eintrag der {@link ColorMap} wird eine
+   *                     {@link Category} erzeugt
+   * @param noDataValues Werte fuer die zusaetzliche {@link Category Categorys}
+   *                     erstellt werden, welche die transparent dargestellt
+   *                     werden (wenn {@code null} wird keine zusaetzliche
+   *                     {@link Category} erstellt)
+   * @param unit         Einheit der Werte in der {@link GridSampleDimension}
+   *                     (kann {@code null} sein)
+   */
+  public static GridSampleDimension createDiscreteGridSampleDimension(String name, ColorMap colorMap, double[] noDataValues, Unit unit) {
+    if ( colorMap.getColorMapEntries().length == 0 )
+      return null;
+
+    SortedMap<Integer,Category> categories = new TreeMap<Integer,Category>();
+    if ( noDataValues == null )
+      noDataValues = new double[0];
+
+    // Create a (discrete) Category for each ColorMapEntry
+    int colorMapMin   = Integer.MAX_VALUE;
+    int colorMapMax   = Integer.MIN_VALUE;
+    for (ColorMapEntry cme : colorMap.getColorMapEntries()) {
+      String label    = cme.getLabel();
+      Color  color    = getColorFromColorMapEntry(cme);
+      double value    = getQuantityFromColorMapEntry(cme);
+      int    intValue = (int)Math.round(value);
+      colorMapMin     = Math.min(colorMapMin, intValue);
+      colorMapMax     = Math.max(colorMapMax, intValue);
+//      if ( label == null || label.trim().equals("") )
+//        label = String.valueOf(intValue);
+      if ( label == null )
+        label = "";
+      categories.put(intValue, new Category(
+          label,
+          new Color[] {color},
+          new NumberRange(intValue, intValue),
+          new NumberRange(intValue, intValue)
+      ));
+    }
+
+
+    // If NoData-Value is set, create an additional Category
+    for (double noDataValue : noDataValues ) {
+      int value = categories.lastKey() + 10; // value not already used
+      categories.put(value, createNoDataCategory(value, noDataValue));
+      colorMapMin     = Math.min(colorMapMin, (int)noDataValue);
+      colorMapMax     = Math.max(colorMapMax, (int)noDataValue);
+    }
+
+    // Declare "all" values smaller/greater the color map values
+    // automatically as NoData
+    //int colorMapRange = colorMapMax - colorMapMin;
+    //double lowerStart    = colorMapMin - 10000*colorMapRange;
+    //double higherEnd     = colorMapMax + 10000*colorMapRange;
+    final int lowerStart    = Integer.MIN_VALUE;
+    final int higherEnd     = Integer.MAX_VALUE;
+    categories.put(colorMapMax+10, new Category(
+        "_autoNoData1_",
+        new Color[] { new Color(0,0,0,0) },
+        new NumberRange(colorMapMax+1, colorMapMax+2),
+        new NumberRange(colorMapMax+1, higherEnd)
+    ));
+    if ( colorMapMin < colorMapMax )
+      categories.put(colorMapMin-10, new Category(
+          "_autoNoData2_",
+          new Color[] { new Color(0,0,0,0) },
+          //new NumberRange(colorMapMin-2, colorMapMin-1), // logischer, aber klappt nicht!?
+          new NumberRange(colorMapMax+3, colorMapMax+4),
+          new NumberRange(lowerStart,    colorMapMin-1)
+      ));
+
+
+
+    // Create the GridSampleDimension
+    GridSampleDimension gsd = new GridSampleDimension(
+        name,
+        categories.values().toArray(new Category[0]),
+        unit
+    ).geophysics(true);
+
+    for ( Category c : (List<Category>)gsd.getCategories() )
+      LOGGER.debug( c.toString() );
+
+    return gsd;
+  }
+
+  /**
+   * Liefert die Farbpalette zu einem Style, wenn der Style aus einem
+   * {@link RasterSymbolizer} erzeugt wurde.
+   * @param style Style Wenn <code>null</code>, dann wird style auf {@link GridUtil}.createDefaultStyle() gesetzt
+   * @return <code>null</code> wenn der Style nicht auf einem {@link RasterSymbolizer}
+   *         basiert.
+   */
+  public static ColorMap getColorMapFromStyle(Style style) {
+
+	if (style == null)
+		style = GridUtil.createDefaultStyle();
+
+    FeatureTypeStyle[] fts  = style.getFeatureTypeStyles();
+    if ( fts == null || fts.length == 0 )
+      return null;
+    Rule[] rule = fts[0].getRules();
+    if ( rule == null || rule.length == 0 )
+      return null;
+    Symbolizer[] symb = rule[0].getSymbolizers();
+    if ( symb == null || symb.length == 0 || !(symb[0] instanceof RasterSymbolizer) )
+      return null;
+    return ((RasterSymbolizer)symb[0]).getColorMap();
+
+  }
+
+  /**
+   * Ermittelt fuer einen Wert den {@link ColorMapEntry} aus einer
+   * {@link ColorMap}.
+   * @param value Wert
+   * @return {@code null}, wenn in der {@link ColorMap} kein expliziter
+   *         Eintrag fuer den Wert hinterlegt ist
+   */
+  public static ColorMapEntry findColorMapEntry(ColorMap colorMap, double value) {
+    for (ColorMapEntry entry : colorMap.getColorMapEntries())
+      if ( value == getQuantityFromColorMapEntry(entry) )
+        return entry;
+    return null;
+  }
+
+  /**
+   * Kopiert eine {@link ColorMap}.
+   * @return <code>null</code> wenn die uebergebene ColorMap <code>null</code>
+   *         ist
+   */
+  public static ColorMap cloneColorMap(ColorMap colorMap) {
+    if ( colorMap == null )
+      return null;
+    ColorMapEntry[] entry = colorMap.getColorMapEntries();
+    ColorMap newColorMap = new ColorMapImpl();
+    newColorMap.setType( colorMap.getType() );  // Hat noch keinen Effekt in 2.3.2, aber dennoch, SK
+    for (int i=0; i<entry.length; i++)
+      newColorMap.addColorMapEntry( cloneColorMapEntry(entry[i]) );
+    return newColorMap;
+  }
+
+  /**
+   * Prueft, ob zwei {@link ColorMap}-Instanzen gleich sind.
+   * @param cm1 eine Farbpalette
+   * @param cm2 eine andere Farbpalette
+   */
+  public static boolean colorMapsEqual(ColorMap cm1, ColorMap cm2) {
+    if ( cm1 == null ^ cm2 == null )
+      return false;
+    if ( cm1 == cm2 )
+      return true;
+
+    ColorMapEntry[] cme1 = cm1.getColorMapEntries();
+    ColorMapEntry[] cme2 = cm2.getColorMapEntries();
+    if ( cme1.length != cme2.length )
+      return false;
+
+    // Farbpaletten-Eintraege muessen gleich sein und in gleicher
+    // Reihenfolge vorliegen!
+    for (int i=0; i<cme1.length; i++)
+      if ( !colorMapEntriesEqual(cme1[i],cme2[i]) )
+        return false;
+    return true;
+  }
+
+  /**
+   * Prueft, ob zwei {@link ColorMapEntry}-Instanzen gleich sind. Dies ist der
+   * Fall, wenn ihnen der gleiche Wert, die gleiche Farbe, die gleiche Transparenz
+   * und das gleiche Label zugeordnet ist.
+   * @param cme1 ein Farbpaletten-Eintrag
+   * @param cme2 ein anderer Farbpaletten-Eintrag
+   */
+  public static boolean colorMapEntriesEqual(ColorMapEntry cme1, ColorMapEntry cme2) {
+    if ( cme1 == null ^ cme2 == null )
+      return false;
+    if ( cme1 == cme2 )
+      return true;
+
+    // Label auf Gleichheit testen
+    if ( cme1.getLabel() == null ^ cme2.getLabel() == null ||
+         cme1 != null && !cme1.getLabel().equals( cme2.getLabel() ) )
+      return false;
+    // Farbe auf Gleichheit testen
+    Color col1 = getColorFromColorMapEntry(cme1);
+    Color col2 = getColorFromColorMapEntry(cme2);
+    if ( col1 == null ^ col2 == null ||
+         col1 != null && !col1.equals( col2 ) )
+      return false;
+    // Wert auf Gleichheit testen
+    Double quan1 = getQuantityFromColorMapEntry(cme1);
+    Double quan2 = getQuantityFromColorMapEntry(cme2);
+    if ( quan1 == null ^ quan2 == null ||
+         quan1 != null && !quan1.equals( quan2 ) )
+      return false;
+    // Transparenz auf Gleichheit testen
+    Double op1 = getOpacityFromColorMapEntry(cme1);
+    Double op2 = getOpacityFromColorMapEntry(cme2);
+    if ( op1 == null ^ op2 == null ||
+         op1 != null && !op1.equals( op2 ) )
+      return false;
+
+    return true;
+  }
+
+
+  /**
+   * Erzeugt einen {@link ColorMapEntry}.
+   * @param label Label fuer den Farbpaletten-Eintrag
+   * @param quantity Wert fuer den Farbpaletten-Eintrag
+   * @param color Farbe fuer den Farbpaletten-Eintrag
+   * @param opacity Transparenz fuer die Farbe des Farbpaletten-Eintrag
+   */
+  public static ColorMapEntry createColorMapEntry(String label, Double quantity, Color color, double opacity)  {
+    ColorMapEntry newEntry = new ColorMapEntryImpl();
+    try {
+      newEntry.setLabel( label );
+      newEntry.setColor( STYLE_BUILDER.colorExpression( color ) );
+      newEntry.setOpacity( STYLE_BUILDER.literalExpression( opacity ) );
+      newEntry.setQuantity( STYLE_BUILDER.literalExpression( quantity ) );
+    } catch (Exception err) {
+    }
+    return newEntry;
+  }
+
+  /**
+   * Kopiert einen {@link ColorMapEntry}.
+   * @return <code>null</code> wenn der uebergebene ColorMapEntry <code>null</code>
+   *         ist
+   */
+  public static ColorMapEntry cloneColorMapEntry(ColorMapEntry colorMapEntry)  {
+    if ( colorMapEntry == null )
+      return null;
+
+    return createColorMapEntry(
+      colorMapEntry.getLabel(),
+      getQuantityFromColorMapEntry(colorMapEntry),
+      getColorFromColorMapEntry(colorMapEntry),
+      getOpacityFromColorMapEntry(colorMapEntry)
+    );
+  }
+
+  /**
+   * Liefert den Wert eines Farbpaletten-Eintrag.
+   * @param expression Expression, die einen String oder einen Double liefert
+   * @return {@code null}, wenn <code>null</code> uebergeben wird
+   */
+  public static Double getQuantityFromExpression(Expression expression) {
+// gt2-2.3.4:
+//    if ( expression == null || expression.getValue(null) == null )
+//      return null
+//    Object exprValue = evaluate.getValue(null);
+// gt2-2.3.4:
+    if ( expression == null || expression.evaluate(null) == null )
+      return null;
+    Object exprValue = expression.evaluate(null);
+
+    return (exprValue instanceof String) ? Double.valueOf( (String) exprValue) : ((Number)exprValue).doubleValue();
+  }
+
+  /**
+   * Liefert den Wert eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   * @return {@code null}, wenn <code>null</code> uebergeben wird
+   */
+  public static Double getQuantityFromColorMapEntry(ColorMapEntry entry) {
+    if ( entry == null )
+      return null;
+    return getQuantityFromExpression(entry.getQuantity());
+  }
+
+  /**
+   * Setzt den Wert eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   * @param quantity neuer Wert
+   */
+  public static void setQuantityForColorMapEntry(ColorMapEntry entry, double quantity) {
+    if ( entry != null )
+      entry.setQuantity( STYLE_BUILDER.literalExpression( quantity ) );
+  }
+
+  /**
+   * Liefert die Transparenz eines Farbpaletten-Eintrag.
+   * @param expression Expression, die einen String oder einen Double liefert
+   * @return 1.0, wenn <code>null</code> uebergeben wird
+   */
+  public static Double getOpacityFromExpression(Expression expression) {
+// gt2-2.3.4:
+//    if ( expression == null || expression.getValue(null) == null )
+//      return 1.0
+//    Object exprValue = evaluate.getValue(null);
+// gt2-2.3.4:
+    if ( expression == null || expression.evaluate(null) == null )
+      return 1.0;
+    Object exprValue = expression.evaluate(null);
+
+    return (exprValue instanceof String) ? Double.valueOf( (String) exprValue) : ((Number)exprValue).doubleValue();
+  }
+
+  /**
+   * Liefert die Transparenz eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   * @return 1.0, wenn <code>null</code> uebergeben wird
+   */
+  public static Double getOpacityFromColorMapEntry(ColorMapEntry entry) {
+    if ( entry == null )
+      return 1.0;
+    return getOpacityFromExpression(entry.getOpacity());
+  }
+
+  /**
+   * Setzt die Transparenz eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   * @param opacity Transparenzwert
+   */
+  public static void setOpacityForColorMapEntry(ColorMapEntry entry, double opacity) {
+    if ( entry != null )
+      entry.setOpacity( STYLE_BUILDER.literalExpression( opacity ) );
+  }
+
+  /**
+   * Liefert die Farbe eines Farbpaletten-Eintrag.
+   * @param expression Expression, die einen String liefert
+   * @param opacity    Transparenz der Farbe
+   */
+  public static Color getColorFromExpression(Expression expression, Double opacity) {
+    if ( expression == null )
+      return null;
+
+    return Color.decode( (String) expression.evaluate(null) );
+
+////  int colorInt = Integer.decode( (String) expression.getValue(null)); // gt2-2.3.4
+//    int colorInt = Integer.decode( (String) expression.evaluate(null)); // gt2-2-4-2
+//
+//    return new Color(
+//      (colorInt >> 16) & 0xFF,
+//      (colorInt >> 8) & 0xFF,
+//      colorInt & 0xFF,
+//      new Double(Math.ceil(255.0 * opacity.floatValue())).intValue()
+//    );
+  }
+
+  /**
+   * Liefert die Farbe eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   */
+  public static Color getColorFromColorMapEntry(ColorMapEntry entry) {
+//    if ( entry == null || entry.getColor() == null || entry.getColor().getValue(null) == null ) // gt2-2.3.4
+    if ( entry == null || entry.getColor() == null || entry.getColor().evaluate(null) == null ) // gt2-2-4-2
+      return null;
+
+    // Transparenz
+    Double opacity = getOpacityFromColorMapEntry(entry);
+    // Farbe
+    return getColorFromExpression(entry.getColor(), opacity);
+  }
+
+  /**
+   * Setzt die Farbe eines Farbpaletten-Eintrag.
+   * @param entry Farbpaletten-Eintrag
+   * @param color eine Farbe
+   */
+  public static void setColorForColorMapEntry(ColorMapEntry entry, Color color) {
+    if ( entry != null )
+      entry.setColor( STYLE_BUILDER.colorExpression( color ) );
+  }
+
+  /**
+   * Erzeugt einen {@link Style} aus einem {@linkplain Element JDOM-Element},
+   * des eine SLD-Definition enthaelt
+   * @param element Element mit SLD-Definition
+   */
+  public static Style createStyleFromSLD(Element element) {
+    String  xmlDefinition = new XMLOutputter().outputString(element);
+    Style[] style = loadSLD( new ByteArrayInputStream(xmlDefinition.getBytes()) );
+    return style == null || style.length == 0 ? null : style[0];
+  }
+
+
+
+
+	/**
+	 * Creates a {@link Style} for a {@link Grid} layer from a {@link ColorMap}
+	 *
+	 * @param colorMap
+	 *            If null, then defaultStyle for Grid will be used
+	 * @param name
+	 *            The name to give to the Style. null will result in name=
+	 *            {@link GridUtil#UNNAMED_RASTER_STYLE_NAME} or GridUtil#DEFAULT_RASTER_STYLE_NAME} (if no colorMap is given).
+	 *
+	 * @param title
+	 * 			 The Title to give to the Style. null will result in title=
+	 * 			  {@link GridUtil#UNTITLED_RASTER_STYLE_TITLE} or {@link GridUtil#DEFAULT_RASTER_STYLE_TITLE} (if no colorMap is given)
+	 *
+	 * @return Always a {@link Style} that you can be applyed to a {@link GridCoverage}.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static Style createStyleFromColorMap(ColorMap colorMap, String name, String title) {
+		if (colorMap == null)
+			return GridUtil.createDefaultStyle();
+
+		if (name == null)
+			// A colorMap was provided, but no name.
+			name = GridUtil.UNNAMED_RASTER_STYLE_NAME;
+
+		if (title == null)
+			// A colorMap was provided, but no title.
+			title = GridUtil.UNTITLED_RASTER_STYLE_TITLE;
+
+		RasterSymbolizerImpl rasterSymbolizerImpl = new RasterSymbolizerImpl();
+
+		// Entferne alle label informationen
+		clearColorMapLabels(colorMap);
+
+		rasterSymbolizerImpl.setColorMap(colorMap);
+		Symbolizer[] symbolizers = { rasterSymbolizerImpl };
+		RuleImpl ruleImpl = new RuleImpl(symbolizers) {
+		};
+		Rule[] rules = { ruleImpl };
+		FeatureTypeStyleImpl featureTypeStyleImpl = new FeatureTypeStyleImpl(
+				rules) {
+		};
+
+		Style style = STYLE_FACTORY.createStyle();
+		style.setName(name);
+		style.setTitle(title);
+		style.addFeatureTypeStyle(featureTypeStyleImpl);
+		return style;
+	}
+
+
+	/**
+	 * Removes all label information from the {@link ColorMapEntry}s of the given {@link ColorMap}
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static ColorMap clearColorMapLabels(ColorMap colorMap) {
+		for (ColorMapEntry cme : colorMap.getColorMapEntries()){
+			cme.setLabel(null);
+		}
+		return colorMap;
+	}
+
+
+	/**
+	 * Creates a {@link Style} for a {@link Grid} layer from a {@link ColorMap}
+	 * Title will be set to {@link GridUtil#UNTITLED_RASTER_STYLE_TITLE}
+	 *
+	 * @param colorMap
+	 *            If null, then defaultStyle for Grid will be used
+	 * @param name
+	 *            The name to give to the Style. null will result in name
+	 *            {@link GridUtil#UNTITLED_RASTER_STYLE_TITLE}
+	 *
+	 * @return Always a Style that you can apply to a {@link GridCoverage}.
+	 *         Style has name {@link GridUtil#DEFAULT_RASTER_STYLE_NAME} if no colormap
+	 *         was provided.
+	 *
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static Style createStyleFromColorMap(ColorMap colorMap, String name) {
+		return createStyleFromColorMap(colorMap, name, null);
+	}
+
+
+
+
+	/**
+	 * Loads {@link Style}s from a SLD {@link InputStream}
+
+	 * @param url {@link URL} to read the SLD from
+	 *
+	 * @return An {@link Array} of {@link Style}s, can be length==0 if no UserStyles in SLD file. null if file not exists
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static Style[] loadSLD(URL url) {
+		// LOGGER.debug("Loading styles from URL...");
+		try {
+			return loadSLD(url.openStream());
+		} catch (IOException e) {
+			return null;
+		}
+	}
+
+
+	/**
+	 * Loads {@link Style}s from a SLD {@link InputStream}
+
+	 * @param inputStream {@link InputStream} to read the SLD from
+	 *
+	 * @return An {@link Array} of {@link Style}s, can be length==0. null if file not exists
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static Style[] loadSLD(InputStream inputStream) {
+		final InputStream inputStream2 = inputStream;
+		if (inputStream2 == null) {
+			return null;
+		}
+
+		LOGGER.debug("Loading styles from inputStream...");
+
+		SLDParser stylereader;
+		stylereader = new SLDParser( StylingUtil.STYLE_FACTORY);
+		stylereader.setInput( inputStream);
+		Style[] styles = stylereader.readXML();
+
+		if (styles[0] == null) {
+			LOGGER.warn(" ... no Styles recognized in inputStream. Will return empty styles[] array!");
+		} else {
+			LOGGER.debug(" ... loaded " + styles.length
+					+ " styles from inputStream, first style's name= "
+					+ styles[0].getName());
+		}
+
+		// This is the main successful exit form loadSLD
+		return styles;
+	}
+
+
+	/**
+	 * Loads {@link Style}s from a SLD {@link InputStream} without setting a {@link Charset}
+
+	 * @param inputStream {@link InputStream} to read the SLD from
+	 *
+	 * @return An {@link Array} of {@link Style}s, can be length==0. null if file not exists
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 *
+	 * @deprecated
+	 */
+	public static Style[] loadSLDOld(InputStream inputStream) {
+		final InputStream inputStream2 = inputStream;
+		if (inputStream2 == null) {
+			return null;
+		}
+
+		// Reader reader = new UTF8Reader(inputStream2);
+
+		LOGGER.debug("Loading styles from inputStream...");
+
+		SLDParser stylereader;
+		stylereader = new SLDParser( StylingUtil.STYLE_FACTORY, inputStream2);
+		Style[] styles = stylereader.readXML();
+
+		if (styles[0] == null) {
+			LOGGER
+					.warn(" ... no Styles recognized in inputStream. Will return empty styles[] array!");
+		} else {
+			LOGGER.debug(" ... loaded " + styles.length
+					+ " styles from inputStream, first style's name= "
+					+ styles[0].getName());
+		}
+
+		// This is the main successful exit form loadSLD
+		return styles;
+	}
+
+
+	/**
+	 * Loads {@link Style}s from a SLD {@link File}
+	 *
+	 * @param sldFile {@link File} to read the SLD from
+	 *
+	 * @return An {@link Array} of {@link Style}s, can be length==0
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static Style[] loadSLD(File sldFile) throws FileNotFoundException {
+		LOGGER.debug("Loading styles from File...");
+
+		final Style[] loadedSLD = loadSLD( new FileInputStream( sldFile ) );
+
+		// TODO Maybe check if names and titles should be set to default values...
+
+		return loadedSLD;
+	}
+
+	/**
+	 * Saves the {@link Style} to OGC SLD. Overwrites any existing file.
+	 *
+	 * @param style
+	 *            {@link Style} to save
+	 * @param charset The charset to use for the XML, e.g. <?xml version="1.0" encoding="ISO-8859-1"?>. If null, ISO-8859-1 is used.
+	 *
+	 * @throws TransformerException
+	 * @throws IOException
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static final void saveStyleToSLD(Style style, File exportFile, Charset charset)
+			throws TransformerException, IOException {
+		Writer w = null;
+		final SLDTransformer aTransformer = new SLDTransformer();
+		if (charset != null) {
+			aTransformer.setEncoding( charset );
+		}
+		aTransformer.setIndentation(2);
+		final String xml = aTransformer.transform(style);
+		w = new FileWriter(exportFile);
+		w.write(xml);
+		w.close();
+		LOGGER.info("Saved a Style to " + exportFile+" with charset "+ aTransformer.getEncoding().name());
+	}
+
+
+	/**
+	 * Saves the {@link Style} to OGC SLD using ISO-8859-1 as charset.
+	 * Overwrites any existing file.
+	 *
+	 * @param style
+	 *            {@link Style} to save
+	 *
+	 * @throws TransformerException
+	 * @throws IOException
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static final void saveStyleToSLD(Style style, File exportFile)
+			throws TransformerException, IOException {
+		saveStyleToSLD(style, exportFile, Charset.forName("ISO-8859-1") );
+	}
+
+
+
+
+//	/**
+//	 * Sorts a {@link ColorMap} by its quantity values. This is mentioned in the
+//	 * OGC SLD Definition, page 52:
+//	 * <p>
+//	 * Quote: For example, a DEM raster giving elevations in meters above sea
+//	 * level can be translated to a colored image with a ColorMap. The quantity
+//	 * attributes of a color-map are used for translating between numeric
+//	 * matrixes and color rasters and the ColorMap entries should be in order of
+//	 * increasing numeric quantity so that intermediate numeric values can be
+//	 * matched to a color (or be interpolated between two colors). Labels may be
+//	 * used for legends or may be used in the future to match character values.
+//	 * Not all systems can support opacity in colormaps. The default opacity is
+//	 * 1.0 (fully opaque). Defaults for quantity and label are system-dependent.
+//	 * </p>
+//	 *
+//	 * @param colorMap
+//	 *            {@link ColorMap} to sort by the quantity-field of the
+//	 *            {@link ColorMapEntry}s
+//	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+//	 */
+//	public static void sort(RemovableColorMapImpl colorMap) {
+//		ColorMapEntry[] colorMapEntries = colorMap.getColorMapEntries();
+//		SortedMap<Double, ColorMapEntry> tmap = new TreeMap<Double, ColorMapEntry>();
+//		// Add to a SortedMap
+//		for (ColorMapEntry cme : colorMapEntries) {
+//			tmap.put(getQuantityFromColorMapEntry(cme), cme);
+//			colorMap.removeColorMapEntry(cme);
+//		}
+//		// Recreate ColorMap from sorted quantity values
+//		for (ColorMapEntry cme : tmap.values()) {
+//			colorMap.addColorMapEntry(cme);
+//		}
+//	}
+
+
+//  /**
+//	 * Lets assume that every quantity value is maximally listed once in a
+//	 * {@link ColorMap}, then it makes some sense to use it as a key.
+//	 *
+//	 * @param quantityString
+//	 *            quantitiy as String
+//	 * @return The first colorMap that has the same Quantity ( compared as
+//	 *         Strings)
+//	 *
+//	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+//	 */
+//	public static ColorMapEntry getColorMapEntryByQuantityString(
+//			ColorMap colorMap, String quantityString) {
+//		for (ColorMapEntry cme : colorMap.getColorMapEntries()) {
+//			if (quantityString.equals(cme.getQuantity().toString()))
+//				return cme;
+//		}
+//		return null;
+//	}
+}

Added: trunk/src/schmitzm/io/BinaryInputBuffer.java
===================================================================
--- trunk/src/schmitzm/io/BinaryInputBuffer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/BinaryInputBuffer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,180 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+/**
+ * Der <code>BinaryInputBuffer</code> erweitert den <code>InputBuffer</code>
+ * durch die Moeglichkeit, neben einzelnen Bytes, auch die Binaerdarstellung
+ * ganze Zahlen (mehrere Bytes auf einen Schub) in den Buffer schreiben
+ * zu koennen.<br>
+ * Um in gleicherweise ganze Zahlen aus dem Buffer zu lesen, kann die
+ * Klasse <code>adagios.types.BinaryInputStream</code> verwendet werden:<br>
+ * <code>
+ * BinaryInputBuffer buf = new BinaryInputBuffer(1000);<br>
+ * BinaryInputStream inp = new BinaryInputStream( buf );<br>
+ * buf.writeInt( 123456 );<br>
+ * buf.writeShort( 1000 );<br>
+ * int i = inp.readInt();<br>
+ * int s = inp.readShort();<br>
+ * </code><br>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BinaryInputBuffer extends InputBuffer {
+
+  /**
+   * Erzeugt einen neuen <code>BinaryInputBuffer</code> der Groesse 1024.
+   */
+  public BinaryInputBuffer() {
+    this(1024);
+  }
+
+  /**
+   * Erzeugt einen neuen <code>BinaryInputBuffer</code>.
+   * @param byteSize Groesse des Buffers in Byte
+   */
+  public BinaryInputBuffer(int byteSize) {
+    super(byteSize);
+  }
+
+  /**
+   * Schreibt einen <code>char</code>-Wert (2 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param c         zu schreibender Char
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertLongToBytes(byte,long,int)
+   */
+  public void writeChar(byte byteOrder, char c) {
+    write( BinaryUtil.convertLongToBytes(byteOrder,c,2) );
+  }
+
+  /**
+   * Schreibt einen <code>char</code>-Wert (2 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement in <i>BigEndian</i> verwendet.
+   * @param c zu schreibender Char
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see #writeChar(byte,char)
+   */
+  public void writeChar(char c) {
+    writeChar( BinaryUtil.XDR, c );
+  }
+
+  /**
+   * Schreibt einen <code>short</code>-Wert (2 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param s         zu schreibender Short
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertLongToBytes(byte,long,int)
+   */
+  public void writeShort(byte byteOrder, short s) {
+    write( BinaryUtil.convertLongToBytes(byteOrder,s,2) );
+  }
+
+  /**
+   * Schreibt einen <code>short</code>-Wert (2 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement in <i>BigEndian</i> verwendet.
+   * @param s zu schreibender Short
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see #writeShort(byte,short)
+   */
+  public void writeShort(short s) {
+    writeShort( BinaryUtil.XDR, s );
+  }
+
+  /**
+   * Schreibt einen <code>int</code>-Wert (4 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param i         zu schreibender Int
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertLongToBytes(byte,long,int)
+   */
+  public void writeInt(byte byteOrder, int i) {
+    write( BinaryUtil.convertLongToBytes(byteOrder,i,4) );
+  }
+
+  /**
+   * Schreibt einen <code>int</code>-Wert (4 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement in <i>BigEndian</i> verwendet.
+   * @param i zu schreibender Int
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see #writeInt(byte,int)
+   */
+  public void writeInt(int i) {
+    writeInt( BinaryUtil.XDR, i );
+  }
+
+  /**
+   * Schreibt einen <code>long</code>-Wert (8 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param l         zu schreibender Long
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertLongToBytes(byte,long,int)
+   */
+  public void writeLong(byte byteOrder, long l) {
+    write( BinaryUtil.convertLongToBytes(byteOrder,l,8) );
+  }
+
+  /**
+   * Schreibt einen <code>long</code>-Wert (8 Byte) in den Buffer.
+   * Hierbei wird das 2er-Komplement in <i>BigEndian</i> verwendet.
+   * @param l zu schreibender Long
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see #writeLong(byte,long)
+   */
+  public void writeLong(long l) {
+    writeLong( BinaryUtil.XDR, l );
+  }
+
+  /**
+   * Schreibt einen <code>float</code>-Wert (4 Byte) in den Buffer.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param f         zu schreibender Float
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertFloatToBytes(byte,float)
+   */
+  public void writeFloat(byte byteOrder, float f) {
+    write( BinaryUtil.convertFloatToBytes(byteOrder,f) );
+  }
+
+  /**
+   * Schreibt einen <code>double</code>-Wert (8 Byte) in den Buffer.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @param d         zu schreibender Double
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see schmitzm.io.BinaryUtil#NDR
+   * @see schmitzm.io.BinaryUtil#convertDoubleToBytes(byte,double)
+   */
+  public void writeDouble(byte byteOrder, double d) {
+    write( BinaryUtil.convertDoubleToBytes(byteOrder,d) );
+  }
+
+  /**
+   * Schreibt einen <code>float</code>-Wert (8 Byte) in den Buffer.
+   * Hierbei wird <i>BigEndian</i> verwendet.
+   * @param f zu schreibender Float
+   * @see schmitzm.io.BinaryUtil#XDR
+   * @see #writeFloat(byte,float)
+   */
+  public void writeFloat(float f) {
+    writeFloat( BinaryUtil.XDR, f );
+  }
+
+}

Added: trunk/src/schmitzm/io/BinaryInputStream.java
===================================================================
--- trunk/src/schmitzm/io/BinaryInputStream.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/BinaryInputStream.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,152 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.InputStream;
+import java.io.DataInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * <code>BinaryInputStream</code> erweitert die Klasse
+ * <code>java.io.DataInputStream</code> um Methoden, mit denen man Werte
+ * wahlweise in <i>BigEndian</i> oder <i>LittleEndian</i> aus dem Stream
+ * auslesen kann.<br>
+ * Die geerbten Methoden von <code>DataInputStream</code> interpretieren
+ * die Byte-Folgen immer als <i>BigEndian</i> (das erste Byte im Stream
+ * ist das hoechstwertige Byte für den zu lesenenden Wert).
+ * @see java.io.DataInputStream
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BinaryInputStream extends DataInputStream {
+  /**
+   * Erzeugt einen neuen Binary-Eingabestream.
+   */
+  public BinaryInputStream(InputStream in) {
+    super(in);
+  }
+
+  /**
+   * Erzeugt einen neuen Binary-Eingabestream, in dem aus dem
+   * <code>byte</code>-Array ein {@link ByteArrayInputStream}
+   * erzeugt wird.
+   * @see java.io.ByteArrayInputStream
+   */
+  public BinaryInputStream(byte[] bytes) {
+    this( new ByteArrayInputStream(bytes) );
+  }
+
+  /**
+   * Erzeugt einen neuen Binary-Eingabestream, in dem aus dem String
+   * ein {@link ByteArrayInputStream} erzeugt wird.
+   * @see java.io.ByteArrayInputStream
+   */
+  public BinaryInputStream(String str) {
+    this( new ByteArrayInputStream(str.getBytes()) );
+  }
+
+  /**
+   * Liefert einen {@link InputStream} als {@link BinaryInputStream}.
+   * Ist der uebergebene <code>InputStream</code> bereits eine Instanz von
+   * <code>BinaryInputStream</code> wird diese (gecastet) zurueckgegeben.
+   * Ansonsten wird eine neue <code>BinaryInputStream</code>-Instanz aus dem
+   * <code>InputStream</code> erzeugt.
+   */
+  public static BinaryInputStream fromInputStream(InputStream in) {
+    if (in instanceof BinaryInputStream) return (BinaryInputStream)in;
+    return new BinaryInputStream(in);
+  }
+
+  /**
+   * Liest einen <code>char</code>-Wert (2 Bytes) aus dem Stream.
+   * Dabei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToLong(byte,InputStream,int)
+   */
+  public char readChar(byte byteOrder) throws IOException {
+    return (char)BinaryUtil.convertBytesToLong(byteOrder,this,2);
+  }
+
+  /**
+   * Liest einen <code>short</code>-Wert (2 Bytes) aus dem Stream.
+   * Dabei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToLong(byte,InputStream,int)
+   */
+  public short readShort(byte byteOrder) throws IOException {
+    return (short)BinaryUtil.convertBytesToLong(byteOrder,this,2);
+  }
+
+  /**
+   * Liest einen <code>short</code>-Wert (2 Bytes) aus dem Stream, der ohne
+   * Vorzeichen interpretiert wird. Dabei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToLong(byte,InputStream,int)
+   */
+  public int readUnsignedShort(byte byteOrder) throws IOException {
+    return (int)BinaryUtil.convertBytesToLong(byteOrder,this,2);
+  }
+
+  /**
+   * Liest einen <code>int</code>-Wert (4 Bytes) aus dem Stream.
+   * Dabei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToLong(byte,InputStream,int)
+   */
+  public int readInt(byte byteOrder) throws IOException {
+    return (int)BinaryUtil.convertBytesToLong(byteOrder,this,4);
+  }
+
+  /**
+   * Liest einen <code>long</code>-Wert (8 Bytes) aus dem Stream.
+   * Dabei wird das 2er-Komplement verwendet.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToLong(byte,InputStream,int)
+   */
+  public long readLong(byte byteOrder) throws IOException {
+    return (long)BinaryUtil.convertBytesToLong(byteOrder,this,8);
+  }
+
+  /**
+   * Liest einen <code>double</code>-Wert (8 Bytes) aus dem Stream.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToDouble(byte,InputStream)
+   */
+  public double readDouble(byte byteOrder) throws IOException {
+    return (double)BinaryUtil.convertBytesToDouble(byteOrder,this);
+  }
+
+  /**
+   * Liest einen <code>float</code>-Wert (4 Bytes) aus dem Stream.
+   * @param byteOrder BigEndian/LittleEndian (vgl. {@link BinaryUtil})
+   * @see BinaryUtil#XDR
+   * @see BinaryUtil#NDR
+   * @see BinaryUtil#convertBytesToFloat(byte,InputStream)
+   */
+  public float readFloat(byte byteOrder) throws IOException {
+    return (long)BinaryUtil.convertBytesToFloat(byteOrder,this);
+  }
+
+}

Added: trunk/src/schmitzm/io/BinaryUtil.java
===================================================================
--- trunk/src/schmitzm/io/BinaryUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/BinaryUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,330 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Diese Klasse enthaelt statische Methoden, die das Arbeiten mit Binaerstrings
+ * erleichtern.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BinaryUtil {
+
+  /**
+   * Numerische Konstante fuer die ByteOrder 'Big Endian'.<br>
+   * Dabei steht das hoechstwertige Byte im ERSTEN Byte einer Folge.
+   */
+  public static final byte XDR = 0;
+
+  /**
+   * Numerische Konstante fuer die ByteOrder 'Little Endian'.<br>
+   * Dabei steht das niederstwertige Byte im ERSTEN Byte einer Folge.
+   */
+  public static final byte NDR = 1;
+
+
+  /**
+   * Konvertiert Bytes eines Streams in einen <code>long</code>-Wert.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param input     Eingabe-Stream
+   * @param byteCount Anzahl der Bytes, die aus dem Stream gelesen und in einen
+   *                  Long umgewandelt werden sollen
+   * @exception java.io.IOException falls das Lesen aus dem Stream scheitert
+   * @exception java.io.EOFException falls nicht genug Bytes auf dem Stream liegen
+   */
+  public static long convertBytesToLong(byte byteOrder, InputStream input, int byteCount) throws IOException {
+    byte[] bytes = new byte[byteCount];
+    if ( input.read(bytes) < byteCount )
+      throw new EOFException("Not enough Bytes available on InputStream");
+
+    return convertBytesToLong(byteOrder,bytes);
+  }
+
+  /**
+   * Konvertiert eine Reihe von Bytes in einen <code>long</code>-Wert.<br>
+   * Fuer negative Werte wird eine Darstellung im 2er-Komplement erwartet.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param bytes     Eingabe-Bytes
+   */
+  public static long convertBytesToLong(byte byteOrder, byte[] bytes) {
+    if (bytes == null || bytes.length == 0)
+      throw new ArrayIndexOutOfBoundsException("At least 1 Byte needed for convert to long!!");
+
+    // Bemerkung:
+    // ==========
+    // Wegen des sukzessiven Bit-Shift, muss die Summierung mit dem
+    // hoechstwertigen Byte begonnen werden.
+    // Dies steht bei 'BigEndian' im ersten, bei 'LittleEndian' im
+    // letzten Byte.
+    // Der zusaetzliche Summand 256 (wenn bytes[i]<0), ist notwendig, da
+    // der Wertebereich von 'byte' von -128 bis +127 reicht (nicht 0..255!!).
+    // Ein einfacher Cast von 'byte' zu 'char' ist leider nicht ausreichend,
+    // da 'char' durch 16Bit repraesentiert wird. Beim Cast wird das
+    // Vorzeichen-Bit also einfach uebernommen und der Wert bleibt unveraendert.
+    // Was gaebe ich in diesem Moment fuer das kleine Woertchen 'unsigned'!! :-)
+
+    // Das erste Bit des hoechstwerigen Bytes ist das Vorzeichenbit. Um das
+    // Vorzeichen im long-Ergebnis zu erhalten, muessen die fuehrenden Bits
+    // (die durch den byte-Array nicht abgedeckt werden) mit diesem VZ-Bit
+    // belegt werden.
+    // Dies geht am Einfachsten in dem man das Ergebnis initial komplett mit
+    // dem VZ-Bit belegt:
+    // a) hoechstwertiges Byte in long konvertierten
+    //    --> nun hat 'result' dasselbe VZ wie die in bytes[] kodierte Zahl
+    // b) die Bits des hoechstwertigen Bytes eliminieren, die nicht das VZ-Bit
+    //    sind (durch den Rechts-Shift wird linkt mit dem VZ-Bit aufgefuellt!)
+    long result = (byteOrder == XDR) ? bytes[0] : bytes[bytes.length-1];
+    result >>= 7;
+
+    if ( byteOrder == XDR )
+      // Big Endian (hoechstwertiges Byte zuerst im Array)
+      for(int i=0; i<bytes.length; i++)
+//      result = (result << 8) + bytes[i] + (bytes[i]<0 ? 256 : 0);
+        result = (result << 8) | getUnsignedByte(bytes[i]);
+    else
+      // Little Endian (niederwertiges Byte zuerst im Array)
+      for(int i=bytes.length-1; i >= 0; i--) {
+//      result = (result << 8) + bytes[i] + (bytes[i]<0 ? 256 : 0);
+        result = (result << 8) | getUnsignedByte(bytes[i]);
+      }
+    return result;
+  }
+
+  /**
+   * Konvertiert 4 Bytes eines Streams in einen <code>float</code>-Wert.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param input     Eingabe-Stream
+   * @exception java.io.IOException falls das Lesen aus dem Stream scheitert
+   * @exception java.io.EOFException falls nicht genug Bytes auf dem Stream liegen
+   */
+  public static float convertBytesToFloat(byte byteOrder, InputStream input) throws IOException {
+    byte[] bytes = new byte[4];
+    if ( input.read(bytes) < 4 )
+      throw new EOFException("Not enough Bytes available on InputStream");
+
+    return convertBytesToFloat(byteOrder,bytes);
+  }
+
+  /**
+   * Konvertiert 4 Bytes in einen <code>float</code>-Wert.<br>
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param bytes     8 Eingabe-Bytes
+   */
+  public static float convertBytesToFloat(byte byteOrder, byte[] bytes) {
+    if (bytes == null || bytes.length != 4)
+      throw new ArrayIndexOutOfBoundsException("Exactly 4 Byte needed for convert to float!!");
+
+    // Bytes wie einen Integer einlesen
+    int intValue = (int)convertBytesToLong(byteOrder, bytes);
+    // Bits in Float konvertieren
+    return Float.intBitsToFloat( intValue );
+  }
+
+  /**
+   * Konvertiert 8 Bytes eines Streams in einen <code>double</code>-Wert.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param input     Eingabe-Stream
+   * @exception java.io.IOException falls das Lesen aus dem Stream scheitert
+   * @exception java.io.EOFException falls nicht genug Bytes auf dem Stream liegen
+   */
+  public static double convertBytesToDouble(byte byteOrder, InputStream input) throws IOException {
+    byte[] bytes = new byte[8];
+    if ( input.read(bytes) < 8 )
+      throw new EOFException("Not enough Bytes available on InputStream");
+
+    return convertBytesToDouble(byteOrder,bytes);
+  }
+
+  /**
+   * Konvertiert 8 Bytes in einen <code>double</code>-Wert.<br>
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param bytes     8 Eingabe-Bytes
+   */
+  public static double convertBytesToDouble(byte byteOrder, byte[] bytes) {
+    if (bytes == null || bytes.length != 8)
+      throw new ArrayIndexOutOfBoundsException("Exactly 8 Byte needed for convert to double!!");
+
+    // Bytes wie einen Long einlesen
+    long longValue = convertBytesToLong(byteOrder, bytes);
+    // Bits in Double konvertieren
+    return Double.longBitsToDouble( longValue );
+  }
+
+  /**
+   * Konvertiert einen <code>long</code>-Wert in eine Reihe von Bytes.
+   * Fuer negative Werte wird das 2er-Komplement verwendet.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @param size      Groesse des Rueckgabe-Arrays; unbenoetigte Felder werden
+   *                  aufgefuellt (fuer negative Werte mit 1, sonst mit 0);<br>
+   *                  Bei <code>size=0</code> ist der Rueckgabe-Array genau
+   *                  so gross wie noetig.
+   * @exception java.lang.ArrayIndexOutOfBoundException falls der long-Wert
+   *            nicht in den Rueckgabe-Array passt.
+   */
+  public static byte[] convertLongToBytes(byte byteOrder, long value, int size) {
+    // 2er-Komplement fuer negative Zahlen:
+    // a) Verwende den positiven Wert
+    // b) Subtrahiere 1
+    // c) Bestimme die Bytes nach B+V (Betrag+VZ)
+    // d) invertiere alle Bits
+    if ( value < 0) {
+      long posValue = Math.abs(value) - 1;                        // a) & b)
+      byte[] bytes = convertLongToBytes(byteOrder,posValue,size); // c)
+      for (int i=0; i<bytes.length; i++) bytes[i] ^= -1;          // d)
+      return bytes;
+    }
+
+    // Anzahl an Bytes, die fuer die Zahl benoetigt werden (im 2er-Komplement)
+    // --> multiplikation mit 2 (Bitshift!), da ein Bit mehr fuer das
+    //     Vorzeichen benoetigt wird
+    int bytesNeeded = (int)(Math.log(value<<1)/Math.log(256) + 1);
+    if ( bytesNeeded > size )
+      throw new ArrayIndexOutOfBoundsException(bytesNeeded+" Bytes needed to store "+value+" in 2's complement!!");
+
+    // Wert in B+V ausrechenen
+    byte[] bytes = new byte[ size==0 ? bytesNeeded : size ];
+    int i=0; // Array-Zaehler
+    // erstmal in LittleEndian ausrechnen
+    for(int potenz=0; value>0; i++, potenz++) {
+//    bytes[i] = (byte)(value%256);
+//    value /= 256;
+      bytes[i] = (byte)(value & 255);
+      value >>= 8; // naechstes Byte
+    }
+
+    // Big Endian (hoechstwertiges Byte zuerst)
+    // --> Array-Reihenfolge vertauschen
+    if ( byteOrder == XDR ) {
+      for (i=0; i<bytes.length/2; i++){
+        // Dreieckstausch
+        byte help = bytes[i];
+        bytes[i] = bytes[bytes.length-i-1];
+        bytes[bytes.length-i-1] = help;
+      }
+    }
+
+    return bytes;
+  }
+
+  /**
+   * Konvertiert einen <code>long</code>-Wert in eine Reihe von Bytes.
+   * Fuer negative Werte wird das 2er-Komplement verwendet.
+   * Die Bytes werden allerdings nicht als <code>byte</code> zurueckgegeben,
+   * sondern als <code>int</code>, der einen <code>unsigned byte</code>
+   * simuliert.<br>
+   * Es werden also immer positive Werte zurueckgegeben!!
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @param size      Groesse des Rueckgabe-Arrays; unbenoetigte Felder werden
+   *                  aufgefuellt (fuer negative Werte mit 1, sonst mit 0);<br>
+   *                  Bei <code>size=0</code> ist der Rueckgabe-Array genau
+   *                  so gross wie noetig.
+   * @exception java.lang.ArrayIndexOutOfBoundException falls der long-Wert
+   *            nicht in den Rueckgabe-Array passt.
+   */
+  public static int[] convertLongToUnsignedBytes(byte byteOrder, long value, int size) {
+    return getUnsignedBytes( convertLongToBytes(byteOrder,value,size) );
+  }
+
+  /**
+   * Konvertiert einen <code>byte</code>-Wert (-128..127) in ein
+   * <i>unsigned</i> <code>byte</code> (0..255).
+   * Der Rueckgabetyp muss hierfuer ein <code>int</code> sein.
+   * @param b Wert zwischen -128 und 127
+   * @return <code>(int)b & 255</code>
+   */
+  public static int getUnsignedByte(byte b) {
+    return (int)b & 255;
+  }
+
+  /**
+   * Konvertiert einen Array von <code>byte</code>-Werten (-128..127) in einen
+   * Array von <i>unsigned</i> <code>bytes</code> (0..255).
+   * Der Rueckgabetyp muss hierfuer ein <code>int</code> sein.
+   * @param signedByte {@code byte}-Array
+   */
+  public static int[] getUnsignedBytes(byte signedByte[]) {
+    int[] unsignedByte = new int[ signedByte.length ];
+    for (int i=0; i<signedByte.length; i++)
+      unsignedByte[i] = getUnsignedByte( signedByte[i] );
+    return unsignedByte;
+  }
+
+  /**
+   * Konvertiert einen <code>float</code>-Wert in 4 Bytes.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @return byte[4]
+   */
+  public static byte[] convertFloatToBytes(byte byteOrder, float value) {
+    int intValue = Float.floatToIntBits(value);
+    return convertLongToBytes(byteOrder, intValue, 4);
+  }
+
+  /**
+   * Konvertiert einen <code>float</code>-Wert in 4 Bytes.
+   * Die Bytes werden allerdings nicht als <code>byte</code> zurueckgegeben,
+   * sondern als <code>int</code>, der einen <code>unsigned byte</code>
+   * simuliert.<br>
+   * Es werden also immer positive Werte zurueckgegeben!!
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @return byte[4]
+   */
+  public static int[] convertFloatToUnsignedBytes(byte byteOrder, float value) {
+    return getUnsignedBytes( convertFloatToBytes(byteOrder,value) );
+  }
+
+  /**
+   * Konvertiert einen <code>double</code>-Wert in 8 Bytes.
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @return byte[8]
+   */
+  public static byte[] convertDoubleToBytes(byte byteOrder, double value) {
+    long longValue = Double.doubleToLongBits(value);
+    return convertLongToBytes(byteOrder, longValue, 8);
+  }
+
+  /**
+   * Konvertiert einen <code>double</code>-Wert in 8 Bytes.
+   * Die Bytes werden allerdings nicht als <code>byte</code> zurueckgegeben,
+   * sondern als <code>int</code>, der einen <code>unsigned byte</code>
+   * simuliert.<br>
+   * Es werden also immer positive Werte zurueckgegeben!!
+   * @param byteOrder 0 = BigEndian = hoechstwertiges Byte zuerst,
+   *                  1 = LittleEndian = niederwertigstes Byte zuerst
+   * @param value     Eingabewert
+   * @return byte[8]
+   */
+  public static int[] convertDoubleToUnsignedBytes(byte byteOrder, double value) {
+    return getUnsignedBytes( convertDoubleToBytes(byteOrder,value) );
+  }
+
+}

Added: trunk/src/schmitzm/io/CombinedInputStream.java
===================================================================
--- trunk/src/schmitzm/io/CombinedInputStream.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/CombinedInputStream.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,143 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Diese Klasse stellt einen Eingabe-Stream dar, der Daten auf Basis
+ * mehrerer anderen <code>InputStream</code>-Instanzen liefert. Zunaechst
+ * werden Bytes aus dem ersten Stream geliefert, wenn dieser Stream keine
+ * Daten mehr liefern kann aus dem zweiten, usw.<br>
+ * Sobald ein Stream einmal komplett geleert wurde, liefert die
+ * <code>read()</code>-Methode nur noch Bytes aus einem der naechsten
+ * Streams. Auch wenn der "leere" Stream spaeter wieder Daten liefern koennte,
+ * werden diese nicht mehr beruecksichtigt, sondern einfach ignoriert.<br>
+ * Diese Klasse kann z.B. verwendet werden, um bereits aus einem InputStream
+ * gelesene Daten (z.B. einen Header) diesem wieder voranzustellen, um
+ * den InputStream "als Original" weiterzugeben.<br>
+ * <b><u>Beispiel</u>:</b><br>
+ * Ein Klasse liest Text-Repraesentationenen <u>unterschiedlicher</u> Typen
+ * aus einer Datei. Erst nachdem der Header eines jeden Objekts eingelesen
+ * wurde, kann entschieden werden, welche Typ-Klasse instanziiert werden muss.
+ * Um deren <code>initFromText(InputStream)</code>-Methode aufrufen zu koennen,
+ * wird aber wieder der komplette InputStream inklusive Header benoetigt.<br>
+ * Hierzu kann ein <code>CombinedInputStream</code>, zusammengesetzt aus einem
+ * neuen <code>ByteArrayInputStream</code> fuer den bereits eingelesenen
+ * Header und dem Eingabe-Stream der Datei, erzeugt werden, welcher der
+ * <code>initFromText(InputStream)</code>-Methode uebergeben wird.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class CombinedInputStream extends InputStream {
+  private InputStream[] input     = null;
+  private int           actStream = 0;
+  private int           avBytes   = 0;
+
+
+  /**
+   * Erzeugt einen neuen <code>CombinedInputStream</code>.
+   * @param input Eingabe-Streams aus denen sukzessive gelesen wird
+   * @exception java.io.IOException falls beim Ermitteln der insgesamt
+   *            zur Verfuegung stehenden Bytes ein Fehler auftritt
+   */
+  public CombinedInputStream(InputStream[] input) throws IOException{
+    this.input = input;
+    regenerateAvailableBytes();
+  }
+
+  /**
+   * Erzeugt einen neuen <code>CombinedInputStream</code>.
+   * @param size Anzahl an Eingabe-Streams auf die der
+   *        <code>CombinedInputStream</code> dimensioniert wird
+   */
+  public CombinedInputStream(int size) {
+    this.input = new InputStream[size];
+  }
+
+  /**
+   * Liefert die Anzahl an Streams aus der der <code>CombinedInputStream</code>
+   * zusammengesetzt wurde.
+   */
+  public int getStreamCount() {
+    return input.length;
+  }
+
+  /**
+   * Setzt einen Teil-Stream. Die Anzahl der insgesamt zur Verfuegung stehenden
+   * Bytes wird danach neu berechnet.
+   * @see #regenerateAvailableBytes()
+   */
+  public void setInputStream(int i, InputStream stream) throws IOException {
+    this.input[i] = stream;
+    regenerateAvailableBytes();
+  }
+
+  /**
+   * Errechnet die Anzahl der insgesamt zur Verfuegung stehenden Bytes neu.
+   * Dabei werden nur der aktuelle Stream und die darauf folgenden beruecksichtigt.
+   * Die bereits ausgelesenen Streams werden ignoriert, auch wenn in ihnen
+   * nun evt. wieder Bytes zur Verfuegung stehen.
+   */
+  public void regenerateAvailableBytes() throws IOException {
+    avBytes = 0;
+    for (int i=actStream; i<input.length; i++)
+      if ( input[i] != null ) avBytes += input[i].available();
+  }
+
+  /**
+   * Liefert die gesamt Anzahl von Bytes, die in den Eingabe-Streams
+   * noch zur Verfuegung stehen.<br>
+   * <b>Bemerkung:</b><br>
+   * Aus Effizienzgruenden wird die Anzahl der Verfuegung stehenden Bytes
+   * nicht bei jedem Aufruf neu errechnet, sondern nur beim Setzen eines
+   * neuen Streams (mittels <code>setInputStream(..)</code>). Danach wird
+   * nach bei jedem <code>read()</code>-Aufruf ein Zaehler herabgesetzt.<br>
+   * Aendert sich die Byte-Zahl eines Teil-Streams waehrend der Benutzung des
+   * <code>CombinedInputStream</code>, sollte die Methode
+   * <code>regenerateAvailableBytes()</code> aufgerufen werden, um die Anzahl
+   * der zur Verfuegung stehenden Bytes neu zu berechnen!!
+   * @exception java.io.IOException falls der Zugriff auf einen der beiden
+   *            Eingabe-Streams scheitert
+   * @see #regenerateAvailableBytes()
+   */
+  public int available() {
+    return avBytes;
+  }
+
+  /**
+   * Liefert ein (unsigned) Byte aus dem aktuellen Stream.
+   * Solange der erste Stream noch Daten liefern kann, werden seine Bytes
+   * zurueckgegeben. Danach die Bytes des zweiten Streams, usw.
+   * @return -1, falls keiner der Streams noch Daten liefern kann
+   * @exception java.io.IOException falls der Zugriff auf einen der
+   *            Eingabe-Streams scheitert
+   * @see #available()
+   */
+  public int read() throws IOException {
+    while( actStream < input.length && input[actStream].available() < 1 ) actStream++;
+    if ( actStream >= input.length ) return -1;
+    avBytes--;
+    return input[actStream].read();
+  }
+
+  /**
+   * Schliesst alle Eingabe-Streams.
+   * @exception java.io.IOException falls der Zugriff auf einen der
+   *            Eingabe-Streams scheitert
+   */
+  public void close() throws IOException {
+    for (int i=0; i<input.length; i++) this.input[i].close();
+  }
+
+}

Added: trunk/src/schmitzm/io/FileInputStream.java
===================================================================
--- trunk/src/schmitzm/io/FileInputStream.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/FileInputStream.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,64 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+
+/**
+ * Diese Klasse erweitert den {@link java.io.FileInputStream} in der Art, dass
+ * eine Referenz auf die zugrunde liegende Datei gespeichert wird. Diese kann
+ * ueber {@link #getFile()} abgerufen werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FileInputStream extends java.io.FileInputStream {
+  private File file = null;
+
+  /**
+   * Erzeugt einen neuen <code>FileInputStream</code>.<br>
+   * Siehe {@link java.io.FileInputStream#FileInputStream(File)}.
+   */
+  public FileInputStream(File file) throws FileNotFoundException {
+    super(file);
+    this.file = file;
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileInputStream</code>.<br>
+   * Siehe {@link java.io.FileInputStream#FileInputStream(String)}.
+   */
+  public FileInputStream(String name) throws FileNotFoundException {
+    super(name);
+    this.file = new File(name);
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileInputStream</code>.<br>
+   * Siehe {@link java.io.FileInputStream#FileInputStream(FileDescriptor)}.
+   */
+  public FileInputStream(FileDescriptor fdObj) throws FileNotFoundException {
+    super(fdObj);
+  }
+
+  /**
+   * Liefert die Datei, auf der der InputStream basiert.
+   * @return <code>null</code> falls der Stream auf einem <code>FileDescriptor</code>
+   *         basiert
+   * @return File
+   */
+  public File getFile() {
+    return file;
+  }
+
+}

Added: trunk/src/schmitzm/io/FileOutputStream.java
===================================================================
--- trunk/src/schmitzm/io/FileOutputStream.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/FileOutputStream.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,82 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+
+/**
+ * Diese Klasse erweitert den {@link java.io.FileOutputStream} in der Art, dass
+ * eine Referenz auf die zugrunde liegende Datei gespeichert wird. Diese kann
+ * ueber {@link #getFile()} abgerufen werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FileOutputStream extends java.io.FileOutputStream {
+  private File file = null;
+
+  /**
+   * Erzeugt einen neuen <code>FileOutputStream</code>.<br>
+   * Siehe {@link java.io.FileOutputStream#FileOutputStream(File)}.
+   */
+  public FileOutputStream(File file) throws FileNotFoundException {
+    super(file);
+    this.file = file;
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileOutputStream</code>.<br>
+   * Siehe {@link java.io.FileOutputStream#FileOutputStream(File,boolean)}.
+   */
+  public FileOutputStream(File file, boolean append) throws FileNotFoundException {
+    super(file,append);
+    this.file = file;
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileOutputStream</code>.<br>
+   * Siehe {@link java.io.FileOutputStream#FileOutputStream(FileDescriptor)}.
+   */
+  public FileOutputStream(FileDescriptor fdObj) throws FileNotFoundException {
+    super(fdObj);
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileOutputStream</code>.<br>
+   * Siehe {@link java.io.FileOutputStream#FileOutputStream(String)}.
+   */
+  public FileOutputStream(String name) throws FileNotFoundException {
+    super(name);
+    this.file = new File(name);
+  }
+
+  /**
+   * Erzeugt einen neuen <code>FileOutputStream</code>.<br>
+   * Siehe {@link java.io.FileOutputStream#FileOutputStream(String,boolean)}.
+   */
+  public FileOutputStream(String name, boolean append) throws FileNotFoundException {
+    super(name,append);
+    this.file = new File(name);
+  }
+
+
+  /**
+   * Liefert die Datei, auf der der OutputStream basiert.
+   * @return <code>null</code> falls der Stream auf einem <code>FileDescriptor</code>
+   *         basiert
+   * @return File
+   */
+  public File getFile() {
+    return file;
+  }
+}

Added: trunk/src/schmitzm/io/IOUtil.java
===================================================================
--- trunk/src/schmitzm/io/IOUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/IOUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,471 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse stellt statische Methoden fuer die Ein/Ausgabe zur Verfuegung.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ *
+ * @version 1.0
+ */
+public class IOUtil {
+
+  private static Logger LOGGER = Logger.getLogger( IOUtil.class.getName() );
+
+  /** {@link FileFilter}, der alle Dateien akzeptiert, aber keine Verzeichnisse. */
+  public static final FileFilter ALL_FILES_FILTER = createSimpleFileFilter("*", FilterMode.FILES_ONLY);
+  /** {@link FileFilter}, der alle Verzeichnisse akzeptiert, aber keine "normalen" Dateien. */
+  public static final FileFilter ALL_DIRS_FILTER = createSimpleFileFilter("*", FilterMode.DIR_ONLY);
+  /** {@link FileFilter}, der alle Dateien und Verzeichnisse akzeptiert. */
+  public static final FileFilter ALL_FILTER = createSimpleFileFilter("*", FilterMode.ALL);
+
+  /**
+   * Modi fuer Filter.
+   * @see IOUtil#createSimpleFileFilter(String, FilterMode)
+   * @see IOUtil#createRegExFileFilter(String, FilterMode)
+   * @author mojays
+   *
+   */
+  public static enum FilterMode {
+    /** Der Filter liefert nur Dateien, aber keine Verzeichnisse. */
+    FILES_ONLY,
+    /** Der Filter liefert nur Verzeichnisse, aber keine Dateien. */
+    DIR_ONLY,
+    /** Der Filter liefert Dateien und Verzeichnisse. */
+    ALL
+  }
+
+  /**
+   * Prueft, ob ein {@link File} einen angegebenen {@link FilterMode}
+   * erfuellt.
+   * @param file eine Datei oder ein Verzeichnis
+   * @param mode Filter-Modus (wenn {@code null} wird {@link FilterMode#ALL}
+   *             angenommen
+   * @return {@code false} wenn die angegebenen Datei {@code null} ist
+   */
+  public static boolean fileFitsFilterMode(File file, FilterMode mode) {
+    if ( mode == null )
+      mode = FilterMode.ALL;
+    final boolean acceptFiles = FilterMode.ALL.equals(mode) || FilterMode.FILES_ONLY.equals(mode);
+    final boolean acceptDirs  = FilterMode.ALL.equals(mode) || FilterMode.DIR_ONLY.equals(mode);
+
+    return file != null && ( file.isDirectory() && acceptDirs || acceptFiles );
+  }
+
+
+  /**
+   * Liefert den Index des Dateinames, an der die Dateinamen-Erweiterung
+   * (inkl. Punkt) beginnt.
+   * @param file Datei
+   */
+  public static int getFileExtIdx(File file) {
+    String fileName = file.getAbsolutePath();
+
+    int lastPointIdx = fileName.lastIndexOf('.');
+    int lastSepIdx   = fileName.lastIndexOf(File.separatorChar);
+    if ( lastPointIdx < lastSepIdx )
+      return -1;
+    return lastPointIdx;
+  }
+
+  /**
+   * Liefert die Dateinamen-Erweiterung (z.B. ".exe") einer Datei.
+   * @param file Datei
+   * @return Leerstring, falls die Datei keine Erweiterung hat
+   */
+  public static String getFileExt(File file) {
+    return getFileExt(file, true);
+  }
+
+  /**
+   * Liefert die Dateinamen-Erweiterung (z.B. ".exe") einer Datei.
+   * @param file Datei
+   * @param withDot wenn {@code false} wird die Dateinamen-Erweiterung ohne
+   *                den fuehrenden Punkt zurueckgegeben (z.B. "exe" statt ".exe")
+   * @return Leerstring, falls die Datei keine Erweiterung hat
+   */
+  public static String getFileExt(File file, boolean withDot) {
+    int extIdx = getFileExtIdx(file);
+    if ( extIdx < 0 )
+      return "";
+    if ( !withDot )
+      extIdx++;
+    return file.getAbsolutePath().substring(extIdx);
+  }
+
+  /**
+   * Haengt an einen Dateinamen-Erweiterung eine Erweiterung an, sofern diese
+   * noch nicht angehaengt ist.
+   * @param file Datei
+   * @param newExt neue Dateinamen-Erweiterung (mit oder ohne ".")
+   * @return neues {@link File}-Objekt
+   */
+  public static File appendFileExt(File file, String newExt) {
+    if (!newExt.startsWith("."))
+      newExt = "."+newExt;
+    String oldExt = getFileExt(file,true);
+    if ( !oldExt.equalsIgnoreCase(newExt) )
+      file = new File( file.getAbsolutePath() + newExt );
+    return file;
+  }
+
+  /**
+   * Aendert die Dateinamen-Erweiterung (z.B. ".exe") einer Datei.
+   * @param file Datei
+   * @param newExt neue Dateinamen-Erweiterung (ohne ".")
+   * @return neues {@link File}-Objekt
+   */
+  public static File changeFileExt(File file, String newExt) {
+    String baseName = getBaseFileName(file);
+    return new File( baseName+"."+newExt );
+  }
+
+
+	/**
+	 * Changes the ending (e.g. ".sld") of a {@link URL}
+	 *
+	 * @param url {@link URL} like <code>file:/sds/a.bmp</code>
+	 * @param postfix New file extension for the {@link URL} without <code>.</code>
+	 *
+	 * @return A new {@link URL} with new extension.
+	 *
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static URL changeUrlExt(final URL url, final String postfix) throws IllegalArgumentException{
+		String a = url.toExternalForm();
+                final int lastDotPos = a.lastIndexOf('.');
+                if ( lastDotPos >= 0 )
+                  a = a.substring(0, lastDotPos);
+                a = a + "." + postfix;
+		try {
+			return new URL(a);
+		} catch (final MalformedURLException e) {
+			throw new IllegalArgumentException("can't create a new URL for "+url+" with new extension "+postfix, e);
+		}
+	}
+
+
+  /**
+   * Die Funktion soll der Funktion File.getParent() fuer URLs entsprechen. Die URL wird in einen Sting konvertiert und dann (kuerzer) neu
+   * zusammengesetzt. Falls eine Verkuerzung nicht moeglich ist, wird eine {@link MalformedURLException} geworfen
+   *
+   * @param url
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+	public static URL getParentURL(URL url) throws MalformedURLException{
+		String a = url.toExternalForm();
+		a = a.substring(0, a.lastIndexOf('/'));
+		return new URL(a);
+	}
+
+	/**
+	 * Erweitert eine {@link URL}. Bei der uebergebenen URL muss es sich um ein Verzeichnis handeln, sonst
+	 * wird eine {@link MalformedURLException} geschmissen
+	 * @param base
+	 * @param string
+	 *
+	 * @throws MalformedURLException
+	 */
+	public static URL extendURL(URL base, String string) throws MalformedURLException {
+		String a = base.toExternalForm();
+		if (!a.endsWith("/")) a+= "/";
+		a+= string;
+		return new URL(a);
+	}
+
+
+
+  /**
+   * Liefert den Namen ohne Erweiterung (z.B. ohne ".exe") einer Datei.
+   * @param file Datei
+   */
+  public static String getBaseFileName(File file) {
+    int extIdx = getFileExtIdx(file);
+    if ( extIdx < 0 )
+      return file.getAbsolutePath();
+    return file.getAbsolutePath().substring(0,extIdx);
+  }
+
+  /**
+   * Liefert das oberste Verzeichnis zu einer Datei (Haupt-Verzeichnis).
+   * @param file Datei
+   */
+  public static File getRootDir(File file) {
+    File temp = new File(file.getAbsolutePath());
+    while( temp.getParentFile() != null )
+      temp = temp.getParentFile();
+    return temp;
+  }
+
+  /**
+   * Erzeugt ein neues File-Objekt relativ zu einer Pfad-Angabe.<br>
+   * Im Normalfall setzt sich der neue Datei-Pfad aus <code>baseDir</code> und
+   * <code>path</code> zusammen, also <i>newFile = <code>baseDir</code> + <code>path<code></i>.<br>
+   * <b>Ausnahme 1:</b><br>
+   * Beginnt <code>path</code> mit einem Slash (bzw. Backslash bei Windows), ist
+   * <i>newFile = ROOT(<code>baseDir</code>) + <code>path<code></i>,
+   * wobei <i>ROOT(<code>baseDir</code>)</i></center> das Hauptverzeichnis
+   * (Laufwerk) des Basis-Verzeichnisses bezeichnet.<br>
+   * <b>Ausnahme 2:</b><br>
+   * Stellt <code>path</code> bereits eine absolute Pfadangabe dar (inkl.
+   * Laufwerksbezeichnung), ist <i>newFile = <code>path<code></i>.
+   * @param baseDir Basis-Verzeichnis
+   * @param path Pfad/Datei-Angabe relativ zu <code>baseDir</code>
+   * @see #getRootDir(File)
+   */
+  public static File createRelativeFile(File baseDir, String path) {
+    File file = new File( path );
+
+    // In relPath steht absolute Pfadangabe -> BaseDir ignorieren
+    if ( path.equalsIgnoreCase( file.getAbsolutePath() ) )
+      return file;
+
+    // Source-Tag beginnt mit '\' -> Source-Angabe relativ zum Haupt-Verzeichnis
+    if ( path.startsWith( File.separator ) )
+      return new File(IOUtil.getRootDir(baseDir),path );
+
+    // Sonst: BaseDir als Basis fuer Source verwenden
+    return new File(baseDir, path);
+  }
+
+  /**
+   * Versucht einen Eingabe-Stream zu schliessen.<br>
+   * Dabei werden bei Misserfolg keine Exceptions geworfen!
+   * SK: bei <code>null</code> wird true zurueckgegeben
+   *
+   * @param in zu schliessender Stream
+   * @return <code>false</code> falls das Schliessen nicht erfolgreich war
+   */
+  public static boolean closeInputStream(InputStream in) {
+	if (in == null) return true;
+    try {
+      in.close();
+    } catch (Exception err) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Versucht alle gegebenen Eingabe-Streams zu schliessen.<br>
+   * Dabei werden bei Misserfolg keine Exceptions geworfen!
+   * @param in zu schliessende Streams
+   * @return <code>true</code> falls das Schliessen aller Streams erfolgreich war
+   */
+  public static boolean closeInputStream(InputStream[] in) {
+    boolean result = true;
+    for (int i=0; i<in.length; i++)
+      result &= closeInputStream(in[i]);
+    return result;
+  }
+
+  /**
+   * Versucht einen Ausgabe-Stream zu schliessen. Zuvor wird ein
+   * {@link OutputStream#flush() flush()} auf den Stream getaetigt.<br>
+   * Bei Misserfolg werden keine Exceptions geworfen!
+   * SK: bei <code>null</code> wird true zurueckgegeben   *
+   * @param out zu schliessender Stream
+   * @return <code>false</code> falls das Schliessen nicht erfolgreich war
+   */
+  public static boolean closeOutputStream(OutputStream out) {
+	  if (out == null) return true;
+    try {
+      out.flush();
+      out.close();
+    } catch (Exception err) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Prueft, ob eine (Datei-)Zeile mit einem Kommentarkennzeichen beginnt
+   * und deshalb ignoriert werden muss. Kommentar-Kennzeichen sind
+   * <code>//</code>, <code>#</code> und <code>|</code>.
+   * @param line Eingabe-Zeile
+   */
+  public static boolean isCommentLine(String line) {
+    return line.startsWith("//") ||
+           line.startsWith("#")  ||
+           line.startsWith("|");
+  }
+
+  /**
+   * Schreibt einen Eingabe-Stream in eine Datei.
+   * @param file      Dateipfad fuer die Zieldatei
+   * @exception java.io.IOException falls das Lesen aus dem Stream oder
+   *            das Schreiben in die Ausgabedatei scheitert
+   */
+  public static void writeStreamToFile(InputStream input, String file) throws IOException {
+    OutputStream output = new FileOutputStream(file);
+    while (input.available() > 0) output.write(input.read());
+    output.flush();
+    output.close();
+  }
+
+  /**
+   * Erstellt einen Filter fuer einen regulaeren Ausdruck.
+   * @param filterStr definiert den Filter
+   */
+  public static FileFilter createRegExFileFilter(final String regEx, final FilterMode mode) {
+    return new FileFilter() {
+      public boolean accept(File file) {
+        return fileFitsFilterMode(file,mode) && file.getName().matches(regEx);
+      }
+    };
+  }
+
+
+  /**
+   * Erstellt einen Filter fuer einen Filter-String. Dieser kann
+   * als Wildcard das Zeichen "*" enthalten. Alle anderen Zeichen
+   * werden direkt als Filter verwendet.
+   * @param filterStr definiert den Filter
+   */
+  public static FileFilter createSimpleFileFilter(String filterStr, final FilterMode mode) {
+    // Gross/Klein-Schreibung ignorieren
+    filterStr = filterStr.toLowerCase();
+
+    //##### Filter definiert den "inneren" Teil eines Dateinames #####
+    if ( filterStr.startsWith("*") && filterStr.endsWith("*") ) {
+      // Filter besteht nur aus "*" --> alle Dateien akzeptieren
+      if ( filterStr.length() <= 2 )
+        return new FileFilter() {
+          public boolean accept(File file) {
+            return fileFitsFilterMode(file,mode);
+          }
+        };
+      // "inneren" Teil des Filters extrahieren
+      final String condStr = filterStr.substring(1,filterStr.length()-1);
+      return new FileFilter() {
+        public boolean accept(File file) {
+          return fileFitsFilterMode(file,mode) && file.getName().toLowerCase().contains(condStr) ;
+        }
+      };
+    }
+
+    //##### Filter definiert den Anfang eines Dateinames #####
+    if ( filterStr.endsWith("*") ) {
+      // Anfang des Filters extrahieren
+      final String condStr = filterStr.substring(0,filterStr.length()-1);
+      return new FileFilter() {
+        public boolean accept(File file) {
+          return fileFitsFilterMode(file,mode) && file.getName().toLowerCase().startsWith(condStr) ;
+        }
+      };
+    }
+    //##### Filter definiert das Ende eines Dateinames #####
+    if ( filterStr.startsWith("*") ) {
+      // Ende des Filters extrahieren
+      final String condStr = filterStr.substring(1);
+      return new FileFilter() {
+        public boolean accept(File file) {
+          return fileFitsFilterMode(file,mode) && file.getName().toLowerCase().endsWith(condStr) ;
+        }
+      };
+    }
+    //##### Filter definiert den genauen Dateinamen #####
+    final String condStr = filterStr;
+    return new FileFilter() {
+      public boolean accept(File file) {
+        return fileFitsFilterMode(file,mode) && file.getName().equalsIgnoreCase(condStr) ;
+      }
+    };
+  }
+
+  /**
+   * Loescht Dateien oder Verzeichnisse in einem Verzeichnis. Das Verzeichnis selbst
+   * wird dabei NICHT geloescht, auch wenn es am Ende leer ist!
+   * @param dir       Verzeichnis in dem Dateien/Verzeichnisse geloescht werden
+   * @param filter    bestimmt, welche Dateien/Verzeichnisse geloescht werden
+   * @param recursive wenn {@code true} werden auch alle Dateien/Verzeichnisse
+   *                  in Unterverzeichnissen geloescht, die dem Filter entsprechen
+   * @param showFiles wenn {@code true} werden die geloeschten Dateien auf der
+   *                  Console ausgegeben
+   * @return Anzahl geloeschter Dateien und Verzeichnisse
+   */
+  public static int deleteFiles(File dir, FileFilter filter, boolean recursive, boolean showFiles) {
+    if ( !dir.isDirectory() )
+      return 0;
+
+    int delCount = 0;
+    // Dateien im aktuellen Verzeichnis loeschen
+    File[] files = filter != null ? dir.listFiles( filter ) : dir.listFiles();
+    for (File file : files) {
+      // Verzeichnis zunaechst leeren
+      if ( file.isDirectory() )
+        delCount += deleteFiles(file, null, true, showFiles);
+      boolean success = file.delete();
+      if ( success )
+        delCount++;
+      if ( showFiles ) {
+        StringBuffer message = new StringBuffer(success ? "Deleted: " : "Error: ");
+        try {
+          message.append( file.getCanonicalPath() );
+        } catch (Exception err) {
+          message.append( file.getAbsolutePath() );
+        }
+        System.out.println(message);
+      }
+    }
+    // Wenn rekusiv geloescht werden soll, alle Verzeichnisse im Verzeichnis
+    // durchsuchen
+    if ( recursive ) {
+      File[] directories = dir.listFiles(ALL_DIRS_FILTER);
+      for (File directory : directories)
+        delCount += deleteFiles(directory, filter, recursive, showFiles);
+    }
+    return delCount;
+  }
+
+  /**
+   * Fuehrt verschiedene Funktionen aus.
+   * <ul>
+   *   <li>{@code DELDIR name}<br>
+   *       Loescht ein Verzeichnis aus allen Unterverzeichnissen.</li>
+   * </ul>
+   * @param arg
+   */
+  public static void main(final String[] arg) {
+    if ( arg.length == 0 ) {
+      System.err.println("Missing arguments.");
+      return;
+    }
+
+    String func = arg[0].toUpperCase();
+    if ( func.equals("DELDIR") ) {
+      if ( arg.length < 2 ) {
+        System.err.println("Missing arguments for DELDIR.");
+      }
+      File currDir        = new File(".");
+      FileFilter filter   = createSimpleFileFilter(arg[1],FilterMode.DIR_ONLY);
+      int        delCount = deleteFiles(currDir, filter, true, true);
+      System.out.println(delCount+ " files deleted.");
+    }
+    else {
+      System.err.println("Unknown function: "+func);
+    }
+  }
+}

Added: trunk/src/schmitzm/io/InputBuffer.java
===================================================================
--- trunk/src/schmitzm/io/InputBuffer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/InputBuffer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,182 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Diese Klasse stellt einen InputStream dar, der sukzessive Bytes
+ * aus einem Buffer liefert. Dieser Buffer kann permanent wieder
+ * (auf)gefuellt werden. Dazu muss er nicht erst komplett geleert
+ * worden sein!<br>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class InputBuffer extends InputStream {
+  protected byte[]  buffer      = null;
+  protected int     readPos     = 0;
+  protected int     writePos    = 0;
+  protected boolean bufferFull  = false;
+
+  /**
+   * Erzeugt einen neuen <code>InputBuffer</code>.
+   * @param byteSize Groesse des Buffers in Byte
+   */
+  public InputBuffer(int byteSize) {
+    buffer = new byte[byteSize];
+  }
+
+  public int getBufferSize() {
+    return buffer.length;
+  }
+
+  /**
+   * Erhoeht die Zeiger-Position im Buffer. Ist das Ende des Buffers
+   * erreicht, springt der Zeiger wieder auf die Position 0.
+   */
+  protected int incPosition(int pos) {
+    return incPosition(pos,1);
+  }
+
+  /**
+   * Erhoeht die Zeiger-Position im Buffer. Ist das Ende des Buffers
+   * erreicht, springt der Zeiger wieder nach vorne.
+   * @param pos aktuelle Zeiger-Position
+   * @param hop Anzahl an Positionen, die vor gerueckt werden sollen
+   */
+  protected int incPosition(int pos, int hop) {
+    pos += hop;
+    return pos % getBufferSize();
+  }
+
+  /**
+   * Schreibt ein Byte in den Buffer.
+   * @param b ein Byte
+   * @exception java.nio.BufferOverflowException falls der Buffer bereits voll ist
+   */
+  public void write(byte b) {
+    if ( availableSpace() == 0 )
+      throw new java.nio.BufferOverflowException();
+    buffer[writePos] = b;
+    // Schreib-Zeiger erhoehen
+    writePos = incPosition(writePos);
+    // wenn Schreib-Zeiger nun auf der aktuellen Lese-Position
+    // steht, ist der Buffer voll
+    bufferFull = ( writePos == readPos );
+  }
+
+  /**
+   * Schreibt eine Reihe von Bytes in den Buffer.
+   * @param bytes Menge von zu schreibenen Bytes
+   * @exception java.nio.BufferOverflowException falls der Buffer bereits voll ist
+   */
+  public void write(byte[] bytes) {
+    for (int i=0; i<bytes.length; i++)
+      write(bytes[i]);
+  }
+
+  /**
+   * Schreibt eine Reihe von Bytes in den Buffer. Diese Methode blockiert,
+   * bis der InputStream die geforderte Anzahl an Bytes liefern kann.
+   * @param input  Stream aus dem die Bytes gelesen werden
+   * @param len    Anzahl zu lesender Bytes
+   * @exception java.nio.BufferOverflowException falls nicht genuegend freier
+   *            Platz im Buffer ist
+   * @exception java.io.IOException falls das Lesen aus dem Eingabe-Stream
+   *            scheitert
+   */
+  public void write(InputStream input, int len) throws IOException {
+    if ( availableSpace() < len )
+      throw new java.nio.BufferOverflowException();
+
+    // Ist zwischen dem Schreib-Cursor und dem Buffer-Ende noch genug
+    // Platz, koennen alle Bytes AN EINEM STUECK gespeichert werden
+    if ( writePos + len-1 < getBufferSize() ) {
+      input.read(buffer,writePos,len);
+      incPosition(writePos,len);
+      bufferFull = ( writePos == readPos );
+    }
+    else {
+      // Ansonsten wird erst bis zum Buffer-Ende geschrieben
+      int len1 = getBufferSize()-writePos;
+      write(input, len1);
+      // Nachdem dann der Cursor wieder am Anfang des Buffers
+      // steht, wird der Rest an einem Stueck geschrieben
+      write(input, len - len1);
+    }
+  }
+
+  /**
+   * Liefert die Anzahl an Bytes, die (noch) in den Buffer
+   * geschrieben werden koennen.
+   */
+  public int availableSpace() {
+    return Math.abs( getBufferSize() - available() );
+  }
+
+  /**
+   * Liesst das naechste Byte aus dem Buffer. Der zurueckgelieferte
+   * Wert liegt zwischen 0 und 255.
+   * @return -1 falls der Buffer leer ist
+   */
+  public int read() throws IOException {
+    if ( available() == 0 ) return -1;
+    int b = BinaryUtil.getUnsignedByte( buffer[readPos] );
+    readPos = incPosition(readPos);
+    // Buffer hat auf jeden Fall jetzt wieder einen freien Platz
+    bufferFull = false;
+    return b;
+  }
+
+  /**
+   * Liefert die Anzahl an Bytes, die (noch) aus dem Buffer
+   * gelesen werden koennen.
+   */
+  public int available() {
+    // wenn der Buffer voll ist, ist writePos = readPos
+    // --> available() wuerde jedoch 0 liefern
+    if ( bufferFull ) return getBufferSize();
+    return ( writePos - readPos + getBufferSize() ) % getBufferSize();
+  }
+
+  /**
+   * Entfernt eine Anzahl von Bytes aus dem Buffer. Liegen nicht mehr
+   * genug Bytes im Buffer, wird dies ignoriert und der Buffer einfach
+   * komplett geleert.
+   * @param n Anzahl zu entfernender Bytes (muss ein <code>int</code> sein)
+   * @return Anzahl an Bytes, die aus dem Buffer entfernt wurden
+   */
+  public long skip(long n) {
+    int skipped = Math.min((int)n,available());
+    readPos = incPosition(readPos,skipped);
+    return skipped;
+  }
+
+  /**
+   * Leert den Buffer.
+   * @return Anzahl an Bytes, die noch im Buffer waren
+   */
+  public long clear() {
+    return skip( this.available() );
+  }
+
+  /**
+   * Liefert den aktuellen Inhalt des Buffers als <code>bytes</code>-Array.
+   */
+  public byte[] getContent() {
+    byte[] buf = new byte[available()];
+    for (int i=0; i<buf.length; i++)
+      buf[i] = buffer[ (readPos+i)%buffer.length ];
+    return buf;
+  }
+}

Added: trunk/src/schmitzm/io/dyntxt/DynamicBlock.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicBlock.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicBlock.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,92 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt einen allgemeinen Block von dynamischen Elementen dar.
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class DynamicBlock extends DynamicElement {
+  public Vector elements = new Vector();
+
+  /**
+   * Erzeugt einen neuen Block.
+   * @param id identifieziert den Block gegenueber dem <code>DynamicInputProvider</code>
+   */
+  public DynamicBlock(String id) {
+    super(id);
+  }
+
+  /**
+   * Erzeugt einen neuen Block.
+   * @param id identifieziert den Block gegenueber dem <code>DynamicInputProvider</code>
+   * @param father anderes dynamisches Element, welches den Block beinhaltet
+   */
+  public DynamicBlock(String id, DynamicElement father) {
+    super(id, father);
+  }
+
+  /**
+   * Fuegt dem Block ein dynamisches Element hinzu.
+   * @param idx     Position, an der das Element eingefuegt wird.
+   * @param element einzufuegendes dynamisches Element
+   * @exception java.lang.ArrayIndexOutOfBoundsException falls
+   *            <code>idx < 0 || idx > size()</code>
+   */
+  public void add(int idx, DynamicElement element) {
+    elements.add(idx,element);
+  }
+
+  /**
+   * Fuegt ein dynamisches Element am Ende des Blocks an.
+   * @param element einzufuegendes dynamisches Element
+   */
+  public void add(DynamicElement element) {
+    elements.add(element);
+  }
+
+  /**
+   * Liefert die aktuelle Anzahl an Elementen im Block.
+   */
+  public int size() {
+    return elements.size();
+  }
+
+  /**
+   * Liefert die aktuelle Anzahl an Elementen im Block.
+   */
+  public boolean isEmpty() {
+    return size()==0;
+  }
+
+  /**
+   * Loescht alle Elemente des Blocks.
+   */
+  public void clear() {
+    elements.clear();
+  }
+
+  /**
+   * Fuehrt nacheinander alle dynamischen Elemente des Blocks der Reihe nach aus.
+   * @param inputProvider verarbeitet die Loops und Fields im Block
+   * @param output        hier wird die Ausgabe reingeschrieben
+   * @exception java.io.IOException falls das Schreiben in den Stream fehlschlaegt
+   */
+  public void performElement(DynamicInputProvider inputProvider, OutputStream output) throws IOException {
+    for (int i=0; i<elements.size(); i++)
+      ((DynamicElement)elements.elementAt(i)).performElement(inputProvider, output);
+  }
+}
\ No newline at end of file

Added: trunk/src/schmitzm/io/dyntxt/DynamicElement.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicElement.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicElement.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,91 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+
+/**
+ * Diese Klasse repraesentiert ein allgemeines dynamisches Text-Element.
+ * Jedes Element kann durch eine alphanumerische ID identifiziert werden (muss
+ * nicht eindeutig sein!!).<br>
+ * Bis auf das Wurzelelement sollten allen Text-Elementen ein Vater-Element
+ * zugeordnet sein, damit ein einfaches abarbeiten des Wurzel-Elements automatisch
+ * die Abarbeitung aller untergeordneten Elemente nach sich zieht.
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public abstract class DynamicElement {
+  private DynamicElement father = null;
+  private String         id     = "";
+
+  /**
+   * Erzeugt ein neues dynamisches Root-Element.
+   * @param id identifieziert den Block gegenueber dem <code>DynamicInputProvider</code>
+   */
+  public DynamicElement(String id) {
+    this(id,null);
+  }
+
+  /**
+   * Erzeugt ein neues dynamisches Text-Element.
+   * @param id identifieziert den Block gegenueber dem <code>DynamicInputProvider</code>
+   * @param father anderes dynamisches Element, welches das Element beinhaltet
+   */
+  public DynamicElement(String id, DynamicElement father) {
+    this.father = father;
+    this.id     = id;
+  }
+
+  /**
+   * Checkt, ob das Element ein Vater-Element hat (<code>false</code>)
+   * oder nicht.
+   */
+  public boolean isRoot() {
+    return getFather()==null;
+  }
+
+  /**
+   * Liefert das uebergeordnete dynamische Element.
+   */
+  public DynamicElement getFather() {
+    return father;
+  }
+
+  /**
+   * Liefert die naechste uebergeordnete Element.
+   * @param cls Klasse der das Element entsprechen soll (muss eine
+   * @return <code>null</code>, falls dem Element keine Element der angegebenen
+   *         Klasse uebergeordnet ist
+   */
+  public DynamicElement getFather(Class cls) {
+    if ( getFather() == null ) return null;
+    if ( cls.isInstance(getFather()) ) return getFather();
+    return getFather().getFather(cls);
+  }
+
+
+  /**
+   * Liefert die ID des dynamischen Elements.
+   */
+  public String getID() {
+    return id;
+  }
+
+  /**
+   * Fuehrt das dynamische Element (und alle etwaigen darin enthaltenen Elemente)
+   * aus.
+   * @param inputProvider verarbeitet die Loops und Fields
+   * @param output        hier wird die Ausgabe reingeschrieben
+   * @exception java.io.IOException falls das Schreiben in den Stream fehlschlaegt
+   */
+  public abstract void performElement(DynamicInputProvider inputProvider, OutputStream output) throws IOException;
+}

Added: trunk/src/schmitzm/io/dyntxt/DynamicField.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicField.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicField.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,52 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+
+/**
+ * Diese Klasse stellt ein dynamisches Feld dar. Der Feld-Inhalt wird durch
+ * <code>DynamicInputProvider.performField(..)</code> bestimmt.
+ * @see schmitzm.io.dyntxt.DynamicInputProvider
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class DynamicField extends DynamicElement {
+  /**
+   * Erzeugt ein neues Feld-Element.
+   * @param id identifieziert das Feld gegenueber dem <code>DynamicInputProvider</code>
+   */
+  public DynamicField(String id) {
+    super(id);
+  }
+
+  /**
+   * Erzeugt ein neues Feld-Element.
+   * @param id identifieziert das Feld gegenueber dem <code>DynamicInputProvider</code>
+   * @param father anderes dynamisches Element, welches das Feld beinhaltet
+   */
+  public DynamicField(String id, DynamicElement father) {
+    super(id,father);
+  }
+
+  /**
+   * Schreibt das Feld in einen Ausgabe-Stream. Der Feld-Inhalt wird durch
+   * <code>DynamicInputProvider.performField(..)</code> bestimmt.
+   * @param inputProvider verarbeitet die Fields
+   * @param output        hier wird die Ausgabe reingeschrieben
+   * @exception java.io.IOException falls das Schreiben in den Stream fehlschlaegt
+   * @see schmitzm.io.dyntxt.DynamicInputProvider#performField(DynamicField)
+   */
+  public void performElement(DynamicInputProvider inputProvider, OutputStream output) throws IOException {
+    output.write( inputProvider.performField(this).getBytes() );
+  }
+}

Added: trunk/src/schmitzm/io/dyntxt/DynamicInputProvider.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicInputProvider.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicInputProvider.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,51 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+/**
+ * Dieses Interface definiert eine Klasse, welche die dynamischen
+ * Eingaben fuer einen dynamischen Text bereitstellt.<br>
+ * Hierzu zaehlen:
+ * <ol>
+ * <li>Das Beenden von Schleifen</li>
+ * <li>Das Fuellen von dynmischen Eingabe-Feldern</li>
+ * </ol>
+ * @see schmitzm.io.dyntxt.DynamicTextGenerator
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public interface DynamicInputProvider {
+  /**
+   * Wird <b>vor</b> jedem Schleifen-Durchlaufs aufgerufen. In dieser Methode
+   * kann der InputProvider die Daten fuer die in der Schleife referenzierten
+   * Felder ermitteln.<br>
+   * Ueber <code>DynamicLoop.getActualLoop()</code> kann der aktuelle
+   * Schleifenzaehler ermittelt werden (0 vor dem ersten Durchlauf).
+   * Soll ein weiterer Schleifendurchlauf ausgefuert werden, muss die Methode
+   * <code>true</code> liefern, andernfalls <code>false</code>.
+   * @param loop die auszufuehrende Schleife (kann ueber <code>getID()</code>
+   *        identifiziert werden
+
+   */
+  public boolean performLoop(DynamicLoop loop);
+
+  /**
+   * Wird fuer jedes Eingabefeld aufgerufen und liefert den Wert, der im dynamischen
+   * Text fuer das Feld eingesetzt werden soll.<br>
+   * Die aktuelle Schleife, in der sich das Feld befindet (und somit deren
+   * Durchlauf), kann ueber <code>field.getFather( DynamicLoop.class )</code>
+   * ermittelt werden.
+   * @param field das zu fuellende Feld (kann ueber <code>getID()</code>
+   *        identifiziert werden
+   */
+  public String performField(DynamicField field);
+}

Added: trunk/src/schmitzm/io/dyntxt/DynamicLoop.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicLoop.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicLoop.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,66 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+
+/**
+ * Diese Klasse stellt eine Schleife dar, welche eine Reihe von Elementen
+ * beihaltet. Wie lange die Schleife ausgefuehrt wird, wird durch
+ * <code>DynamicInputProvider.performLoop(..)</code> bestimmt.
+ * @see schmitzm.io.dyntxt.DynamicInputProvider
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class DynamicLoop extends DynamicBlock {
+  private int loopNo = 0;
+
+  /**
+   * Erzeugt ein neues Schleifen-Element.
+   * @param id identifieziert die Schleife gegenueber dem <code>DynamicInputProvider</code>
+   */
+  public DynamicLoop(String id) {
+    super(id);
+  }
+
+  /**
+   * Erzeugt ein neues Schleifen-Element.
+   * @param id identifieziert die Schleife gegenueber dem <code>DynamicInputProvider</code>
+   * @param father anderes dynamisches Element, welches die Schleife beinhaltet
+   */
+  public DynamicLoop(String id, DynamicElement father) {
+    super(id,father);
+  }
+
+  /**
+   * Liefert den aktuellen Schleifen-Zaehler (beginnend bei 0).
+   * Dieser wird zu Beginn der Ausfuehrung (<code>performElement(..)</code>)
+   * auf 0 zurueckgesetzt.
+   * @see #performElement(DynamicInputProvider,OutputStream)
+   */
+  public int getActualLoop() {
+    return this.loopNo;
+  }
+
+  /**
+   * Fuehrt solange alle dynamischen Elemente des Blocks aus, bis der
+   * <code>DynamicInputProvider</code> die Schleife beendet.
+   * @param inputProvider verarbeitet die Loops und Fields im Loop-Block
+   * @param output        hier wird die Ausgabe reingeschrieben
+   * @exception java.io.IOException falls das Schreiben in den Stream fehlschlaegt
+   * @see schmitzm.io.dyntxt.DynamicInputProvider#performLoop(DynamicLoop)
+   */
+  public void performElement(DynamicInputProvider inputProvider, OutputStream output) throws IOException {
+    for (this.loopNo=0; inputProvider.performLoop(this); loopNo++)
+      super.performElement(inputProvider,output);
+  }
+}
\ No newline at end of file

Added: trunk/src/schmitzm/io/dyntxt/DynamicTextGenerator.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/DynamicTextGenerator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/DynamicTextGenerator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,202 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+import java.util.regex.*;
+
+/**
+ * Diese Klasse stellt einen dynamischen Text dar. Dieser wird durch ein
+ * Text-Geruest (Template) definiert, in dem durch spezielle Tags Schleifen
+ * und Felder eingebaut sind:
+ * <ul>
+ * <li><b><code>LOOP(<i>LoopID</i>)</code></b><br>
+ *     Definiert den Beginn einer Schleife.</li>
+ * <li><b><code>ENDLOOP()</code></b><br>
+ *     Definiert das Ende einer Schleife.</li>
+ * <li><b><code>FIELD(<i>FieldID</i>)</code></b><br>
+ *     Definiert ein Eingabe-Feld.</li>
+ * </ul>
+ * <br>
+ * Um die Tags von normalem Text zu unterscheiden, werden sie zusaetzlich von
+ * einer speziellen Zeichenfolge (TAG_INTRO) eingeleitet. Standardmaessig
+ * wird hierfuer das <b>$-Zeichen</b> verwendet.<br>
+ * <b>Beachte:</b><br>
+ * Wird ein benutzerdefinierter Delimiter verwendet, so muss dieser evt. mit
+ * zusaetzlichen Backslashs versehen werden, wenn er Spezialzeichen der
+ * regulaeren Ausdruecke (Klasse <code>Pattern</code>) beinhaltet!!<br>
+ * <br>
+ * Das Template wird zunaechst komplett eingelesen und dabei die Schleifen und
+ * Felder identifiziert.<br>
+ * Beim Aufruf von <code>generateDynamicText(..)</code> werden dann die Schleifen
+ * und Felder ausgewertet und der Ausgabetext erzeugt.
+ * Wann eine Schleife beendet wird, und welche Werte jeweils fuer die Felder
+ * eingesetzt werden, entscheidet ein <code>DynamicInputProvider</code>.<br>
+ * Die Ausgabe wird komplett in einem Buffer zwischengespeichert. Ueber einen
+ * <code>InputStream</code> kann dann beliebig darauf zugegriffen werden.
+ * @see #generateDynamicText(OutputStream,DynamicInputProvider)
+ * @see schmitzm.io.dyntxt.DynamicInputProvider
+ * @see java.util.regex.Pattern
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+
+public class DynamicTextGenerator {
+  /**
+   * Leitet die Tags ein.
+   */
+  public static final String TAG_INTRO          = "$";
+  /**
+   * <code>Pattern</code>-konformes <code>TAG_INTRO</code>.
+   * @see #TAG_INTRO
+   * @see java.util.regex.Pattern
+   */
+  public static final String TAG_INTRO_REGEXPR  = "\\$";
+  /**
+   * Leitet im Template eine Schleife ein.
+   */
+  public static final String TAG_LOOPSTART  = "LOOP";
+  /**
+   * Beendet im Template eine Schleife.
+   */
+  public static final String TAG_LOOPEND    = "ENDLOOP";
+  /**
+   * Steht im Template fuer ein dynamisches Feld.
+   */
+  public static final String TAG_FIELD      = "FIELD";
+
+  private DynamicBlock initialBlock     = null;
+  private String       tagIntro        = "";
+  private String       tagIntroRegExpr = "";
+
+  /**
+   * Erzeugt einen neuen dynamischen Text.
+   * @param template Vorlage-Text mit Tags (Felder, Schleife)
+   * @exception java.io.IOException falls das Lesen aus dem Eingabe-Stream
+   *            fehlschlaegt
+   */
+  public DynamicTextGenerator(InputStream template) throws IOException {
+    this(template,TAG_INTRO,TAG_INTRO_REGEXPR);
+  }
+
+  /**
+   * Erzeugt einen neuen dynamischen Text.
+   * @param template Vorlage-Text mit Tags (Felder, Schleife)
+   * @param tagIntro String, der die Tags einleitet
+   * @param tagIntroRegExpr String, der die Tags einleitet (so erweitert, dass er
+   *        mit den Regulaeren Ausdruecken kompatibel ist)
+   * @exception java.io.IOException falls das Lesen aus dem Eingabe-Stream
+   *            fehlschlaegt
+   * @see java.util.regex.Pattern
+   */
+  public DynamicTextGenerator(InputStream template, String tagIntro, String tagIntroRegExpr) throws IOException {
+    this.tagIntro        = tagIntro;
+    this.tagIntroRegExpr = tagIntroRegExpr;
+    this.initialBlock    = createBlockFromTemplate(template);
+  }
+
+  /**
+   * Generiert einen Text, in dem die dynamischen Felder der Vorlage durch
+   * den <code>DynamicInputProvider</code> aufgeloest werden.
+   * @param output Ausgabe-Stream fuer den dynamischen Text
+   * @param inputProvider loest die dynamischen Elemente auf
+   * @exception java.io.IOException falls das Schreiben in den Ausgabe-Stream
+   *            fehlschlaegt
+   */
+  public void generateDynamicText(OutputStream output, DynamicInputProvider inputProvider) throws IOException {
+    initialBlock.performElement(inputProvider,output);
+  }
+
+  /**
+   * Liefert die Zeichenfolge, die die Tags im Template einleitet und sie beim
+   * Parsen eindeutig vom Text unterscheidet.
+   */
+  public String getTagIntro() {
+    return this.tagIntro;
+  }
+
+  /**
+   * Liefert das um Sonderzeichen erweitere TagIntro. Diese (insbesondere
+   * Backslashes) werden notwendig, wenn das TagIntro Zeichen enthaelt, die
+   * in regulaeren Ausdruecken durch die Klasse <code>Pattern</code> speziell
+   * interpretiert werden.
+   * @see #getTagIntro()
+   * @see java.util.regex.Pattern
+   */
+  public String getTagIntroForRegExpr() {
+    return this.tagIntroRegExpr;
+  }
+
+  /**
+   * Parst die Vorlage und erzeugt daraus einen Wurzel-Block.
+   */
+  private DynamicBlock createBlockFromTemplate(InputStream template) throws IOException {
+    // Initialen Block erzeugen
+    DynamicBlock rootBlock = new DynamicBlock("ROOT");
+    // regulaeren Ausdruck fuer Tags erzeugen
+    String loopStartPattern = getTagIntroForRegExpr()+TAG_LOOPSTART+"\\(\\w*\\)";
+    String loopEndPattern   = getTagIntroForRegExpr()+TAG_LOOPEND+"\\(\\)";
+    String fieldPattern     = getTagIntroForRegExpr()+TAG_FIELD+"\\(\\w*\\)";
+    Pattern pattern = Pattern.compile("("+loopStartPattern+")|("+loopEndPattern+")|("+fieldPattern+")");
+
+    DynamicBlock actualBlock = rootBlock;
+
+    // Eingabe zeilenweise auslesen
+    BufferedReader input = new BufferedReader( new InputStreamReader(template) );
+    String line   = "";
+    int    lineNo = 0;
+    for(lineNo=1; input.ready() && (line=input.readLine())!=null; lineNo++ ) {
+      int lastEnd = 0; // Ende des letzten Pattern-Match der Zeile (=Anfang)
+      Matcher m = pattern.matcher(line);
+      while( m.find() ) {
+        // alles zwischen letzem Tag und neuem Tag wird zu Text-Feld
+        String text = line.substring(lastEnd,m.start());
+        if ( text.length() > 0 )
+          actualBlock.add( new StaticText(actualBlock,text) );
+
+        // neuer Tag
+        String tag = m.group();
+        // Ende des neuen Tags bestimmen
+        lastEnd = m.end();
+        // ID steht zwischen den beiden Klammern
+        String id  = tag.substring(tag.indexOf('(')+1,tag.indexOf(')'));
+//        System.err.println(m.start()+"\t"+m.end()+"\t"+tag+"\t"+id);
+        // welcher Tag ist es?
+        if ( tag.startsWith(getTagIntro()+TAG_LOOPSTART) ) {
+          // Start Loop
+          DynamicLoop loop = new DynamicLoop(id,actualBlock);
+          actualBlock.add(loop);
+          actualBlock = loop;
+        } else if ( tag.startsWith(getTagIntro()+TAG_LOOPEND) ) {
+          // End Loop
+          actualBlock = (DynamicBlock)actualBlock.getFather();
+          // ein ENDLOOP zuviel
+          if (actualBlock==null)
+            throw new IOException("Line "+lineNo+": ENDLOOP not expected");
+        } else if ( tag.startsWith(getTagIntro()+TAG_FIELD) ) {
+          // Field
+          actualBlock.add( new DynamicField(id,actualBlock) );
+        }
+      }
+      // alles zwischen letzem Tag und Zeilenende wird zu Text-Feld
+      String text = line.substring(lastEnd)+"\n";
+      if ( !actualBlock.isEmpty() || !text.equals("\n") )
+        actualBlock.add( new StaticText(actualBlock,text) );
+    }
+
+    // ENDLOOP fehlt
+    if (actualBlock!=rootBlock)
+      throw new IOException("Line "+lineNo+++": ENDLOOP missing");
+
+    return rootBlock;
+  }
+}

Added: trunk/src/schmitzm/io/dyntxt/StaticText.java
===================================================================
--- trunk/src/schmitzm/io/dyntxt/StaticText.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/dyntxt/StaticText.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,72 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.io.dyntxt;
+
+import java.io.*;
+
+/**
+ * Diese Klasse stellt einen dynamisches Element dar, welches einen
+ * statischen Text speichert.
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public class StaticText extends DynamicElement {
+  private String text = "";
+
+  /**
+   * Erzeugt ein neues Text-Element. Als ID wird diesem ein Leerstring
+   * zugeordnet. Dies ist unproblematisch, da das statische Text-Feld
+   * nicht durch einem <code>DynamicInputProvider</code> identifiziert
+   * werden muss.
+   * @param text statischer Text
+   */
+  public StaticText(String text) {
+    this(null,text);
+  }
+
+  /**
+   * Erzeugt ein neues Text-Element.
+   * @param father anderes dynamisches Element, welches die Schleife beinhaltet
+   * @param text statischer Text
+   */
+  public StaticText(DynamicElement father, String text) {
+    this("",father,text);
+  }
+
+  /**
+   * Erzeugt ein neues Feld-Element.
+   * @param id identifieziert das Feld gegenueber dem <code>DynamicInputProvider</code>
+   * @param father anderes dynamisches Element, welches die Schleife beinhaltet
+   * @param text statischer Text
+   */
+  public StaticText(String id, DynamicElement father, String text) {
+    super(id,father);
+    this.text = text;
+  }
+
+  /**
+   * Liefert den statischen Text.
+   */
+  public String getText() {
+    return text;
+  }
+
+  /**
+   * Schreibt den statischen Text einen Ausgabe-Stream.
+   * @param inputProvider verarbeitet die Fields (hat keine Bedeutung fuer diese Funktion)
+   * @param output        hier wird die Ausgabe reingeschrieben
+   * @exception java.io.IOException falls das Schreiben in den Stream fehlschlaegt
+   */
+  public void performElement(DynamicInputProvider inputProvider, OutputStream output) throws IOException {
+    output.write( getText().getBytes() );
+  }
+}
\ No newline at end of file

Added: trunk/src/schmitzm/io/package.html
===================================================================
--- trunk/src/schmitzm/io/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/io/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code java.io}.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/jfree/JFreeChartUtil.java
===================================================================
--- trunk/src/schmitzm/jfree/JFreeChartUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/jfree/JFreeChartUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,34 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.jfree;
+
+import java.util.Locale;
+import java.util.Vector;
+
+import org.jfree.data.category.CategoryDataset;
+import org.jfree.data.category.DefaultCategoryDataset;
+
+import schmitzm.lang.ResourceProvider;
+import schmitzm.lang.LangUtil;
+
+/**
+ * In dieser Klasse sind Hilfsmethoden fuer JFreeChart hinterlegt.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class JFreeChartUtil {
+  /** {@link ResourceProvider}, der die Lokalisation fuer GUI-Komponenten
+   *  zur Verfuegung stellt. Diese sind in properties-Datein unter
+   *  {@code schmitzm.jfree.resource.locales.JFreeResourceBundle_XXX.properties}
+   *  hinterlegt. */
+  public static ResourceProvider RESOURCE = new ResourceProvider( LangUtil.extendPackagePath(JFreeChartUtil.class,"resource.locales.JFreeResourceBundle"), Locale.ENGLISH );
+
+}

Added: trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties
===================================================================
--- trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,10 @@
+# --------------------------------------------------------------------------
+# ------ Default Translations (english) for JFreeChart GUI components ------
+# ------ in Package schmitz.jfree                                     ------
+# --------------------------------------------------------------------------
+
+# Diagram types
+diagram.points=Points
+diagram.lines=Lines
+diagram.bars=Bars
+diagram.areas=Areas

Added: trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties
===================================================================
--- trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/jfree/resource/locales/JFreeResourceBundle_de.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,10 @@
+# ---------------------------------------------------------------
+# ------ German Translations for JFreeChart GUI components ------
+# ------ in Package schmitz.jfree                          ------
+# ---------------------------------------------------------------
+
+# Diagram types
+diagram.points=Punkte
+diagram.lines=Linien
+diagram.bars=Balken
+diagram.areas=Flächen

Added: trunk/src/schmitzm/lang/AbstractNamedObject.java
===================================================================
--- trunk/src/schmitzm/lang/AbstractNamedObject.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/AbstractNamedObject.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,37 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Diese Klasse bildet eine Basis-Implementierung von {@link NamedObject}
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractNamedObject implements NamedObject {
+  /** Speichert den Namen des Objekts. */
+  protected String name = "";
+
+  /**
+   * Liefert den Namen des Objekts.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Setzt den Namen des Objekts.
+   * @param name neuer Name
+   */
+  public void setName(String name) {
+    this.name = name;
+  }
+}

Added: trunk/src/schmitzm/lang/AlreadyHandledException.java
===================================================================
--- trunk/src/schmitzm/lang/AlreadyHandledException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/AlreadyHandledException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,36 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Diese Exception stellt einen Fehler dar, der bereits "irgendwo" abgefangen
+ * wurde. Die zeit nur an, DASS ein Fehler aufgetreten ist, dieser aber nicht
+ * mehr (mit einer Fehlermeldung) verarbeitet werden muss.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class AlreadyHandledException extends RuntimeException {
+  /**
+   * Erzeugt eine neue <code>AlreadyHandledException</code>.
+   */
+  public AlreadyHandledException() {
+    this(null);
+  }
+
+  /**
+   * Erzeugt eine neue <code>AlreadyHandledException</code>.
+   * @param mess Meldung
+   */
+  public AlreadyHandledException(String mess) {
+    super(mess);
+  }
+}

Added: trunk/src/schmitzm/lang/ComparableObject.java
===================================================================
--- trunk/src/schmitzm/lang/ComparableObject.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/ComparableObject.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,105 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+// nur fuer Doku
+import java.util.TreeSet;
+
+/**
+ * Diese Klasse dient dazu, ein beliebiges Objekt <b>{@linkplain Comparable vergleichbar}</b>
+ * zu machen. Hierzu verbindet eine diese <code>ComparableObject</code>
+ * das Objekt mit einem {@linkplain Comparable vergleichbaren} Key, so dass
+ * ein Tupel <K,V> entsteht, welches z.B. in ein {@link TreeSet} eingefuegt
+ * werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ComparableObject<K extends Comparable,V> implements Comparable {
+  private K       key    = null;
+  private V       obj    = null;
+  private boolean onlyObj = false;
+
+  /**
+   * Erzeugt ein neues Tupel
+   * @param key  Key-Wert ueber den das Objekt verglichen wird
+   *             (muss eine {@link Comparable}-Instanz sein!)
+   * @param obj  beliebiges Objekt, dem der Key <code>key</code> zugeordnet ist
+   */
+  public ComparableObject(K key, V obj) {
+    this(key,obj,false);
+  }
+
+  /**
+   * Erzeugt ein neues Tupel
+   * @param key     Key-Wert ueber den das Objekt verglichen wird
+   *                (muss eine {@link Comparable}-Instanz sein!)
+   * @param obj     beliebiges Objekt, dem der Key <code>key</code> zugeordnet ist
+   * @param onlyObj wenn <code>true</code> wird in der {@link #equals(Object)}-Methode
+   *                nur auf <code>obj</code> verglichen. Standardmaessig vergleicht
+   *                die {@link #equals(Object)}-Methode <code>obj</code> und
+   *                <code>key</code>.
+   */
+  public ComparableObject(K key, V obj, boolean onlyObj) {
+    this.key = key;
+    this.obj = obj;
+    this.onlyObj = onlyObj;
+  }
+
+  /**
+   * Liefert das Objekt.
+   */
+  public V getObject() {
+    return obj;
+  }
+
+  /**
+   * Liefert den Schluessel.
+   */
+  public K getKey() {
+    return key;
+  }
+
+  /**
+   * Vergleicht den Schluessel dieses Objekts mit einem anderen. Liefert nur 0,
+   * wenn <code>o</code> mit <code>this</code> uebereinstimmt!
+   * @param o zu vergleichendes Objekt (kann ein anderes <code>ComparableObject</code>
+   *          sein oder eine {@link Comparable}-Instanz)
+   * @return einen <b>negativen</b> Wert, falls dieses Objekt <i>kleiner</i> ist als <code>o</code>;<br>
+   *         einen <b>positiven</b> Wert, falls dieses Objekt <i>groesser</i> ist als <code>o</code>;<br>
+   *         <b>0</b>, falls die beiden Objekte identisch sind
+   */
+  public int compareTo(Object o) {
+    if ( this == o || this.equals(o) )
+      return 0;
+
+    int comp = ( o instanceof ComparableObject ) ? key.compareTo( ((ComparableObject)o).getKey() ) : key.compareTo(o);
+    if ( comp == 0 ) {
+      comp = ( this.hashCode() < o.hashCode() ) ? -1 : 1;
+    }
+    return comp;
+  }
+
+  /**
+   * Vergleicht das <code>ComparableObject</code> mit einem anderen. Wurde im
+   * {@linkplain #ComparableObject(K,V,boolean) Konstruktor} das <code>onlyObj</code>-Flag
+   * auf <code>true</code> gesetzt, wird an dieser Stelle nur das Objekt
+   * auf Gleichheit verglichen, ansonsten auch der Key.
+   * @param o ein anderes <code>ComparableObject</code>
+   */
+  public boolean equals(Object o) {
+    if ( !(o instanceof ComparableObject) )
+      return false;
+    ComparableObject c = (ComparableObject)o;
+    return this.obj.equals(c.getObject()) && (onlyObj || this.key.equals(c.getKey()));
+  }
+
+}

Added: trunk/src/schmitzm/lang/DefaultComparator.java
===================================================================
--- trunk/src/schmitzm/lang/DefaultComparator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/DefaultComparator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,69 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.lang;
+
+import java.util.Comparator;
+
+/**
+ * {@code Comparator} um beliebige Objekte ueber ihre {@code toString()}-Methode
+ * zu vergleichen.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class DefaultComparator implements Comparator<Object> {
+
+  /** Standard-Instanz dieses {@code Comparator}. */
+  public static final DefaultComparator DEFAULT = new DefaultComparator();
+
+  /**
+   * Erzeugt einen neuen {@code Comparator}.
+   */
+  public DefaultComparator() {
+  }
+
+  /**
+   * Fuehrt den Vergleich aus.
+   * @param obj1 ein Objekt
+   * @param obj2 ein anders Objekt
+   * @return ein negativer Wert fuer {@code obj1 < obj2}, 0 für {@code obj1 = obj2}
+   *         oder ein positiver Wert fuer {@code obj1 > obj2}
+   */
+  public int compare(Object obj1, Object obj2) {
+    if ( obj1 == null && obj2 == null )
+      return 0;
+    if ( obj1 == null )
+      return Integer.MAX_VALUE;
+    if ( obj2 == null )
+      return Integer.MIN_VALUE;
+    return obj1.toString().compareTo(obj2.toString());
+  }
+
+  /**
+   * Liefert einen {@link Comparator}, der die umgekehrte Reihenfolge eines
+   * {@link Comparator} definiert. {@code null}-Werte sind trotzdem groesser
+   * als alles andere und werden somit auch in der invertierten Reihenfolge
+   * hinten angehaengt.
+   * @param comparator ein Comparator
+   */
+  public static <T> Comparator<T> invert(final Comparator<T> comparator) {
+    return new Comparator<T>() {
+      public int compare(T obj1, T obj2) {
+        if ( obj1 == null && obj2 == null )
+          return 0;
+        if ( obj1 == null )
+          return Integer.MAX_VALUE;
+        if ( obj2 == null )
+          return Integer.MIN_VALUE;
+        return comparator.compare(obj2,obj1);
+      }
+    };
+  }
+}

Added: trunk/src/schmitzm/lang/DuplicateException.java
===================================================================
--- trunk/src/schmitzm/lang/DuplicateException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/DuplicateException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,65 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Diese Exception stellt einen Duplikat-Fehler dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class DuplicateException extends RuntimeException {
+  private Object currObj = null;
+  private Object duplObj = null;
+
+  /**
+   * Erzeugt eine neue Exception
+   * @param mess    Fehlerbeschreibung
+   * @param currObj das bestehende Objekt
+   * @param duplObj das Duplikat, das den Fehler ausgeloest hat
+   */
+  public DuplicateException(String mess, Object currObj, Object duplObj) {
+    super(mess);
+    this.currObj = currObj;
+    this.duplObj = duplObj;
+  }
+
+  /**
+   * Erzeugt eine neue Exception
+   * @param mess    Fehlerbeschreibung
+   */
+  public DuplicateException(String mess) {
+    this(mess, null, null);
+  }
+
+  /**
+   * Erzeugt eine neue Exception
+   */
+  public DuplicateException() {
+    this("", null, null);
+  }
+
+  /**
+   * Liefert das bestehende Objekt.
+   * @return Object
+   */
+  public Object getCurrentObject() {
+    return this.currObj;
+  }
+
+  /**
+   * Liefert das Objekt, das den Duplikatfehler ausgeloest hat.
+   */
+  public Object getDuplicateObject() {
+    return this.duplObj;
+  }
+
+}

Added: trunk/src/schmitzm/lang/HashtableResourceBundle.java
===================================================================
--- trunk/src/schmitzm/lang/HashtableResourceBundle.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/HashtableResourceBundle.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,99 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.ResourceBundle;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * Diese Klasse stellt ein als {@link Hashtable} organisiertes
+ * {@link ResourceBundle} dar. Sub-Klassen muessen lediglich noch die Methode
+ * {@link #getContents()} implementieren, die den Inhalt des ResourceBundle
+ * liefert. Aus diesem wird die Hash-Tabelle zusammengesetzt.<br>
+ * Das ResourceBundle kann so eingestellt werden, dass es Fehler ignoriert.
+ * Normalerweise wirft {@link ResourceBundle#getObject} eine Exception, wenn
+ * zu einem Key kein Objekt (auch in keinem Parent-ResourceBundle) zu finden ist.
+ * Ueber die Methode {@link #setErrorTolerant(boolean)} kann das
+ * <code>HashtableResourceBundle</code> so eingestellt werden, dass ein
+ * solcher Fehler vermieden wird, in dem statt <code>null</code> ein Dummy-String
+ * von der Methode {@link #handleGetObject(String)} geliefert wird.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class HashtableResourceBundle extends ResourceBundle {
+  /** Speichert den Inhalt des {@link ResourceBundle}. */
+  protected Hashtable content = new Hashtable<String,Object>();
+
+  /** Speichert, ob das Fehlen einer angeforderten Ressource toleriert wird.<br>
+   *  Default: <code>false</code> */
+  protected static boolean errorTolerant = false;
+
+  /**
+   * Erzeugt ein neues ResourceBundle.
+   */
+  public HashtableResourceBundle() {
+    Object[][] res = getContents();
+    if ( res!=null ) {
+      content = new Hashtable<String, Object> (res.length);
+      for (int i = 0; i < res.length; i++) {
+        if (res[i].length < 2)
+          throw new UnsupportedOperationException("Return of HashtableResourceBundle.getContents() must be a two dimensional array!");
+        if ( !(res[i][0] instanceof String) )
+          throw new UnsupportedOperationException("HashtableResourceBundle.getContents() must contain Strings in first dimension!");
+        content.put(res[i][0], res[i][1]);
+      }
+    }
+  }
+
+  /**
+   * Liefert alle Keys zu denen eine Ressource hinterlegt ist.
+   */
+  public Enumeration<String> getKeys() {
+    return content.keys();
+  }
+
+  /**
+   * Stellt sie Toleranz des ResourceBundle bei fehlenden Ressourcen ein.
+   */
+  public static void setErrorTolerant(boolean tolerant) {
+    errorTolerant = tolerant;
+  }
+
+  /**
+   * Prueft, ob das ResourceBundle fehlertolerant auf fehlende Ressourcen
+   * reagiert.
+   */
+  public static boolean isErrorTolerant() {
+    return errorTolerant;
+  }
+
+  /**
+   * Liefert eine Ressource zu einem Key aus der Hash-Tabelle.
+   * @return <code>null</code> falls es den Key in der Hash-Tabelle nicht
+   *         gibt und die Fehlerroleranz abgeschaltet ist
+   */
+  protected Object handleGetObject(String key) {
+    Object obj = content.get(key);
+    // Wenn kein Objekt gefunden wurde, wuerde die Standard-Methode
+    // von ResourceBundle.getObject(String) eine Exception werfen
+    // Dies soll aber nicht der Fall sein, wenn das Bundle Fehler-Tolerant ist
+    if ( obj == null && parent == null && isErrorTolerant() )
+      obj = "'"+key+"'-Resource not found!";
+    return obj;
+  }
+
+  /**
+   * Liefert die (Key,Ressource)-Paerchen. Der Key <b>muss</b> ein String sein.
+   */
+  public abstract Object[][] getContents();
+}

Added: trunk/src/schmitzm/lang/HashtableWithCollisionList.java
===================================================================
--- trunk/src/schmitzm/lang/HashtableWithCollisionList.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/HashtableWithCollisionList.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,86 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Diese Klasse erweitert die normale Hash-Tabelle um Kollisionslisten.
+ * Pro Hashtable-Key koennen somit mehrere Werte gespeichert werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class HashtableWithCollisionList<K,V> extends Hashtable<K,Vector<V>> {
+
+  /**
+   * Erzeugt eine neue Hash-Tabelle.
+   */
+  public HashtableWithCollisionList() {
+    super();
+  }
+
+  /**
+   * Fuegt der Hash-Tabelle ein Objekt hinzu. Existiert bereits ein Element
+   * mit dem Key, wird dieses nicht entfernt, sondern die entsprechende
+   * Kollisionsliste erweitert.
+   * @param key   Der Hashtable-Key
+   * @param value Wert
+   */
+  public synchronized void add(K key, V value) {
+    // Wenn noch keine Kollisionsliste zu dem Key existiert, wird
+    // diese erzeugt
+    if ( !this.containsKey(key) )
+      super.put(key,new Vector<V>());
+    // In der Liste das Objekt hinzufuegen
+    ((Vector)this.get(key)).add(value);
+  }
+
+  /**
+   * Ersetzt eine komplette Kollisionsliste fuer einen Key.
+   * @param key   Der Hashtable-Key
+   * @param value Kollisionsliste
+   * @return immer <code>null</code>
+   */
+  public synchronized Vector<V> put(K key, Vector<V> value) {
+    super.put(key,value);
+    return null;
+  }
+
+  /**
+   * Entfernt eine komplette Kollisionsliste zu einem Key. Es werden nur die
+   * Element der Liste entfernt. Die Kollisionsliste selbst bleibt bestehen!
+   * @param key   Der Hashtable-Key
+   * @return immer <code>null</code>
+   */
+  public synchronized Vector<V> remove(Object key) {
+    Vector list = super.get(key);
+    if ( list!= null )
+      list.clear();
+    return null;
+  }
+
+  /**
+   * Entfernt ein objekt aus der Hash-Tabelle.
+   * @param key   Der Hashtable-Key
+   * @return Das entfernte Objekt oder <code>null</code> falls das Objekt
+   *         nicht in der Hash-Tabelle gespeichert war
+   */
+  public synchronized Object remove(Object key, Object value) {
+    Vector list = super.get(key);
+    if ( list == null )
+      return null;
+    list.remove(value);
+    return value;
+  }
+
+}

Added: trunk/src/schmitzm/lang/Indent.java
===================================================================
--- trunk/src/schmitzm/lang/Indent.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/Indent.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,99 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Diese Klasse stellt eine Hilfsklasse fuer eingerueckte Texte dar.
+ * Mittels der Methoden {@link #push()} und {@link #pop()} wird die Einrueckung
+ * um ein feste Anzahl an Leerstellen erweitert bzw. reduziert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class Indent {
+  private StringBuffer buffer = new StringBuffer();
+  private String       indent = null;
+
+  /**
+   * Erzeugt eine neue Einrueckung fuer 2 Stellen.
+   */
+  public Indent() {
+    this(2);
+  }
+
+  /**
+   * Erzeugt eine neue Einrueckung.
+   * @param len Anzahl der Stellen, um die eingerueckt wird.
+   */
+  public Indent(int len) {
+    indent = "";
+    for (int i=0; i<len; i++)
+      indent += "";
+  }
+
+  /**
+   * Loescht den Einrueck-Buffer.
+   */
+  public void clear() {
+    buffer = new StringBuffer();
+  }
+
+  /**
+   * Liefert die aktuelle Anzahl an Leerstellen, um die eingerueckt wird.
+   */
+  public int getLength() {
+    return buffer.length();
+  }
+
+  /**
+   * Liefert die Leerstellen, um die bei einem {@link #push()}-Aufruf
+   * eingerueckt wird.
+   */
+  public String getIndent() {
+    return indent;
+  }
+
+  /**
+   * Liefert die Anzahl an Leerstellen, um die bei einem {@link #push()}-Aufruf
+   * eingerueckt wird.
+   */
+  public int getIndentLength() {
+    return indent.length();
+  }
+
+
+  /**
+   * Erweitert die Einrueckung um die im Konstruktor angegebene Anzahl an
+   * Leerstellen.
+   * @see #getIndent()
+   * @see #getIndentLength()
+   */
+  public void push() {
+    buffer.append(indent);
+  }
+
+  /**
+   * Reduziert die Einrueckung um die im Konstruktor angegebene Anzahl an
+   * Leerstellen.
+   * @see #getIndent()
+   * @see #getIndentLength()
+   */
+  public void pop() {
+    buffer.delete( buffer.length()-indent.length(), buffer.length() );
+  }
+
+  /**
+   * Liefert die aktuelle Einrueckung als String.
+   */
+  public String toString() {
+    return buffer.toString();
+  }
+}

Added: trunk/src/schmitzm/lang/LangUtil.java
===================================================================
--- trunk/src/schmitzm/lang/LangUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/LangUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,892 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.TreeSet;
+import java.util.Vector;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.MalformedURLException;
+import java.lang.reflect.Constructor;
+import net.jini.loader.pref.PreferredClassLoader;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Category;
+
+/**
+ * Diese Klasse stellt Hilfsfunktionen fuer Basisoperationen bereit.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LangUtil {
+
+
+  /**
+   * Liefert den einfachen Namen einer Klasse (ohne Package) oder den
+   * String "null", falls das Objekt {@code null} ist
+   * @param object ein Objekt
+   * @param niemals {@code null}
+   */
+  public static String getSimpleClassName(Object object) {
+    if ( object == null )
+      return "null";
+    return object.getClass().getSimpleName();
+  }
+
+
+  /**
+   * Prueft, ob ein {@link Enum} ein spezielles Feld besitzt.
+   * @param enumClass enum typ
+   * @param fieldName feld name
+   */
+  public static boolean checkEnumValue(Class<? extends Enum> enumClass, String fieldName) {
+    try {
+      Enum.valueOf(enumClass, fieldName);
+      return true;
+    } catch (IllegalArgumentException err) {
+      return false;
+    }
+  }
+
+  /**
+   * Fuegt alle Elemente eines Arrays in einen {@link Vector} ein. Wird als Vector
+   * <code>null</code> angegeben, wird ein neuer Vector erzeugt.
+   * @param obj einzufuegende Objekte
+   * @param vec Vector in den die Elemente eingefuegt werden (kann <code>null</code>
+   *            sein)
+   * @return den Vector <code>vec</code> oder eine neue {@link Vector}-Instanz
+   */
+  public static Vector addObjectsToVector(Object[] obj, Vector vec) {
+    if ( vec == null )
+      vec = new Vector();
+    for (int i=0; i<obj.length; i++)
+      vec.add(obj[i]);
+    return vec;
+  }
+
+  /**
+   * Ersetzt alle Tabulator-Vorkommen in einem String durch Leerzeichen.
+   * @param source Quell-String
+   * @param tabLen Anzahl an Zeichen die ein Tabulator darstellt
+   */
+  public static String replaceTabulators(String source, int tabLen) {
+    String outString       = new String(source);
+    char[] maxSpacePattern = new char[tabLen];
+    Arrays.fill(maxSpacePattern,' ');
+
+    for(int pos; (pos=outString.indexOf('\t')) >= 0;) {
+      outString = outString.replaceFirst(
+        "\t",
+        String.valueOf(maxSpacePattern,0,tabLen-pos%tabLen)
+      );
+    }
+    return outString;
+  }
+
+  /**
+   * Haengt eine beliebige Anzahl von Strings hintereinander.
+   * @param str aneinanderzuhaengende Stings
+   */
+  public static String stringConcat(String... str) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; str!=null && i<str.length; i++)
+      sb.append(str[i]);
+    return sb.toString();
+  }
+
+  /**
+   * Haengt eine beliebige Anzahl von Strings hintereinander und fuegt zwischen
+   * den Strings einen Trenner ein.
+   * @param sep Trenner
+   * @param str aneinanderzuhaengende Stings
+   */
+  public static String stringConcatWithSep(String sep, String... str) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; str!=null && i<str.length; i++) {
+      if ( i>0 )
+        sb.append(sep);
+      sb.append(str[i]);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Erzeugt einen vorbelegten Array.
+   * @param count Groesse des Arrays
+   * @param value Wert mit dem die Elemente vorbelegt werden
+   */
+  public static int[] createArray(int count, int value) {
+    int[] array = new int[count];
+    java.util.Arrays.fill(array,value);
+    return array;
+  }
+
+  /**
+   * Erzeugt einen vorbelegten Array.
+   * @param count Groesse des Arrays
+   * @param value Wert mit dem die Elemente vorbelegt werden
+   */
+  public static long[] createArray(int count, long value) {
+    long[] array = new long[count];
+    java.util.Arrays.fill(array,value);
+    return array;
+  }
+
+  /**
+   * Erzeugt einen vorbelegten Array.
+   * @param count Groesse des Arrays
+   * @param value Wert mit dem die Elemente vorbelegt werden
+   */
+  public static float[] createArray(int count, float value) {
+    float[] array = new float[count];
+    java.util.Arrays.fill(array,value);
+    return array;
+  }
+
+  /**
+   * Erzeugt einen vorbelegten Array.
+   * @param count Groesse des Arrays
+   * @param value Wert mit dem die Elemente vorbelegt werden
+   */
+  public static double[] createArray(int count, double value) {
+    double[] array = new double[count];
+    java.util.Arrays.fill(array,value);
+    return array;
+  }
+
+  /**
+   * Erzeugt einen vorbelegten Array.
+   * @param count Groesse des Arrays
+   * @param value Wert mit dem die Elemente vorbelegt werden
+   */
+  public static String[] createArray(int count, String value) {
+    String[] array = new String[count];
+    java.util.Arrays.fill(array,value);
+    return array;
+  }
+
+  /**
+   * Erzeugt einen mehr-dimensionalen {@link Object}-Array.
+   * @param size Groesse der Array-Dimensionen
+   * @return einen {@code Object[]..[]}
+   */
+  public static Object createArray(int... size) {
+    switch ( size.length ) {
+      case  1: return new Object[size[0]];
+      case  2: return new Object[size[0]][size[1]];
+      case  3: return new Object[size[0]][size[1]][size[2]];
+      case  4: return new Object[size[0]][size[1]][size[2]][size[3]];
+      case  5: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]];
+      case  6: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]];
+      case  7: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]];
+      case  8: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]];
+      case  9: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]];
+      case 10: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]];
+      case 11: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]];
+      case 12: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]];
+      case 13: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]];
+      case 14: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]];
+      case 15: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]];
+      case 16: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]][size[15]];
+      case 17: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]][size[15]][size[16]];
+      case 18: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]][size[15]][size[16]][size[17]];
+      case 19: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]][size[15]][size[16]][size[17]][size[18]];
+      case 20: return new Object[size[0]][size[1]][size[2]][size[3]][size[4]][size[5]][size[6]][size[7]][size[8]][size[9]][size[10]][size[11]][size[12]][size[13]][size[14]][size[15]][size[16]][size[17]][size[18]][size[19]];
+    }
+    throw new IllegalArgumentException("Dimension not supported: "+size.length);
+  }
+
+  /**
+   * Liefert die Dimension eines multi-dimensionalen {@link Object}-Array.<br>
+   * <b>Diese Methode funktioniert nur, wenn das Element 0 in jeder Dimension
+   * belegt ist!</b>
+   * @param array multi-dim. Array (Instanz von {@code Object[]..[]})
+   * @exception ArrayIndexOutOfBoundsException wenn das 0te Element in einer
+   *            Dimension mit {@code null} belegt ist
+   */
+  public static int getArrayDimension(Object array) {
+    int dim = 0;
+    try {
+      for (;; dim++) {
+        array = ((Object[])array)[0];
+      }
+    } catch (ClassCastException err) {
+    }
+    return dim;
+  }
+
+  /**
+   * Liefert die Groessen der einzelnen Dimensionen eines multi-dimensionalen
+   * {@link Object}-Array.<br>
+   * <b>Diese Methode funktioniert nur, wenn das Element 0 in jeder Dimension
+   * belegt ist!</b>
+   * @param array multi-dim. Array (Instanz von {@code Object[]..[]})
+   * @exception ArrayIndexOutOfBoundsException wenn das 0te Element in einer
+   *            Dimension mit {@code null} belegt ist
+   */
+  public static int[] getArrayLength(Object array) {
+    int[] dimSize = new int[ getArrayDimension(array) ];
+    for (int d=0; d<dimSize.length; d++) {
+      dimSize[d] = ((Object[])array).length;
+      array = ( (Object[]) array)[0];
+    }
+    return dimSize;
+  }
+
+  /**
+   * Liefert einen Wert aus einem multi-dimensionalen {@link Object}-Array.
+   * @param array multi-dim. Array (Instanz von {@code Object[]..[]})
+   * @param coord Koordinaten des Arrays
+   * @exception ArrayIndexOutOfBoundsException wenn eine fehlerhafte Koordinate
+   *            angegeben wird
+   */
+  public static Object getArrayValue(Object array, int... coord) {
+    for (int i=0; i<coord.length; i++)
+      array = ((Object[])array)[ coord[i] ];
+    return array;
+  }
+
+  /**
+   * Setzt einen Wert aus einem multi-dimensionalen {@link Object}-Array.
+   * @param array multi-dim. Array (Instanz von {@code Object[]..[]})
+   * @param value zu setzender Wert (Instanz von {@code Object[]..[]})
+   * @param coord Koordinaten des Arrays
+   * @exception ArrayIndexOutOfBoundsException wenn eine fehlerhafte Koordinate
+   *            angegeben wird
+   */
+  public static void setArrayValue(Object array, Object value, int... coord) {
+    for (int i=0; i<coord.length-1; i++)
+      array = ((Object[])array)[ coord[i] ];
+    ((Object[])array)[ coord[coord.length-1] ] = value;
+  }
+
+  /**
+   * Kopiert einen Array (aber nicht dessen Elemente). Darueberhinaus koennen
+   * (einige) Elemente direkt neu belegt werden. Ist das Flag {@code replNull}
+   * auf {@code false} gesetzt, werden {@code null}-Werte in {@code replElem}
+   * ignoriert. So ist es z.B. moeglich, nur das 4. und 5. Element im neuen
+   * Array neu zu belegen und die davor liegenden Elemente unangetastet zu lassen.
+   * @param source   zu kopierender Array
+   * @param replNull Bestimmt, ob {@code null}-Elemente von {@code replElem}
+   *                 im neuen Array gesetzt werden ({@code true}) oder nicht
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static <T> T[] cloneArray(T[] source, boolean replNull, T... replElem) {
+    T[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      if (replNull || replElem[i] != null)
+        newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array (aber nicht dessen Elemente). Darueberhinaus koennen
+   * (einige) Elemente direkt neu belegt werden.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static <T> T[] cloneArray(T[] source, T... replElem) {
+    return cloneArray(source,true,replElem);
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static long[] cloneArray(long[] source, long... replElem) {
+    long[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static int[] cloneArray(int[] source, int... replElem) {
+    int[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static short[] cloneArray(short[] source, short... replElem) {
+    short[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static byte[] cloneArray(byte[] source, byte... replElem) {
+    byte[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static double[] cloneArray(double[] source, double... replElem) {
+    double[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static float[] cloneArray(float[] source, float... replElem) {
+    float[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static boolean[] cloneArray(boolean[] source, boolean... replElem) {
+    boolean[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Kopiert einen Array und belegt (einige) Elemente direkt neu.
+   * @param source   zu kopierender Array
+   * @param replElem Elemente (beginnend bei Index 0), die nach dem Kopieren
+   *                 im Array neu gesetzt werden
+   * @return Array von der Groesse {@code max(source.length, replElem.length)}
+   */
+  public static char[] cloneArray(char[] source, char... replElem) {
+    char[] newArray =  java.util.Arrays.copyOf(source, Math.max(source.length, replElem.length));
+    for (int i=0; i<replElem.length; i++)
+      newArray[i] = replElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static <T> T[] extendArray(T[] source, T... newElem) {
+    T[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static long[] extendArray(long[] source, long... newElem) {
+    long[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static int[] extendArray(int[] source, int... newElem) {
+    int[] newArray = java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static short[] extendArray(short[] source, short... newElem) {
+    short[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static byte[] extendArray(byte[] source, byte... newElem) {
+    byte[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static double[] extendArray(double[] source, double... newElem) {
+    double[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static float[] extendArray(float[] source, float... newElem) {
+    float[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static boolean[] extendArray(boolean[] source, boolean... newElem) {
+    boolean[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Erweitert einen Array.
+   * @param source   zu kopierender Array
+   * @param newElem  Elemente, die an das Ende des Arrays angehaengt werden
+   * @return Array von der Groesse {@code source.length + newElem.length}
+   */
+  public static char[] extendArray(char[] source, char... newElem) {
+    char[] newArray =  java.util.Arrays.copyOf(source, source.length+newElem.length);
+    for (int i=0; i<newElem.length; i++)
+      newArray[source.length+i] = newElem[i];
+    return newArray;
+  }
+
+  /**
+   * Liefert die Klassen-Namen zu einem Array von Klassen.
+   * @param clazz  Klassen
+   * @param simple gibt an, ob der einfache Klassenname ({@code true}) oder der
+   *               komplette Klassenname inkl. Paketname ({@code false})
+   *               zurueckgegeben wird.
+   */
+  public static String[] getClassNames(Class[] clazz, boolean simple) {
+    String[] className = new String[clazz.length];
+    for (int i=0; i<className.length; i++)
+      className[i] = simple ? clazz[i].getSimpleName() : clazz[i].getName();
+    return className;
+  }
+
+  /**
+   * Ruft die Java Garbage Collection auf.
+   * @return Anzahl an Bytes, die durch den Aufruf der Garbage Collection
+   *         freigegeben wurden
+   * @see Runtime#gc()
+   * @see Runtime#freeMemory()
+   */
+  public static long gc() {
+    Runtime runtime = Runtime.getRuntime();
+    long prevFreeMem = runtime.freeMemory();
+    runtime.gc();
+    return runtime.freeMemory() - prevFreeMem;
+  }
+
+  /**
+   * Ruft die Java Garbage Collection solange auf, bis kein Speicher mehr
+   * freigegeben werden kann.
+   * @return Gesamt-Anzahl an Bytes, die durch die Aufrufe der Garbage Collection
+   *         freigegeben wurden
+   * @see #gc()
+   */
+  public static long gcTotal() {
+    long totalFreedMem = 0;
+    for ( long freedMem=0; (freedMem = gc()) > 0; )
+     totalFreedMem += freedMem;
+    return totalFreedMem;
+  }
+
+  /**
+   * Erzeugt einen neuen {@link URLClassLoader}, der vom
+   * {@linkplain ClassLoader#getSystemClassLoader() System-ClassLoaders}
+   * abgeleitet ist.
+   * @param searchPath Pfade, in denen der ClassLoader nach Klassen sucht
+   */
+  public static ClassLoader createClassLoader(URL[] searchPath) {
+    return URLClassLoader.newInstance( searchPath, ClassLoader.getSystemClassLoader() );
+  }
+
+  /**
+   * Erzeugt einen neuen {@link URLClassLoader}, der vom
+   * {@linkplain ClassLoader#getSystemClassLoader() System-ClassLoaders}
+   * abgeleitet ist.
+   * @param searchPath Pfad, in denm der ClassLoader nach Klassen sucht
+   */
+  public static ClassLoader createClassLoader(URL searchPath) {
+    return createClassLoader( new URL[] {searchPath} );
+  }
+
+  /**
+   * Laed eine Klasse. Ggf. wird hierfuer ein neuer {@link ClassLoader}
+   * erzeugt.
+   * @param clazz      Klassenname (inkl. Paket)
+   * @param searchPath Pfade, in denen der ClassLoader nach Klassen sucht
+   *                   (wenn <code>null</code>, wird der
+   *                   {@linkplain ClassLoader#getSystemClassLoader() System-ClassLoader}
+   *                   zum Laden der Klasse verwendet)
+   */
+  public static Class loadClass(String clazz, URL[] searchPath) throws ClassNotFoundException {
+    if ( searchPath == null )
+      return Class.forName(clazz);
+    return createClassLoader(searchPath).loadClass(clazz);
+  }
+
+  /**
+   * Laed eine Klasse. Ggf. wird hierfuer ein neuer {@link ClassLoader}
+   * erzeugt.
+   * @param clazz      Klassenname (inkl. Paket)
+   * @param searchPath Pfad, in dem der ClassLoader nach Klassen sucht
+   *                   (wenn <code>null</code>, wird der
+   *                   {@linkplain ClassLoader#getSystemClassLoader() System-ClassLoader}
+   *                   zum Laden der Klasse verwendet)
+   */
+  public static Class loadClass(String clazz, URL searchPath) throws ClassNotFoundException {
+    if ( searchPath == null )
+      return Class.forName(clazz);
+    return createClassLoader(searchPath).loadClass(clazz);
+  }
+
+  /**
+	 *
+	 * Laed eine Klasse derart das Klassen im Cache ignoriert werden. Dazu ist es erforderlich
+	 * das unter dem Parameter <code>searchPath</code> eine Verzeichnis "META-INF" vorhanden ist.
+	 * Darin muss eine Datei "PREFERRED.LIST" sein. Wie genau diese Datei aufgebaut sein muss, lässt
+	 * sich in der Dokumentation des {@link PreferredClassLoader PreferredClassLoaders} nachlesen.
+	 *
+	 * @param classname  Klassenname (inkl. Paket)
+	 * @param searchPath Pfad, in dem der ClassLoader nach Klassen sucht
+	 *                   (wenn <code>null</code>, wird der System-Klassenpfad
+	 *                   {@linkplain ClassLoader#getSystemResource(String) ClassLoader.getSystemResource("")}
+	 *                   zum Laden der Klasse verwendet)
+	 * @throws ClassNotFoundException
+	 * @see PreferredClassLoader
+	 *
+	 *  @author Dominik Appl
+	 */
+	public static Class loadPreferredClass(String classname, URL searchPath)
+			throws ClassNotFoundException {
+                if ( searchPath == null )
+                  // System-Klassenpfad verwenden
+                  searchPath = ClassLoader.getSystemResource("");
+		PreferredClassLoader classLoader = new PreferredClassLoader(
+				new URL[]{searchPath}, Thread.currentThread()
+						.getContextClassLoader(), null, false);
+		Class loadedClass = classLoader.loadClass(classname);
+		if (loadedClass == null)
+			return Class.forName(classname);
+
+		return loadedClass;
+	}
+
+  /**
+   * Laed eine Klasse neu und erzeugt eine Objekt-Instanz. Als Suchpfad fuer
+   * die Klasse der System-Klassenpfad
+   * {@linkplain ClassLoader#getSystemResource(String) ClassLoader.getSystemResource("")}
+   * verwendet.
+   * @param classname Klassenname (inkl. Paket)
+   * @param param Parameter fuer den Konstruktor-Aufruf
+   * @exception RuntimeException falls irgendein Fehler auftritt
+   */
+  public static Object loadPreferredObject(String classname, Object... param) {
+    return loadPreferredObject(classname,null,param);
+  }
+
+  /**
+   * Laed eine Klasse neu und erzeugt eine Objekt-Instanz.
+   * @param classname Klassenname (inkl. Paket)
+   * @param searchPath Pfad, in dem der ClassLoader nach Klassen sucht
+   *                   (wenn <code>null</code>, wird der System-Klassenpfad
+   *                   {@linkplain ClassLoader#getSystemResource(String) ClassLoader.getSystemResource("")}
+   *                   zum Laden der Klasse verwendet)
+   * @param param Parameter fuer den Konstruktor-Aufruf
+   * @exception RuntimeException falls irgendein Fehler auftritt
+   */
+  public static Object loadPreferredObject(String classname, URL searchPath, Object... param) {
+    try {
+      // Klasse laden
+      Class clazz = loadPreferredClass(classname,searchPath);
+//      // Klassen der Konstruktor-Parameter ermitteln
+//      Class[] paramClass = new Class[ param.length ];
+//      for (int i=0; i<paramClass.length; i++)
+//        paramClass[i] = param[i].getClass();
+//      // Konstruktor fuer Parameter ermitteln (erfordert exakes Matching
+//      // zwischen den Klassen der uebergebenen Parameter und der Klassen
+//      // der Konstruktor-Parameter!)
+//      Constructor constructor = clazz.getConstructor(paramClass);
+      // Konstruktor fuer Parameter ausprobieren
+      for ( Constructor constructor : clazz.getConstructors() )
+        try {
+          return constructor.newInstance(param);
+        } catch (IllegalArgumentException err) {
+        }
+        // kein passender Konstruktor gefunden
+        throw new UnsupportedOperationException("No matching constructor found...");
+    } catch (Exception err) {
+      throw new RuntimeException(err);
+    }
+  }
+
+
+  /**
+   * Liefert das Package einer Klasse erweitert um einen relativen Pfad
+   * in Punkt-Notation.
+   * @param clazz eine Klasse
+   * @param pathExt Erweiterung des Pfads
+   */
+  public static String extendPackagePath(Class clazz, String pathExt) {
+    String packagePath = clazz.getPackage().getName();
+    if ( pathExt != null && !pathExt.trim().equals("") )
+      packagePath += "." + pathExt;
+    return packagePath;
+  }
+
+
+  /**
+   * Prueft einen Identifier auf Korrektheit.
+   * @param identifier Identifier
+   * @exception IllegalArgumentException falls der Identifier nicht-erlaubte
+   *            Zeichen enthaelt
+   * @see Character#isJavaIdentifierStart(char)
+   * @see Character#isJavaIdentifierPart(char)
+   */
+  public static void checkIdentifierAndError(String identifier) {
+    if ( !Character.isJavaIdentifierStart( identifier.charAt(0) ) )
+      throw new IllegalArgumentException("Illegal first character '"+identifier.charAt(0)+"'");
+    for (int i=1; i<identifier.length(); i++) {
+      char c = identifier.charAt(i);
+      if ( !Character.isJavaIdentifierPart(c) )
+        throw new IllegalArgumentException("Illegal inner character '"+c+"'");
+    }
+  }
+
+  /**
+   * Prueft einen Identifier auf Korrektheit.
+   * @param identifier Identifier
+   * @see #checkIdentifierAndError(String)
+   */
+  public static boolean checkIdentifier(String identifier) {
+    try {
+      checkIdentifierAndError(identifier);
+      return true;
+    } catch ( Exception err ) {
+      return false;
+    }
+  }
+
+  /**
+   * Sortiert einen Array aus beliebigen Objekten nach der natuerlichen
+   * Sortierung ihrer {@code toString()}-Werte.
+   * @param objects Objekte
+   */
+  public static void sort(Object[] objects) {
+    Arrays.sort( objects, new Comparator<Object>() {
+      public int compare(Object c1, Object c2) {
+        return c1.toString().compareTo(c2.toString());
+      }
+    });
+  }
+
+  /**
+   * Rundet einen Wert auf Nach- oder Vorkommastellen.
+   * @param value Wert
+   * @param digits Anzahl an Nachkommastellen (negative Werte runden auf
+   *               Vorkommastellen, also 10er-, 100er-, usw. Stelle)
+   * @param mode Modus für das Runden (0 = normal Runden, 1 = Aufrunden, -1 = Abrunden;
+   *             wenn nicht angegeben, wird normal gerundet)
+   */
+  public static double round(double value, int digits, int... mode) {
+    int roundMode = 0; // Default: Normal-Runden
+    if ( mode.length > 0 )
+      roundMode = mode[0];
+
+    // 10er-Potenz fuer Nachkommestellen bilden um vor dem Runden
+    // die Nachkommastellen "vor das Komma" zu schieben
+    double digitsFactor = Math.pow(10, digits);
+    value = value * digitsFactor;
+    // Runden
+    if ( roundMode < 0 )
+      value = Math.floor(value);
+    else if ( roundMode > 0 )
+      value = Math.ceil(value);
+    else
+      value = Math.round(value);
+
+    // Nachkommastellen wieder hinter das Komma schieben
+    return value / digitsFactor;
+  }
+
+  /**
+   * Rundet alle Werte einer {@link Collection} auf Nach- oder Vorkommastellen.<br>
+   * <b>Achtung:</b><br>
+   * Es wird eine neue {@link Collection} des gleichen Typs erzeugt und die
+   * gerundeten Werte darin in der Reihenfolge eingefuegt, wie sie von
+   * {@link Collection#iterator()} geliefert werden! Unter umstaenden kann
+   * die urspruengliche Sortierung verloren gehen! Diese Methode sollte also
+   * im Zweifelsfall nur auf automatisch sortierte {@link Collection Collections}
+   * angewandt werden (z.B. {@link TreeSet}).
+   * @param values Werte
+   * @param digits Anzahl an Nachkommastellen (negative Werte runden auf
+   *               Vorkommastellen, also 10er-, 100er-, usw. Stelle)
+   * @param mode Modus für das Runden (0 = normal Runden, 1 = Aufrunden, -1 = Abrunden;
+   *             wenn nicht angegeben, wird normal gerundet)
+   */
+  public static Collection<Number> round(Collection<Number> values, int digits, int... mode) {
+    try {
+      Collection<Number> newCollection = values.getClass().newInstance();
+      for (Number value : values) {
+        newCollection.add( round(value.doubleValue(),digits,mode) );
+      }
+      return newCollection;
+    } catch (Exception err) {
+      throw new IllegalArgumentException("Can not handle collection: "+getSimpleClassName(values),err);
+    }
+  }
+
+  /**
+   * Erzeugt einen Log4j-Logger fuer ein Objekt. Als Identifier fuer den Logger
+   * wird der Klassenname des Objekts verwendet.
+   * @param object ein Objekt
+   * @return Logger mit dem Namen "NULL", falls das uebergebene Objekt {@code null}
+   *         ist
+   */
+  public static Logger createLogger(Object object) {
+    if ( object == null )
+      return Logger.getLogger("");
+    if ( object instanceof Class )
+      return Logger.getLogger( ((Class)object).getName() );
+    return Logger.getLogger( object.getClass().getName() );
+  }
+
+  /**
+   * Erzeugt einen Error- und einen Debug-Eintrag in einem Logger. Der Error-Eintrag
+   * erhaelt nur die Meldung, der Debug-Eintrag auch die detaillierten Informationen
+   * des Fehlers.
+   * @param logger ein Logger
+   * @param mess   Fehlermeldung (wenn {@code null} wird die Message des Fehlers ausgegeben)
+   * @param err    ein Fehler
+   */
+  public static void logDebugError(Category logger, Object mess, Throwable err) {
+    if ( mess == null )
+      mess = err.getClass().getName()+": "+err.getMessage();
+    logger.error(mess);
+    logger.debug(mess,err);
+  }
+
+  /**
+   * Erzeugt einen Error- und einen Debug-Eintrag in einem Logger. Der Error-Eintrag
+   * erhaelt nur die Meldung, der Debug-Eintrag auch die detaillierten Informationen
+   * des Fehlers.
+   * @param logger ein Logger
+   * @param err    ein Fehler
+   */
+  public static void logDebugError(Category logger, Throwable err) {
+    logDebugError(logger,null,err);
+  }
+
+  /**
+   * Erzeugt einen Warn- und einen Debug-Eintrag in einem Logger. Der Warn-Eintrag
+   * erhaelt nur die Meldung, der Debug-Eintrag auch die detaillierten Informationen
+   * des Fehlers.
+   * @param logger ein Logger
+   * @param mess   Fehlermeldung (wenn {@code null} wird die Message des Fehlers ausgegeben)
+   * @param err    ein Fehler
+   */
+  public static void logDebugWarn(Category logger, Object mess, Throwable err) {
+    if ( mess == null )
+      mess = err.getClass().getName()+": "+err.getMessage();
+    logger.warn(mess);
+    logger.debug(mess,err);
+  }
+
+  /**
+   * Erzeugt einen Warn- und einen Debug-Eintrag in einem Logger. Der Warn-Eintrag
+   * erhaelt nur die Meldung, der Debug-Eintrag auch die detaillierten Informationen
+   * des Fehlers.
+   * @param logger ein Logger
+   * @param err    ein Fehler
+   */
+  public static void logDebugWarn(Category logger, Throwable err) {
+    logDebugWarn(logger,null,err);
+  }
+
+}

Added: trunk/src/schmitzm/lang/LimitedVector.java
===================================================================
--- trunk/src/schmitzm/lang/LimitedVector.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/LimitedVector.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,396 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.Vector;
+import java.util.Collection;
+
+import schmitzm.data.event.Invoker;
+import schmitzm.data.event.ObjectTraceable;
+import schmitzm.data.event.AbstractObjectTraceable;
+import schmitzm.data.event.ObjectListener;
+import schmitzm.data.event.ObjectEvent;
+import schmitzm.data.event.GeneralObjectChangeEvent;
+import schmitzm.data.event.ObjectChangeEvent;
+
+/**
+ * Diese Klasse stellt einen {@link Vector} dar, der auf eine bestimmte
+ * Anzahl an Elementen begrenzt ist. Wird die maximale Anzahl ueberschritten
+ * wird automatisch das <b>ERSTE</b> Element der Liste entfernt.<br>
+ * Der {@code LimitedVector} eignet sich z.B. gut zum Speichern der letzten
+ * 10 geladenen Dateien.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LimitedVector<E> extends Vector<E> implements ObjectTraceable {
+  /** Speichert die Anzahl der Elemente, die die Liste aufnehmen kann. */
+  protected int maxLoad = 0;
+
+  /** Flag, ob der Vector ein Objekt mehrmals enthalten darf.<br>
+   *  Standard: {@code true} */
+  protected boolean duplAllowed = true;
+
+  // Einfache Implementierung von ObjectTraceable, um die Methoden der
+  // Listener-Verwaltung nicht doppelt implementieren zu muessen
+  private AbstractObjectTraceable objectTraceableProxy = new AbstractObjectTraceable() {
+  };
+
+  /**
+   * Erzeugt eine leere Liste.
+   * @param maxLoad maximale Anzahl an Elementen, die die Liste aufnehmen kann
+   */
+  public LimitedVector(int maxLoad) {
+    super(maxLoad);
+    setMaxLoad(maxLoad);
+  }
+
+  /**
+   * Liefert die maximale Anzahl an Elementen, die die Liste aufnehmen kann.
+   */
+  public int getMaxLoad() {
+    return maxLoad;
+  }
+
+  /**
+   * Setzt die maximale Anzahl an Elementen, die die Liste aufnehmen kann.
+   * Ist der neue Wert kleiner als der alte, so werden alle ueberzaehligen
+   * Elemente sofort entfernt!
+   * @param maxLoad Maximal-Anzahl an Elementen
+   */
+  public void setMaxLoad(int maxLoad) {
+    this.maxLoad = maxLoad;
+    truncate();
+  }
+
+  /**
+   * Loescht solange das letzte Element der Liste, bis die maximale Anzahl
+   * an Elementen erreicht ist.
+   */
+  protected void truncate() {
+    while( size() > maxLoad )
+      removeElementAt( 0 );
+  }
+
+  /**
+   * Prueft, ob die Liste ein Objekt mehrmals enthalten darf.
+   * @see #setDuplicatedAllowed(boolean)
+   */
+  public boolean getDuplicatedAllowed() {
+    return this.duplAllowed;
+  }
+
+  /**
+   * Bestimmt, ob die Liste ein Objekt mehrmals enthalten darf.<br>
+   * Standard: {@code false}<br>
+   * <b>Bemerkung:</b><br>
+   * Die Aenderung dieser Eigenschaft wirkt sich NICHT auf den aktuellen Inhalt
+   * der Liste aus. Bestehende Duplikate in der Liste bleiben erhalten!!
+   * @param duplAllowed bei {@code false} wird vor dem Einfuegen ein etwaiges
+   *                    Duplikat aus der Liste entfernt
+   */
+  public void setDuplicatedAllowed(boolean duplAllowed) {
+    this.duplAllowed = duplAllowed;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////   Ueberschreiben von Vector   ///////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Fuegt ein Element in die Liste ein. Danach werden alle ueberzaehligen
+   * Element geloescht. Erzeugt ein {@link ObjectChangeEvent} falls sich der
+   * Inhalt des Vectors aendert.<br>
+   * <b>Bemerkung:</b><br>
+   * Diese Methode macht nichts, falls sie bei bereits voll gefuellter Liste
+   * mit Index 0 aufgerufen wird, da in diesem Fall das neue Element sofort
+   * ueberzaehlig ist.<br>
+   * <b>ACHTUNG:</b><br>
+   * Bei automatischer Duplikat-Eleminierung kann diese Methode zu Problemen
+   * fuehren, da ERST die Duplikate geloescht werden und dann die Einfuege-
+   * Operation vorgenommen wird. {@code index} ist dann evt. nicht mehr die
+   * gewuenschte Einfuege-Position!!
+   * @param obj einzufuegendes Objekt
+   * @param index Index an dem das Element eingefuegt wird
+   */
+  public synchronized void insertElementAt(E obj, int index) {
+    if ( size() >= getMaxLoad() && index == 0 )
+      return;
+
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( remove(obj) );
+
+    int oldSize = size(); //
+    super.insertElementAt(obj,index);
+    if ( size() != oldSize ) {
+      truncate();
+      fireEvent( new ObjectChangeEvent(new Invoker(this),null,obj) );
+    }
+  }
+
+  /**
+   * Belegt eine Listenposition neu und erzeugt ein
+   * {@link ObjectChangeEvent}, falls sich der Listen-Inhalt aendert.<br>
+   * <b>ACHTUNG:</b><br>
+   * Bei automatischer Duplikat-Eleminierung kann diese Methode zu Problemen
+   * fuehren, da ERST die Duplikate geloescht werden und dann die Set-
+   * Operation vorgenommen wird. {@code index} ist dann evt. nicht mehr die
+   * gewuenschte Set-Position!!
+   * @param obj neues Listen-Element
+   * @param index Listen-Position
+   */
+  public synchronized void setElementAt(E obj, int index) {
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( remove(obj) );
+
+    E oldObj = this.elementAt(index);
+    super.setElementAt(obj,index);
+    if ( oldObj != obj )
+      fireEvent( new ObjectChangeEvent(new Invoker(this),oldObj,obj) );
+  }
+
+  /**
+   * Belegt eine Listenposition neu und erzeugt ein
+   * {@link ObjectChangeEvent}, falls sich der Listen-Inhalt aendert.<br>
+   * <b>ACHTUNG:</b><br>
+   * Bei automatischer Duplikat-Eleminierung kann diese Methode zu Problemen
+   * fuehren, da ERST die Duplikate geloescht werden und dann die Set-
+   * Operation vorgenommen wird. {@code index} ist dann evt. nicht mehr die
+   * gewuenschte Set-Position!!
+   * @param obj neues Listen-Element
+   * @param index Listen-Position
+   */
+  public synchronized E set(int index, E obj) {
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( remove(obj) );
+
+    E oldObj = super.set(index,obj);
+    if ( oldObj != obj )
+      fireEvent( new ObjectChangeEvent(new Invoker(this),oldObj,obj) );
+    return oldObj;
+  }
+
+  /**
+   * Fuegt ein Element in am Ende der Liste ein. Danach werden alle ueberzaehligen
+   * Element geloescht. Erzeugt ein {@link ObjectChangeEvent} falls
+   * sich der Inhalt des Vectors aendert.
+   * @param obj einzufuegendes Objekt
+   */
+  public synchronized void addElement(E obj) {
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( remove(obj) );
+
+    int oldSize = size();
+    super.addElement(obj);
+    if ( size() != oldSize ) {
+      truncate();
+      fireEvent( new ObjectChangeEvent(new Invoker(this),null,obj) );
+    }
+  }
+
+  /**
+   * Fuegt ein Element am Ende der Liste ein. Danach werden alle ueberzaehligen
+   * Element geloescht. Erzeugt ein {@link ObjectChangeEvent} falls
+   * sich der Inhalt des Vectors aendert.
+   * @param o einzufuegendes Objekt
+   */
+  public synchronized boolean add(E o) {
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( remove(o) );
+
+    boolean changed = super.add(o);
+    if ( changed ) {
+      truncate();
+      fireEvent( new ObjectChangeEvent(new Invoker(this),null,o) );
+    }
+    return changed;
+  }
+
+  /**
+   * Fuegt alle Elemente einer {@link Collection} am Ende der Liste ein.
+   * Danach werden alle ueberzaehligen Element geloescht.  Erzeugt ein
+   * {@link GeneralObjectChangeEvent} falls sich der Inhalt des Vectors aendert.
+   * @param c einzufuegende Objekte
+   */
+  public synchronized boolean addAll(Collection<? extends E> c) {
+    // etwaige Duplikate entfernen
+    if ( !duplAllowed )
+      while( removeAll(c) );
+
+    boolean changed = super.addAll(c);
+    if ( changed ) {
+      truncate();
+      fireEvent( new GeneralObjectChangeEvent(this) );
+    }
+    return changed;
+  }
+
+  /**
+   * Fuegt alle Elemente einer {@link Collection} in die Liste ein.
+   * Danach werden alle ueberzaehligen Element geloescht. Erzeugt ein
+   * {@link GeneralObjectChangeEvent} falls sich der Inhalt des Vectors aendert.<br>
+   * <b>Bemerkung:</b><br>
+   * Diese Methode macht nichts, falls sie bei bereits voll gefuellter Liste
+   * mit Index 0 aufgerufen wird, da in diesem Fall die neuen Elemente auch
+   * die ueberzaehligen sind.
+   * @param index Listen-Position ab der die Elemente eingefuegt werden
+   * @param c einzufuegende Objekte
+   */
+  public synchronized boolean addAll(int index, Collection<? extends E> c) {
+    // etwaiges Duplikat entfernen
+    if ( !duplAllowed )
+      while( removeAll(c) );
+
+    if ( size() >= getMaxLoad() && index == 0 )
+      return false;
+
+    boolean changed = super.addAll(index,c);
+    if ( changed ) {
+      truncate();
+      fireEvent( new GeneralObjectChangeEvent(this) );
+    }
+    return changed;
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#trimToSize()} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}.
+   */
+  public synchronized void trimToSize() {
+    super.trimToSize();
+    fireEvent( new GeneralObjectChangeEvent(this) );
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#setSize(int)} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}.
+   */
+  public synchronized void setSize(int newSize) {
+    super.setSize(newSize);
+    fireEvent( new GeneralObjectChangeEvent(this) );
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#removeElementAt(int)} und erzeugt ein
+   * {@link ObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  public synchronized void removeElementAt(int index) {
+    E oldObj = this.elementAt(index);
+    super.removeElementAt(index);
+    fireEvent( new ObjectChangeEvent(new Invoker(this),oldObj,null) );
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#removeAllElements()} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  public synchronized void removeAllElements() {
+    if ( size() == 0 )
+      return;
+    super.removeAllElements();
+    fireEvent( new GeneralObjectChangeEvent(this) );
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#remove(int)} und erzeugt ein
+   * {@link ObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  public synchronized E remove(int index) {
+    E oldObj = super.remove(index);
+    fireEvent( new ObjectChangeEvent(new Invoker(this),oldObj,null) );
+    return oldObj;
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#removeAll(Collection)} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  public synchronized boolean removeAll(Collection<?> c) {
+    boolean changed =  super.removeAll(c);
+    if ( changed )
+      fireEvent(new GeneralObjectChangeEvent(this));
+    return changed;
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#retainAll(Collection)} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  public synchronized boolean retainAll(Collection<?> c)  {
+    boolean changed =  super.retainAll(c);
+    if ( changed )
+      fireEvent(new GeneralObjectChangeEvent(this));
+    return changed;
+  }
+
+  /**
+   * Verhaelt sich wie {@link Vector#removeRange(int,int)} und erzeugt ein
+   * {@link GeneralObjectChangeEvent}, falls sich der Listen-Inhalt aendert.
+   */
+  protected synchronized void removeRange(int fromIndex, int toIndex) {
+    int oldSize = size();
+    super.removeRange(fromIndex,toIndex);
+    if ( size() != oldSize )
+      fireEvent(new GeneralObjectChangeEvent(this));
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  ////////////   Implementierung von ObjectTraceable   //////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Fuegt dem Objekt einen Listener hinzu, der bei Aenderungen informiert wird.
+   */
+  public void addObjectListener(ObjectListener listener) {
+    this.objectTraceableProxy.addObjectListener(listener);
+  }
+
+  /**
+   * Entfernt einen Listener von dem Objekt.
+   */
+  public void removeObjectListener(ObjectListener listener) {
+    this.objectTraceableProxy.removeObjectListener(listener);
+  }
+
+  /**
+   * Checkt, ob der angegebene Listener dem Objekt zugeordnet ist.
+   */
+  public boolean containsObjectListener(ObjectListener l) {
+    return this.objectTraceableProxy.containsObjectListener(l);
+  }
+
+  /**
+   * Liefert alle Listener eines bestimmten Typs.
+   * @param type Art des Listeners (Filter)
+   */
+  public ObjectListener[] getObjectListener(Class type) {
+    return this.objectTraceableProxy.getObjectListener(type);
+  }
+
+  /**
+   * Informiert alle Listener, dass sich das Objekt (this) geaendert hat.
+   */
+  public void fireEvent(ObjectEvent e) {
+    this.objectTraceableProxy.fireEvent(e);
+  }
+
+  /**
+   * Informiert alle Listener eine bestimmten Klasse, dass sich das Objekt
+   * (this) geaendert hat.
+   */
+  public void fireEvent(ObjectEvent e, Class c) {
+    this.objectTraceableProxy.fireEvent(e, c);
+  }
+
+}

Added: trunk/src/schmitzm/lang/LocaleComparator.java
===================================================================
--- trunk/src/schmitzm/lang/LocaleComparator.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/LocaleComparator.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,51 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.lang;
+
+import java.util.Comparator;
+import java.util.Locale;
+
+/**
+ * {@code Comparator} um {@link Locale Locales} zu vergleichen. Es kann nach
+ * der Beschreibung (in der aktuellen Default-Locale) verglichen werden oder
+ * nach dem Sprach-Code.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LocaleComparator implements Comparator<Locale> {
+  /** Flag, ob nach der {@linkplain Locale#getDisplayName() Beschreibung}
+   *  verglichen wird ({@code true}) oder nach dem
+   *  {@linkplain Locale#toString() Sprach-Code}. */
+  protected boolean compareDesc = false;
+
+  /**
+   * Erzeugt einen neuen {@code Comparator}.
+   * @param compareDesc Flag, ob nach der {@linkplain Locale#getDisplayName() Beschreibung}
+   *                    verglichen wird ({@code true}) oder nach dem
+   *                    {@linkplain Locale#toString() Sprach-Code}.
+   */
+  public LocaleComparator(boolean compareDesc) {
+    this.compareDesc = compareDesc;
+  }
+
+  /**
+   * Fuehrt den Vergleich aus.
+   * @param loc1 eine Locale
+   * @param loc2 eine andere Locale
+   * @return ein negativer Wert fuer {@code loc1 < loc2}, 0 für {@code loc1 = loc2}
+   *         oder ein positiver Wert fuer {@code loc1 > loc2}
+   */
+  public int compare(Locale loc1, Locale loc2) {
+    if ( compareDesc )
+      return loc1.getDisplayName().compareTo(loc2.getDisplayName());
+    return loc1.toString().compareTo(loc2.toString());
+  }
+}

Added: trunk/src/schmitzm/lang/MultiDimArray.java
===================================================================
--- trunk/src/schmitzm/lang/MultiDimArray.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/MultiDimArray.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,298 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.lang.reflect.Array;
+
+/**
+ * Diese Klasse stellt ein Pendant zur Klasse {@link java.lang.reflect.Array} und
+ * stellt direkten Element-Zugriff fuer mehrdimensionales Arrays zur Verfuegung.<br>
+ * <b>Bemerke:</b><br>
+ * Leider kann diese Klasse nicht von <code>java.lang.reflect.Array</code>
+ * abgeleitet werden, da diese als final deklariert ist!!
+ * @see java.lang.reflect.Array
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MultiDimArray {
+
+  /**
+   * Erzeugt einen neuen mehrdimensionalen Array.
+   * Ist aequivalent zu <code>java.lang.reflect.Array.newInstance(Class,int[])</code>.
+   * @param componentType Typ, den der Array speichern kann. Kann auch ein build-in-Type
+   *                      sein (z.B. <code>int.class</code>)
+   * @param size          Groesse des Arrays in allen Dimensionen
+   * @see java.lang.reflect.Array#newInstance(Class,int[])
+   */
+  public static Object newInstance(Class componentType, int[] size) {
+    return Array.newInstance(componentType, size);
+  }
+
+  /**
+   * Erzeugt einen native {@code Object[]...[]} aus einem {@code MultiDimArray}.
+   * @param multiDimArray ein ueber {@code MultiDimArray#newInstance(.)} oder
+   *                      {@code Array.newInstance(.)} erzeugter Array
+   * @return ein {@code Object[]...[]}
+   */
+  public static Object wrapToNativeArray(Object multiDimArray) {
+    int[] dimSizes = getLength(multiDimArray); // Size of the dimensions
+    Object nativeArray = LangUtil.createArray(dimSizes);
+
+    // Calculate the total number of elements in the matrix
+    int elemCount = 1;
+    for (int dimSize : dimSizes)
+      elemCount *= dimSize;
+
+    // put the elements in the array
+    int[] coord = new int[dimSizes.length];
+    for (int i = 0; i < elemCount; i++) {
+      // set the element at coordinate tCoord
+      LangUtil.setArrayValue(
+          nativeArray,
+          get(multiDimArray, coord),
+          coord
+      );
+
+      // increment the coordinate for the element
+      for (int d = 1; d < dimSizes.length; d++)
+        if (++coord[d] < dimSizes[d])
+          break; // Coordinates in dimensionen > d not influenced by the increment
+        else
+          coord[d] = 0; // Initialize coordinate in dimension d and continue
+                        // to increment in dimension d+1 erhoehen
+    }
+
+    return nativeArray;
+  }
+
+  /**
+   * Liefert den Array, in dem die angegebene Koordinate zu finden ist.
+   * Der Wert an der Koordinate kann dann mit der jeweiligen getter/setter-Methode
+   * ermittelt oder gesetzt werden.<br>
+   * z.B. <code>Array.getInt( ArrayUtil.getArray(anArray,c), c[c.length-1] )</code>
+   * @param array mehrdimensionaler Array
+   * @param c     Koordinate
+   */
+  private static Object getArray(Object array, int c[]) {
+    for (int d=0; d<c.length-1; d++)
+      array = Array.get(array,c[d]);
+    return array;
+  }
+
+  /**
+   * Liefert die Dimension des Arrays.
+   * @param array ein Array
+   */
+  public static int getDimension(Object array) {
+    for (int d=0;; d++)
+      try {
+        array = Array.get(array,0);
+      } catch (IllegalArgumentException err) {
+        return d;
+      }
+  }
+
+  /**
+   * Liefert die Groesse des Arrays in allen Dimensionen.
+   * @param array ein Array
+   */
+  public static int[] getLength(Object array) {
+    int[] length = new int[ getDimension(array) ];
+    for (int d=0; d<length.length; d++) {
+      length[d] = Array.getLength( array );
+      array = Array.get(array,0);
+    }
+    return length;
+  }
+
+  /**
+   * Liefert die Groesse des Arrays in der angegebenen Dimension.
+   * @param array ein Array
+   * @param dim   Index einer Dimension (beginnend bei 0!)
+   */
+  public static int getLength(Object array, int dim) {
+    return Array.getLength( getArray(array, new int[dim]) );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static Object get(Object array, int[] c) {
+    return Array.get( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>boolean</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static boolean getBoolean(Object array, int[] c) {
+    return Array.getBoolean( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>byte</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static byte getByte(Object array, int[] c) {
+    return Array.getByte( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>char</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static char getChar(Object array, int[] c) {
+    return Array.getChar( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>double</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static double getDouble(Object array, int[] c) {
+    return Array.getDouble( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>float</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static float getFloat(Object array, int[] c) {
+    return Array.getFloat( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>int</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static int getInt(Object array, int[] c) {
+    return Array.getInt( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>long</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static long getLong(Object array, int[] c) {
+    return Array.getLong( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Liefert ein Object aus einem mehrdimensionalen Array als <code>short</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   */
+  public static short getShort(Object array, int[] c) {
+    return Array.getShort( getArray(array,c), c[c.length-1] );
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     Objekt, da an die Koordinate gesetzt wird
+   */
+  public static void set(Object array, int[] c, Object v) {
+    Array.set( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>boolean</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setBoolean(Object array, int[] c, boolean v) {
+    Array.setBoolean( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>byte</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setByte(Object array, int[] c,  byte v) {
+    Array.setByte( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>char</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setChar(Object array, int[] c, char v) {
+    Array.setChar( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>double</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setDouble(Object array, int[] c, double v) {
+    Array.setDouble( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>float</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setFloat(Object array, int[] c, float v) {
+    Array.setFloat( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>int</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setInt(Object array, int[] c, int v) {
+    Array.setInt( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>long</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setLong(Object array, int[] c, long v) {
+    Array.setLong( getArray(array,c), c[c.length-1], v);
+  }
+
+  /**
+   * Setzt eine Koordinate in einem mehrdimensionalen Array als <code>short</code>.
+   * @param array ein Array
+   * @param c     Koordinate
+   * @param v     neuer Wert
+   */
+  public static void setShort(Object array, int[] c, short v) {
+    Array.setShort( getArray(array,c), c[c.length-1], v);
+  }
+
+
+}

Added: trunk/src/schmitzm/lang/NamedObject.java
===================================================================
--- trunk/src/schmitzm/lang/NamedObject.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/NamedObject.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,31 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Dieses Interface implementieren alle Objekte, die "in irgendeiner Form"
+ * eine Beschreibung (oder einen Namen) besitzen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface NamedObject {
+  /**
+   * Liefert den Namen des Objekts.
+   */
+  public String getName();
+
+  /**
+   * Setzt den Namen des Objekts.
+   * @param name neuer Name
+   */
+  public void setName(String name);
+}

Added: trunk/src/schmitzm/lang/OperationCanceledException.java
===================================================================
--- trunk/src/schmitzm/lang/OperationCanceledException.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/OperationCanceledException.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,36 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+/**
+ * Diese Exception stellt keinen wirklichen Fehler dar, sondern lediglich
+ * eine Information, dass die aktuelle Operation abgebrochen wurde und
+ * eine etwaige Operationen-Folge beendet werden sollte.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationCanceledException extends RuntimeException {
+  /**
+   * Erzeugt eine neue <code>OperationCanceledException</code>.
+   */
+  public OperationCanceledException() {
+    this(null);
+  }
+
+  /**
+   * Erzeugt eine neue <code>OperationCanceledException</code>.
+   * @param mess Fehlermeldung
+   */
+  public OperationCanceledException(String mess) {
+    super(mess);
+  }
+}

Added: trunk/src/schmitzm/lang/PushbackStringTokenizer.java
===================================================================
--- trunk/src/schmitzm/lang/PushbackStringTokenizer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/PushbackStringTokenizer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,136 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.Vector;
+import java.util.StringTokenizer;
+
+/**
+ * Diese Klasse erweitert den {@link StringTokenizer} um eine PushBack-Funktion,
+ * mit der ein Token zurueck auf den Tokenizer gelegt werden kann.<br>
+ * <b>Bemerkung:</b><br>
+ * Solange noch Token im Pushback-Speicher liegen, liefert die Methode
+ * {@link #nextToken(String)} eines diese Token, <u>ungeachtet</u> von den
+ * dieser Methode uebergebenen Delimitern!
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PushbackStringTokenizer extends StringTokenizer {
+  /** Verwaltet die bereits gelesenen Tokens. */
+  protected Vector<String> readTokens = new Vector<String>();
+
+  /** Verwaltet die zurueck gelegten Token. */
+  protected Vector<String> pushedbackTokens = new Vector<String>();
+
+  /**
+   * Erzeugt einen neuen Tokenizer.
+   * @param str          zu parsender String
+   * @param delim        Delimiter
+   * @param returnDelims bestimmt, ob die Delimiter ebenfalls als Token
+   *                     zurueckgeliefert werden
+   */
+  public PushbackStringTokenizer(String str, String delim,  boolean returnDelims) {
+    super(str, delim, returnDelims);
+  }
+
+  /**
+   * Erzeugt einen neuen Tokenizer. Die Delimiter werden nicht als Token
+   * zurueckgegeben.
+   * @param str   zu parsender String
+   * @param delim Delimiter
+   */
+  public PushbackStringTokenizer(String str, String delim) {
+    super(str, delim);
+  }
+
+  /**
+   * Erzeugt einen neuen Tokenizer. Als Delimiter werden {@code " \t\n\r\f"}
+   * verwendet. Diese werden nicht als Token interpretiert.
+   * zurueckgegeben.
+   * @param str zu parsender String
+   */
+  public PushbackStringTokenizer(String str) {
+    super(str);
+  }
+
+  /**
+   * Prueft, ob noch Tokens gelesen werden koennen.
+   */
+  public boolean hasMoreTokens() {
+    return !pushedbackTokens.isEmpty() || super.hasMoreTokens();
+  }
+
+  /**
+   * Liefert das naechste Token.
+   */
+  public String nextToken() {
+    String token = !pushedbackTokens.isEmpty() ? pushedbackTokens.remove(0) : super.nextToken();
+    readTokens.add(0,token);
+    return token;
+  }
+
+  /**
+   * Liefert das naechste Token.<br>
+   * <b>Diese Methode ist nur mit Bedacht zu verwenden, da sie - sofern vorhanden -
+   * das letzte mittels {@link #pushback()} zurueckgelegte Token liefert, egal
+   * welcher Delimiter uebergeben wurde!!</b>
+   * @param delim Delimiter fuer das naechste Token
+   */
+  public String nextToken(String delim) {
+    String token = !pushedbackTokens.isEmpty() ? pushedbackTokens.remove(0) : super.nextToken(delim);
+    readTokens.add(0,token);
+    return token;
+  }
+
+  /**
+   * Liefert denselben Wert wie {@link #hasMoreTokens()}.
+   */
+  public boolean hasMoreElements() {
+    return hasMoreTokens();
+  }
+
+  /**
+   * Liefert denselben Wert wie {@link #nextToken()}.
+   */
+  public Object nextElement() {
+    return nextToken();
+  }
+
+  /**
+   * Liefert die Anzahl an Token, die noch gelesen werden koennen.
+   */
+  public int countTokens() {
+    return pushedbackTokens.size() + super.countTokens();
+  }
+
+  /**
+   * Legt das zuletzt gelesene Token zurueck in den Tokenizer.
+   * @return das zurueckgelegte Token
+   */
+  public String pushback() {
+    String pushbackedToken = readTokens.remove(0);
+    pushedbackTokens.add(0, pushbackedToken );
+    return pushbackedToken;
+  }
+
+  /**
+   * Liefert alle bisher gelesenen Token als Zeichenkette. Diese Methode macht
+   * nur Sinn, wenn auch die Delimiter als Token gelesen werden.
+   */
+  public String getReadTokens() {
+    StringBuffer buffer = new StringBuffer();
+    for (int i=readTokens.size()-1; i>=0; i--)
+      buffer.append(readTokens.elementAt(i));
+    return buffer.toString();
+  }
+
+}

Added: trunk/src/schmitzm/lang/ResourceProvider.java
===================================================================
--- trunk/src/schmitzm/lang/ResourceProvider.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/ResourceProvider.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,454 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.lang;
+
+import java.util.ResourceBundle;
+import java.util.MissingResourceException;
+import org.apache.log4j.Logger;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.Locale;
+import java.util.HashSet;
+import java.net.URL;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.io.PrintWriter;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+
+/**
+ * Stellt den Zugriff auf ein {@link ResourceBundle} zur Verfuegung.
+ * Hierbei muss der Name (Pfad) ein standard-maessig verwendeten Bundles
+ * angebenen werden. Es besteht die Moeglichkeit, dass fehlende Ressourcen
+ * nicht mit einer {@link MissingResourceException} behandelt werden, sondern
+ * mit {@code null}, bzw. einem Alternativ-String ignoriert werden.<br>
+ * Zudem erlaubt es der {@code ResourceProvider}, dass ein alternatives
+ * Resource-Bundle spezifiziert wird ({@link #resetResourceBundle(String,String) resetResourceBundle(..)}).
+ * Ist dieses gesetzt, wird primaer darin nach einer Ressource gesucht. Nur wenn
+ * dort die Ressource nicht gefunden wird, wird auf das Standard-Bundle
+ * zurueckgegriffen.<br>
+ * <br>
+ * <b>Anwendungsbereich:</b><br>
+ * Diverse Standard-Libraries stellen jeweils einen eigenen {@link ResourceProvider}
+ * zur Verfuegung, dessen Bundle im Pfad des Standard-Library hinterlegt ist (z.B.
+ * {@link JFreeChartUtil#RESOURCE}: Ressourcen hinterlegt in
+ * {@code schmitzm.jfree.ResourceBundle_XXX.properties}).<br>
+ * Sollen fuer eine spezielle Applikation diese Ressourcen (u.U. nur teilweise)
+ * abgeaendert oder fuer eine neue Sprache erweitert werden, kann dies geschehen,
+ * ohne Zugriff auf die Standard-Library zu haben. Die Applikation kann
+ * einfach {@link #resetResourceBundle(String,String) resetResourceBundle(..)}
+ * aufrufen und so den Resource-Provider anweisen, zunaechst ein applikationsspezifisches
+ * Bundle zu verwenden (welches in einem appl.spez. Pfad hinterlegt ist).
+ * Alle Library-internen Zugriffe werden durch den {@code ResourceProvider}
+ * umgelenkt. Wenn das appl.spez. Bundle eine Ressource nicht zur Verfuegung stellt,
+ * wird automatisch auf das Standard-Bundle zurueck gegriffen.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ResourceProvider {
+  private static final Logger LOGGER = Logger.getLogger(ResourceProvider.class.getSimpleName());
+
+  /** Standard-Resource-Bundle, auf das zurueckgegriffen wird, wenn
+   *  Ressource im aktuell gesetzten Bundle nicht gefunden wird. */
+  private String defaultResourceBundle = null;
+  /** Alternatives Resource-Bundle, in dem zuerst nach einer
+   *  Ressource gesucht wird. */
+  private String resourceBundle = defaultResourceBundle;
+  /** Prefix, das beim Suchen im alternativen Resource-Bundle jedem Key
+   *  vorangestellt wird. */
+  private String keyPrefix = "";
+  /** Flag, ob fehlende Ressourcen ignoriert werden, oder mit einer
+   *  {@link MissingResourceException} behandelt werden. */
+  private boolean ignoreMissingResource = false;
+  /** Wenn fehlende Ressourcen ignoriert werden, liefert {@link #getObject(String)}
+   *  den Wert {@code null}. {@link #getString(String)} liefert den in dieser
+   *  Variable hinterlegten Alternativ-String. */
+  private String  missingResourceString = null;
+  /** Sprache, die durch das Root-Bundle (Fallback-Bundle) dargestellt wird.
+   *  Nur zu Informationszwecken! */
+  private Locale rootLocale = null;
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider mit dem Namen "ResourceBundle".
+   * Fehlende Ressourcen werden ignoriert und mit {@code null} oder "???"
+   * als Alternativ-String behandelt.
+   * @param defaultResourceBundle Name des Standard Resource-Bundle
+   * @param rootLocale Sprache des Root-Bundles
+   */
+  public ResourceProvider(Class clazz, Locale rootLocale) {
+    this(clazz, rootLocale, true);
+  }
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider mit dem Namen "ResourceBundle".
+   * Wenn fehlende Ressourcen ignoriert werden, wird "???" als Alternativ-String
+   * verwendet.
+   * @param clazz                 Bestimmt das Package in dem das Bundle "RessourceBundle"
+   *                              gesucht wird
+   * @param rootLocale            Sprache des Root-Bundles
+   * @param ignoreMissingResource bestimmt, ob fehlende Ressourcen ignoriert werden
+   *                              oder mit einer {@link MissingResourceException}
+   *                              behandelt werden
+   */
+  public ResourceProvider(Class clazz, Locale rootLocale, boolean ignoreMissingResource) {
+    this(clazz,rootLocale,ignoreMissingResource,"???");
+  }
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider mit dem Namen "ResourceBundle".
+   * @param clazz                 Bestimmt das Package in dem das Bundle "RessourceBundle"
+   *                              gesucht wird
+   * @param rootLocale            Sprache des Root-Bundles
+   * @param ignoreMissingResource bestimmt, ob fehlende Ressourcen ignoriert werden
+   *                              oder mit einer {@link MissingResourceException}
+   *                              behandelt werden
+   * @param missingResourceString Alternativ-String der - wenn fehlende Ressourcen
+   *                              ignoriert werden - von {@link #getString(String)}
+   *                              zurueckgegeben wird
+   */
+  public ResourceProvider(Class clazz, Locale rootLocale, boolean ignoreMissingResource, String missingResourceString) {
+    this(clazz.getPackage().getName()+".ResourceBundle",rootLocale,ignoreMissingResource,missingResourceString);
+  }
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider.
+   * Fehlende Ressourcen werden ignoriert und mit {@code null} oder "???"
+   * als Alternativ-String behandelt.
+   * @param defaultResourceBundle Name des Standard Resource-Bundle
+   * @param rootLocale            Sprache des Root-Bundles
+   */
+  public ResourceProvider(String defaultResourceBundle, Locale rootLocale) {
+    this(defaultResourceBundle,rootLocale,true);
+  }
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider. Wenn fehlende Ressourcen
+   * ignoriert werden, wird "???" als Alternativ-String verwendet.
+   * @param defaultResourceBundle Name des Standard Resource-Bundle
+   * @param rootLocale            Sprache des Root-Bundles
+   * @param ignoreMissingResource bestimmt, ob fehlende Ressourcen ignoriert werden
+   *                              oder mit einer {@link MissingResourceException}
+   *                              behandelt werden
+   */
+  public ResourceProvider(String defaultResourceBundle, Locale rootLocale, boolean ignoreMissingResource) {
+    this(defaultResourceBundle,rootLocale,ignoreMissingResource,"???");
+  }
+
+  /**
+   * Erzeugt neuen neuen Resource-Provider.
+   * @param defaultResourceBundle Name des Standard Resource-Bundle
+   * @param rootLocale            Sprache des Root-Bundles
+   * @param ignoreMissingResource bestimmt, ob fehlende Ressourcen ignoriert werden
+   *                              oder mit einer {@link MissingResourceException}
+   *                              behandelt werden
+   * @param missingResourceString Alternativ-String der - wenn fehlende Ressourcen
+   *                              ignoriert werden - von {@link #getString(String)}
+   *                              zurueckgegeben wird
+   */
+  public ResourceProvider(String defaultResourceBundle, Locale rootLocale, boolean ignoreMissingResource, String missingResourceString) {
+    this.defaultResourceBundle = defaultResourceBundle;
+    this.rootLocale            = rootLocale;
+    this.resourceBundle        = null;
+    this.ignoreMissingResource = ignoreMissingResource;
+    this.missingResourceString = missingResourceString;
+  }
+
+  /**
+   * Liefert den Namen des Bundles.
+   * @see #getBundleName()
+   */
+  public String toString() {
+    return getBundleName();
+  }
+
+  /**
+   * Liefert alle Keys unter denen eine Ressource hinterlegt ist.
+   */
+  public SortedSet<String> getKeys() {
+    String prefix = (keyPrefix != null) ? keyPrefix.trim() : "";
+    if ( !keyPrefix.equals("") )
+      prefix += ".";
+
+    SortedSet<String> keys = new TreeSet<String>();
+    // Alle Keys aus dem Alternativ-Bundle in die Key-Menge aufnehmen, die
+    // mit dem Preafix beginnen
+    try {
+      if ( resourceBundle != null )
+        for ( String key : ResourceBundle.getBundle(resourceBundle).keySet() )
+          if ( key.startsWith(prefix) )
+            keys.add( key.substring(prefix.length()) );
+    } catch (MissingResourceException err) {
+      // Es gibt kein Root-Bundle fuer das Alternativ-Bundle
+      // --> Keys im Alternativ-Bundle ignorieren
+    }
+    // Alle Keys aus dem Standard-Bundle um das Praefix erweitern un
+    // in die Key-Menge aufnehmen
+    for ( String key : ResourceBundle.getBundle(defaultResourceBundle).keySet() )
+      keys.add(key);
+
+    return keys;
+  }
+
+  /**
+   * Prueft, ob fehlende Ressourcen ignoriert werden (mit {@code null} oder
+   * Alternativ-String) oder ob eine {@link MissingResourceException}
+   * geworfen wird.
+   */
+  public boolean isMissingResourceIgnored() {
+    return ignoreMissingResource;
+  }
+
+  /**
+   * Bestimmt, ob fehlende Ressourcen ignoriert werden (mit {@code null} oder
+   * Alternativ-String) oder ob eine {@link MissingResourceException}
+   * geworfen wird.
+   */
+  public void setIgnoreMissingResource(boolean ignoreMissingResource) {
+    this.ignoreMissingResource = ignoreMissingResource;
+  }
+
+  /**
+   * Liefert den Alternativ-String, der beim Ignorieren fehlender
+   * Ressourcen von {@link getString(String)} zurueckgegeben wird.
+   */
+  public String getMissingResourceString() {
+    return missingResourceString;
+  }
+
+  /**
+   * Setzt den Alternativ-String, der beim Ignorieren fehlender
+   * Ressourcen von {@link getString(String)} zurueckgegeben wird.
+   */
+  public void setMissingResourceString(String missingResourceString) {
+    this.missingResourceString = missingResourceString;
+  }
+
+  /**
+   * Liefert ein Objekt aus dem Resource-Bundle. Wenn gesetzt, wird zunaechst im
+   * alternativen Bundle (ggf. mit Prefix) gesucht. Wenn die Ressource dort
+   * nicht gefunden wird, wird auf das Standard-Bundle zurueckgegriffen.
+   * Wenn die Suche (auch) dort nicht erfolgreich ist, wird je nach Einstellung
+   * eine {@link MissingResourceException} geworfen oder {@code null} zurueck
+   * gegeben.
+   * @param key Key unter dem der String gesucht wird
+   */
+  public Object getObject(String key) {
+    String primaryBundle = null;
+    String prefix        = "";
+    // Wenn gesetzt, dann das alternative Bundle (mit Key-Prefix)
+    // verwenden. Ansonsten das Standard-Bundle ohne Prefix.
+    if ( resourceBundle != null ) {
+      primaryBundle = resourceBundle;
+      prefix        = (keyPrefix != null && !keyPrefix.trim().equals("")) ? keyPrefix+"." : "";
+    } else {
+      primaryBundle = defaultResourceBundle;
+      prefix        =  "";
+    }
+
+    try {
+      // Ressource im gesetzten Bundle suchen
+      return ResourceBundle.getBundle(primaryBundle).getObject(prefix+key);
+    } catch (MissingResourceException err) {
+      // Ressource im Standard-Bundle suchen
+      try {
+        return ResourceBundle.getBundle(defaultResourceBundle).getObject(key);
+      } catch (MissingResourceException err2) {
+        // Wenn Ressource auch im Standard-Bundle nicht vorhanden ist,
+        // Fehler werfen (oder ignorieren
+        if ( ignoreMissingResource ) {
+          LOGGER.warn("ResourceBundle "+primaryBundle+": "+err.getMessage());
+          return null;
+        }
+        throw err2;
+      }
+    }
+  }
+
+  /**
+   * Liefert einen String aus dem Resource-Bundle. Wenn gesetzt, wird zunaechst im
+   * alternativen Bundle (ggf. mit Prefix) gesucht. Wenn die Ressource dort
+   * nicht gefunden wird, wird auf das Standard-Bundle zurueckgegriffen.
+   * Wenn die Suche (auch) dort nicht erfolgreich ist, wird je nach Einstellung
+   * eine {@link MissingResourceException} geworfen oder der Alternativ-String
+   * zurueck gegeben.
+   * @param key Key unter dem der String gesucht wird
+   * @param replParams Strings/Zahlen, mit denen die im String enthaltenen
+   *                   Wildcards {@code ${0}}, {@code ${1}}, {@code ${2}}, usw.
+   *                   ersetzt werden (sehr hilfreich z.B. fuer lokalisierte
+   *                   Fehlermeldungen)
+   */
+  public String getString(String key, Object... replParams) {
+    Object object = getObject(key);
+    String string = object != null ? object.toString() : missingResourceString;
+    for (int i=0; i<replParams.length; i++)
+      string = string.replaceAll("\\$\\{"+i+"\\}", replParams[i].toString());
+    return string;
+  }
+
+  /**
+   * Setzt ein alternatives Resource-Bundle, in welchem als erstes nach
+   * einer Ressource gesucht wird. Dieses sollte die gleiche Fall-Back-Sprache
+   * haben, wie das Standard-Bundle.
+   * @param bundle String
+   * @param keyPrefix Prefix, welches bei der Suche im alternativen Bundle
+   *                  automatisch jedem Key vorangestellt wird
+   */
+  public void resetResourceBundle(String bundle, String keyPrefix) {
+    this.resourceBundle = bundle;
+    this.keyPrefix      = keyPrefix;
+  }
+
+  /**
+   * Setzt ein alternatives Resource-Bundle, in welchem als erstes nach
+   * einer Ressource gesucht wird. Dieses sollte die gleiche Fall-Back-Sprache
+   * haben, wie das Standard-Bundle. Ein Key-Prefix wird nicht verwendet.
+   * @param bundle String
+   */
+  public void resetResourceBundle(String bundle) {
+    resetResourceBundle(bundle,null);
+  }
+
+  /**
+   * Entfernt das alternative Resource-Bundle, so dass im folgenden immer
+   * direkt im Standard-Bundle gesucht wird.
+   */
+  public void resetResourceBundle() {
+    resetResourceBundle(null,null);
+  }
+
+  /**
+   * Liefert das Prefix, das im Alternativ-Bundle jedem Key vorangestellt
+   * ist.
+   */
+  public String getKeyPrefix() {
+    return keyPrefix;
+  }
+
+  /**
+   * Liefert den Namen des Resource-Bundle. Wenn ein Alternativ-Bundle gesetzt
+   * wurde, wird dessen Name zurueckgegeben.
+   */
+  public String getBundleName() {
+    return resourceBundle == null ? defaultResourceBundle : resourceBundle;
+  }
+
+  /**
+   * Liefert den Namen des Standard-Resource-Bundle.
+   */
+  public String getDefaultBundleName() {
+    return defaultResourceBundle;
+  }
+
+  /**
+   * Liefert die Standard-Sprache des Resource-Bundles ("Fall-Back").
+   */
+  public Locale getRootLocale() {
+    return rootLocale;
+  }
+
+  /**
+   * Liefert alle Sprachen, die der {@code ResourceProvider} zur Verfuegung
+   * stellt.
+   * @param inclRootLocale   wenn {@link false} wird die "Fall-Back"-Sprache
+   *                         nicht beruecksichtigt
+   */
+  public Set<Locale> getAvailableLocales(boolean inclRootLocale) {
+    return getAvailableLocales(this,inclRootLocale);
+  }
+
+
+  /**
+   * Liefert alle Sprachen, die ein {@link ResourceProvider} zur Verfuegung
+   * stellt.
+   * @param resourceProvider ResourceProvider
+   * @param inclRootLocale   wenn {@link false} wird die "Fall-Back"-Sprache
+   *                         nicht beruecksichtigt
+   */
+  public static Set<Locale> getAvailableLocales(ResourceProvider resourceProvider, boolean inclRootLocale) {
+    Set<Locale> avLocales = new TreeSet<Locale>(new LocaleComparator(true));
+    if ( inclRootLocale && resourceProvider.getRootLocale() != null )
+      avLocales.add( resourceProvider.getRootLocale() );
+
+    // leider sehe ich keine andere Moeglichkeit, als "alle Sprachen"
+    // durchzuprobieren...
+    for ( Locale l : Locale.getAvailableLocales() ) {
+      if ( checkLocaleAvailable( resourceProvider.getDefaultBundleName(),l ) ||
+           checkLocaleAvailable( resourceProvider.getBundleName(),l ) )
+        avLocales.add(l);
+    }
+    return avLocales;
+  }
+
+  private static boolean checkLocaleAvailable(String bundleName, Locale l) {
+    try {
+      return l != null && l.equals( ResourceBundle.getBundle(bundleName,l).getLocale() );
+    } catch (Exception err) {
+    }
+    return false;
+  }
+
+  /**
+   * Liefert die Property-Datei ({@code .properties}) fuer ein Resource Bundle.
+   * @param provider Resource-Provider fuer den das neue Bundle angelegt wird
+   * @param l Sprache des neuen Bundles
+   * @exception URISyntaxException wenn bei der Ermittlung des Datei-Pfads
+   *            ein Fehler auftritt
+   */
+  public static File getPropertyFile(ResourceProvider provider, Locale l) throws URISyntaxException{
+    String bundlePath       = provider.getBundleName();
+    int    lastDotIdx       = bundlePath.lastIndexOf(".");
+    String bundleName       = bundlePath.substring(lastDotIdx+1);
+    String bundlePackage    = bundlePath.substring(0, lastDotIdx).replace(".","/");
+    URL    bundlePackageURL = ClassLoader.getSystemResource(bundlePackage);
+    String newBundleName    = bundleName + "_" + l.toString() + ".properties";
+    File   newBundleFile    = new File(new File(bundlePackageURL.toURI()),newBundleName);
+    return newBundleFile;
+  }
+
+  /**
+   * Erzeugt eine Property-Datei ({@code .properties}) fuer ein Resource Bundle.
+   * In dieser sind bereits alle Keys des Bundles enthalten und mit einem Dummy-Wert
+   * vorbelegt.
+   * @param provider Resource-Provider fuer den das neue Bundle angelegt wird
+   * @param l Sprache des neuen Bundles
+   * @param append wenn {@code true}, wird eine bestehende Datei erweitert,
+   *               andernfalls ueberschrieben
+   */
+  public static void createPropertyFile(ResourceProvider provider, Locale l, boolean append) throws URISyntaxException, FileNotFoundException {
+    File        newBundleFile = getPropertyFile(provider,l);
+    PrintWriter out           = new PrintWriter( new FileOutputStream(newBundleFile, append) );
+    // Wenn Datei neu angelegt wird, wird ein Header erstellt
+    if ( !newBundleFile.exists() || newBundleFile.length() == 0 ) {
+      out.println("# ----------------------------------------------------------------------------------");
+      out.println("#  LANGUAGE: "+l.toString()+" = "+l.getDisplayLanguage());
+      out.println("# ----------------------------------------------------------------------------------");
+    }
+    out.println("# Original bundle: "+provider.getDefaultBundleName());
+
+    // etwaiges Key-Prefix ermitteln
+    String prefix = provider.getKeyPrefix();
+    if ( prefix == null )
+      prefix = "";
+    if ( !prefix.equals("") )
+      prefix += ".";
+
+    // Keys mit Dummy-Werten in Datei schreiben
+    for (String key : provider.getKeys())
+      out.println(prefix+key+"=?"+l.toString()+"? "+provider.getString(key));
+    out.println("# ----------------------------------------------------------------------------------");
+    out.flush();
+    out.close();
+  }
+
+
+}

Added: trunk/src/schmitzm/lang/SortableVector.java
===================================================================
--- trunk/src/schmitzm/lang/SortableVector.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/SortableVector.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,126 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.Collection;
+import java.util.Vector;
+import java.util.Comparator;
+import java.util.Arrays;
+
+/**
+ * Diese Klasse erweitert den {@linkplain Vector Java-Vector} um eine
+ * Sortier-Funktion.
+ * Die Elemente werden (aus Effizienzgruenden) <b>nicht</b> automatisch
+ * sortiert. Ein Aufruf von {@link #sort()} oder {@link #sortInverted()} ist
+ * hierfuer notwendig.
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SortableVector<E> extends Vector<E> {
+  /** {@link Comparator}, der die Sortierung bestimmt. */
+  protected Comparator comparator = null;
+  /** {@link Comparator}, der die invertierte Sortierung bestimmt. */
+  protected Comparator invertedComparator = null;
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}. Es wird der
+   * {@link DefaultComparator} verwendet.
+   */
+  public SortableVector(int initialCapacity, int capacityIncrement) {
+    this(null,initialCapacity, capacityIncrement);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}. Es wird der
+   * {@link DefaultComparator} verwendet.
+   */
+  public SortableVector(int initialCapacity) {
+    this(null,initialCapacity);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}. Es wird der
+   * {@link DefaultComparator} verwendet.
+   */
+  public SortableVector() {
+    this((Comparator)null);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}. Es wird der
+   * {@link DefaultComparator} verwendet.
+   */
+  public SortableVector(Collection c) {
+    this(null, c);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}
+   * @param comparator bestimmt die Sortierung
+   */
+  public SortableVector(Comparator comparator, int initialCapacity, int capacityIncrement) {
+    super(initialCapacity, capacityIncrement);
+    setComparator(comparator);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}
+   * @param comparator bestimmt die Sortierung
+   */
+  public SortableVector(Comparator comparator, int initialCapacity) {
+    super(initialCapacity);
+    setComparator(comparator);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}
+   * @param comparator bestimmt die Sortierung
+   */
+  public SortableVector(Comparator comparator) {
+    super();
+    setComparator(comparator);
+  }
+
+  /**
+   * Erzeugt einen neuen {@code SortableVector}
+   * @param comparator bestimmt die Sortierung
+   */
+  public SortableVector(Comparator comparator, Collection c) {
+    super(c);
+    setComparator(comparator);
+  }
+
+  /**
+   * Belegt die globale Variable {@link #comparator} mit einem neuen Wert.
+   * Wird {@code null} uebergeben, wird der {@link DefaultComparator} verwendet
+   * @param comparator ein Comparator
+   */
+  private void setComparator(Comparator comparator) {
+    this.comparator = (comparator != null) ? comparator : DefaultComparator.DEFAULT;
+    this.invertedComparator = DefaultComparator.invert(this.comparator);
+  }
+
+  /**
+   * Sortiert den Vector.
+   */
+  public void sort() {
+    Arrays.sort( this.elementData, comparator );
+  }
+
+  /**
+   * Sortiert den Vector in der umgekehrten Reihenfolge.
+   */
+  public void sortInverted() {
+    Arrays.sort( this.elementData, invertedComparator );
+  }
+
+}

Added: trunk/src/schmitzm/lang/StepThread.java
===================================================================
--- trunk/src/schmitzm/lang/StepThread.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/StepThread.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,101 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import schmitzm.lang.WorkingThread;
+
+/**
+ * Diese Klasse stellt einen "arbeitenden" Thread dar (im Gegensatz zum
+ * "konkurrierenden" Thread, der mit anderen Threads auf gemeinsame Ressourcen
+ * zugreift). Die Arbeitsweise des <code>StepThread</code> erfolgt durch
+ * immer wiederkehrende Arbeitsschritte. Der Thread kann
+ * <ul>
+ * <li>gestartet werden ({@link #start()})</li>
+ * <li>angehalten werden ({@link #pause()})</li>
+ * <li>fortgesetzt werden ({@link #resume()})</li>
+ * <li>abgebrochen werden ({@link #terminate()})</li>
+ * <li>schrittweise ausgefuehrt werden (step)</li>
+ * </ul>
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public abstract class StepThread extends WorkingThread {
+  /** Modus "Thread fuehrt aktuellen Schritt zu Ende und wechselt dann in
+   *  'Pause'". */
+  public static final int MODE_STEP = 5;
+
+  /**
+   * Erzeugt einen neuen Prozess.
+   * @param name Name fuer den Thread
+   */
+  public StepThread(String name) {
+    super(name);
+  }
+
+  /**
+   * Erzeugt einen neuen Prozess.
+   */
+  public StepThread() {
+    super();
+  }
+
+  /**
+   * Fuehrt einen einzelnen Arbeitsschritt aus. Danach wird der Thread
+   * wieder schlafen gelegt.
+   */
+  public void step() {
+    boolean wasSleeping = isSleeping();
+
+    this.mode = MODE_STEP;
+    // beim allerersten Mal muss der Thread gestartet werden
+    if ( !isAlive() )
+      super.start();
+    else
+      if ( wasSleeping )
+        wakeUp();
+  }
+
+  /**
+   * Implementiert den konitinuierlichen Arbeitsablauf des Threads als eine
+   * Schleife, die fortlaufend {@link #performStep(int)} aufruft.
+   */
+  protected void performWork() {
+    for( int step=1;; step++ ) {
+       try {
+         checkBreakingCommands();
+       }
+       catch (ThreadDeath err) {
+         return;
+       }
+
+       switch (mode) {
+         case MODE_RUN:  performStep(step);
+                         // wurde waehrend der Ausfuehrung des
+                         // Schritts ein Step-Signal gegeben, wird nur
+                         // der aktuelle Schritt beendet und kein neuer
+                         // begonnen.
+                         if ( mode == MODE_STEP )
+                           mode = MODE_PAUSE;
+                         break;
+         case MODE_STEP: performStep(step);
+                         mode = MODE_PAUSE;
+                         break;
+       }
+     }
+
+  }
+
+  /**
+   * Implementiert einen Arbeitsschritt des Threads.
+   */
+  protected abstract void performStep(int stepNo);
+}

Added: trunk/src/schmitzm/lang/WorkingThread.java
===================================================================
--- trunk/src/schmitzm/lang/WorkingThread.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/WorkingThread.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,304 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import java.util.Vector;
+import schmitzm.lang.WorkingThreadListener;
+
+/**
+ * Diese Klasse stellt einen "arbeitenden" Thread dar (im Gegensatz zum
+ * "konkurrierenden" Thread, der mit anderen Threads auf gemeinsame Ressourcen
+ * zugreift). Der <code>WorkingThread</code> kann
+ * <ul>
+ * <li>gestartet werden ({@link #start()})</li>
+ * <li>angehalten werden ({@link #pause()})</li>
+ * <li>fortgesetzt werden ({@link #resume()})</li>
+ * <li>abgebrochen werden ({@link #terminate()})</li>
+ * </ul>
+ * @author Martin Schmitz
+ * @version 1.0
+ */
+public abstract class WorkingThread extends Thread {
+  /** Modus "Thread ist bereit, gestartet zu werden". */
+  public static final int MODE_READY = 1;
+  /** Modus "Thread ist initialisiert, und laeuft". */
+  public static final int MODE_RUN = 2;
+  /** Modus "Thread ist beendet".<br>
+   *  <b>Der Thread kann nicht neu initialisiert oder gestartet werden!</b> */
+  public static final int MODE_TERMINATE = 3;
+  /** Modus "Thread pausiert und kann fortgesetzt werden". */
+  public static final int MODE_PAUSE = 4;
+//  /** Modus "Thread fuehrt aktuellen Schritt zu Ende und wechselt dann in
+//   *  'Pause'". */
+//  public static final int MODE_STEP = 5;
+
+  /** Speichert den aktuellen Thread-Status. */
+  protected int mode = MODE_READY;
+  /** Speichert die Listener, die auf den Thread horchen. */
+  protected Vector listeners = new Vector<WorkingThreadListener>();
+
+  private String name = null;
+
+  /**
+   * Erzeugt einen neuen Thread.
+   * @param name Bezeichnung fuer den Thread
+   */
+  public WorkingThread(String name) {
+    this.name = name;
+  }
+
+  /**
+   * Erzeugt einen neuen Thread.
+   */
+  public WorkingThread() {
+    this(null);
+  }
+
+  /**
+   * Liefert den Status, in dem sich der Thread gerade befindet.
+   */
+  public int getMode() {
+    return mode;
+  }
+
+  /**
+   * Liefert den Namen des Threads. Falls dieser nicht gesetzt ist, wird
+   * <code>super.toString()</code> zurueckgegeben.
+   */
+  public String toString() {
+    return this.name != null ? this.name : super.toString();
+  }
+
+  /**
+   * Legt den Thread schlafen, bis er durch <code>wakeUp()</code>
+   * wieder aktiviert wird. Loest {@link WorkingThread#fireThreadPaused()} aus.
+   * @see #wakeUp()
+   */
+  protected synchronized void goSleep() {
+    try {
+      fireThreadPaused();
+      wait();
+    }
+    catch ( InterruptedException err ) {
+    }
+  }
+
+  /**
+   * Aktiviert den Thread wieder, nachdem er durch <code>goSleep()</code>
+   * Schlafen gelegt wurde. Loest {@link WorkingThread#fireThreadResumed()} aus.
+   * @see #goSleep()
+   */
+  protected synchronized void wakeUp() {
+    notify();
+    fireThreadResumed();
+  }
+
+  /**
+   * Startet den Thread (erneut). Beim allerersten Mal wird die
+   * <code>performInit()</code>-Methode aufgerufen.
+   * @see #performInit()
+   */
+  public void start() {
+    boolean wasSleeping = isSleeping();
+
+    this.mode = MODE_RUN;
+    // beim allerersten Mal muss der Thread gestartet werden
+    if ( !isAlive() ) {
+      performInit();
+      super.start();
+    } else {
+      if ( wasSleeping )
+        wakeUp();
+    }
+  }
+
+  /**
+   * Beendet (temporaer) die Arbeit des Threads. Ueber die
+   * <code>start()</code>- oder <code>step()</code>-Methode kann die Arbeit
+   * wieder aufgenommen werden.
+   */
+  public void pause() {
+    boolean wasSleeping = isSleeping();
+
+    this.mode = MODE_PAUSE;
+    // beim allerersten Mal muss der Thread gestartet werden
+    if ( !isAlive() )
+      super.start();
+    else
+      if ( wasSleeping )
+        wakeUp();
+    checkBreakingCommands();
+  }
+
+  /**
+   * Beendet den Thread, in dem die Run-Methode auslaeuft. Danach kann der
+   * Thread <b>nicht</b> mehr neu gestartet werden.
+   *
+   */
+  public void terminate() {
+    boolean wasSleeping = isSleeping();
+
+    this.mode = MODE_TERMINATE;
+    if ( !isAlive() && !isInterrupted() )
+      super.start();
+    else
+      if ( wasSleeping )
+        wakeUp();
+  }
+
+  /**
+   * Checkt, ob der Thread aktuell am arbeiten ist.
+   */
+  public boolean isRunning() {
+    return mode == MODE_RUN;
+  }
+
+  /**
+   * Checkt, ob der Thread aktuell am warten ist. Dies ist er, wenn er
+   * manuell in den Pause-Mode versetzt wurde.
+   * @see #pause()
+   */
+  public boolean isSleeping() {
+    return mode == MODE_PAUSE;
+  }
+
+  /**
+   * Implementiert den Arbeitsablauf des Threads.
+   * Sollte nicht manuell aufgerufen werden, da der aufrufende Prozess
+   * sonst blockiert. Loest zunaechst {@link WorkingThread#fireThreadStarted()}
+   * aus und {@link WorkingThread#fireThreadStopped()} bevor der Thread
+   * auslaeuft.
+   * @see #start()
+   * @see #performWork()
+   */
+  public void run() {
+    fireThreadStarted();
+    try {
+      // Thread wurde nur gestartet, um auszulaufen!
+      if (mode != MODE_TERMINATE)
+        performWork();
+    } catch (ThreadDeath err) {
+//      err.printStackTrace();
+    }
+    mode = MODE_TERMINATE;
+    performDispose();
+    fireThreadStopped();
+  }
+
+  /**
+   * Prueft, on ein Unterbrechnung-Signal erfolgt ist (<code>pause()</code>
+   * oder <code>terminate()</code>).<br>
+   * Bei <code>pause()</code> wird der Thread sofort schlafen gelegt. Bei
+   * <code>terminate()</code> wird eine <code>ThreadDeath</code>-Exception
+   * geworfen. Diese Methode kann/sollte an geeigneten Stellen im
+   * {@linkplain #performWork() Arbeitsablauf} aufgerufen werden.
+   */
+  protected void checkBreakingCommands() {
+    switch (mode) {
+      case MODE_TERMINATE: throw new ThreadDeath();
+      case MODE_PAUSE: goSleep();
+                       break;
+    }
+  }
+
+  /**
+   * Wird beim allerersten Aufruf der <code>start()</code>-Methode
+   * aufgerufen, um Thread-Komponenten zu initialisieren.
+   * <b>Sollte {@link #fireThreadInitialised()} aufrufen!</b>
+   */
+  protected abstract void performInit();
+
+  /**
+   * Implementiert den Arbeitsablauf des Threads.<br>
+   * Zwischendurch sollte an geeigneter Stelle immer wieder die
+   * {@link #checkBreakingCommands()}-Methode aufgerufen werden, um
+   * auf Pause- oder Terminate-Signale zu reagieren.
+   */
+  protected abstract void performWork();
+
+  /**
+   * Implementiert alle Aktionen, die nach Auslaufen des Threads
+   * durchzufuehren sind. z.B. sollte der Thread alle verteilten Listener
+   * von anderen Objekten wieder entfernen, damit er nicht mehr informiert
+   * wird. Diese Methode wird vor dem Auslaufen der {@link #run()}-Methode
+   * aufgerufen.
+   */
+  protected abstract void performDispose();
+
+  /**
+   * Fuegt einen Listener hinzu.
+   * @return <code>true</code> wenn dass Hinzufuegen erfolgreich war (siehe
+   *         {@link Vector#add(Object)})
+   */
+  public boolean addThreadListener(WorkingThreadListener listener) {
+    return listeners.add(listener);
+  }
+
+  /**
+   * Entfernt einen Listener von dem Thread.
+   * @return <code>true</code> wenn dass Entfernen erfolgreich war (siehe
+   *         {@link Vector#remove(Object)})
+   */
+  public boolean removeThreadListener(WorkingThreadListener listener) {
+    return listeners.remove(listener);
+  }
+
+  /**
+   * Informiert alle {@link WorkingThreadListener} darueber, dass der Thread
+   * initialisiert wurde.
+   */
+  protected void fireThreadInitialised() {
+//    System.err.println("Thread init");
+    for (int i=0; i<listeners.size(); i++)
+      ((WorkingThreadListener)listeners.get(i)).threadInitialised(this);
+  }
+
+  /**
+   * Informiert alle {@link WorkingThreadListener} darueber, dass der Thread
+   * gestartet wurde.
+   */
+  protected void fireThreadStarted() {
+//    System.err.println("Thread started");
+    for (int i=0; i<listeners.size(); i++)
+      ((WorkingThreadListener)listeners.get(i)).threadStarted(this);
+  }
+
+  /**
+   * Informiert alle {@link WorkingThreadListener} darueber, dass der Thread
+   * pausiert.
+   */
+  protected void fireThreadPaused() {
+//    System.err.println("Thread paused");
+    for (int i=0; i<listeners.size(); i++)
+      ((WorkingThreadListener)listeners.get(i)).threadPaused(this);
+  }
+
+  /**
+   * Informiert alle {@link WorkingThreadListener} darueber, dass der Thread
+   * (nach einer Pause) wieder arbeitet.
+   */
+  protected void fireThreadResumed() {
+//    System.err.println("Thread resumed");
+    for (int i=0; i<listeners.size(); i++)
+      ((WorkingThreadListener)listeners.get(i)).threadResumed(this);
+  }
+
+  /**
+   * Informiert alle {@link WorkingThreadListener} darueber, dass der Thread
+   * gestoppt wurde oder ausgelaufen ist.
+   */
+  protected void fireThreadStopped() {
+//    System.err.println("Thread stopped");
+   for (int i=0; i<listeners.size(); i++)
+      ((WorkingThreadListener)listeners.get(i)).threadStopped(this);
+  }
+}

Added: trunk/src/schmitzm/lang/WorkingThreadAdapter.java
===================================================================
--- trunk/src/schmitzm/lang/WorkingThreadAdapter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/WorkingThreadAdapter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,60 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import schmitzm.lang.WorkingThread;
+import schmitzm.lang.WorkingThreadListener;
+
+/**
+ * Diese Klasse stellt eine Basis-Implementierung des Interfaces
+ * {@link WorkingThreadListener} dar. <b>Die implementierten Methoden machen
+ * jedoch allesamt nichts.</b> Die Klasse dient lediglich der Vereinfachung,
+ * dass nicht benoetigte Methoden auch nicht implementiert werden muessen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class WorkingThreadAdapter implements WorkingThreadListener {
+  /**
+   * Wird ausgeloest, nachdem der Thread initialisiert wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadInitialised(WorkingThread thread) {
+  }
+
+  /**
+   * Wird ausgeloest, nachdem der Thread (temporaer) angehalten wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadPaused(WorkingThread thread) {
+  }
+
+  /**
+   * Wird ausgeloest, nachdem der Thread fortgesetzt wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadResumed(WorkingThread thread) {
+  }
+
+  /**
+   * Wird ausgeloest, nachdem der Thread gestartet wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadStarted(WorkingThread thread) {
+  }
+
+  /**
+   * Wird ausgeloest, nachdem der Thread komplett gestoppt hat.
+   * @param thread ausloesender Thread
+   */
+  public void threadStopped(WorkingThread thread) {
+  }
+}

Added: trunk/src/schmitzm/lang/WorkingThreadListener.java
===================================================================
--- trunk/src/schmitzm/lang/WorkingThreadListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/WorkingThreadListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,53 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang;
+
+import schmitzm.lang.WorkingThread;
+
+/**
+ * Diese Klasse stellt einen Listener dar, der bei bestimmten Thread-Aktivitaeten
+ * informiert wird.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface WorkingThreadListener {
+  /**
+   * Wird ausgeloest, nachdem der Thread initialisiert wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadInitialised(WorkingThread thread);
+
+  /**
+   * Wird ausgeloest, nachdem der Thread komplett gestoppt hat.
+   * @param thread ausloesender Thread
+   */
+  public void threadStopped(WorkingThread thread);
+
+  /**
+   * Wird ausgeloest, nachdem der Thread gestartet wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadStarted(WorkingThread thread);
+
+  /**
+   * Wird ausgeloest, nachdem der Thread (temporaer) angehalten wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadPaused(WorkingThread thread);
+
+  /**
+   * Wird ausgeloest, nachdem der Thread fortgesetzt wurde.
+   * @param thread ausloesender Thread
+   */
+  public void threadResumed(WorkingThread thread);
+
+}

Added: trunk/src/schmitzm/lang/package.html
===================================================================
--- trunk/src/schmitzm/lang/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code java.lang}.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/lang/tree/BinaryTreeNode.java
===================================================================
--- trunk/src/schmitzm/lang/tree/BinaryTreeNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/BinaryTreeNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,236 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt einen Knoten in einem Baum dar, der maximal 2 Kindknoten
+ * hat, einen linken und einen rechten.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BinaryTreeNode<E> extends TreeNode<E> {
+  /** Speichert das linke Kind des Knoten. */
+  protected BinaryTreeNode<E> leftChild = null;
+  /** Speichert das rechte Kind des Knoten. */
+  protected BinaryTreeNode<E> rightChild = null;
+
+  /**
+   * Erzeugt einen Wurzel-Knoten ohne Nachfolger.
+   */
+  public BinaryTreeNode() {
+    this((E)null);
+  }
+
+  /**
+   * Erzeugt einen Wurzel-Knoten ohne Nachfolger.
+   * @param object Objekt, das in dem Knoten gespeichert wird
+   */
+  public BinaryTreeNode(E object) {
+    this(object, null);
+  }
+
+  /**
+   * Erzeugt einen Blatt-Knoten.
+   * @param parent direkter Vorgaenger-Knoten
+   */
+  public BinaryTreeNode(BinaryTreeNode<E> parent) {
+    this(null, parent);
+  }
+
+  /**
+   * Erzeugt einen Blatt-Knoten.
+   * @param object Objekt, das in dem Knoten gespeichert wird
+   * @param parent direkter Vorgaenger-Knoten
+   */
+  public BinaryTreeNode(E object, BinaryTreeNode<E> parent) {
+    this(object,parent,null,null);
+  }
+
+  /**
+   * Erzeugt einen inneren Knoten.
+   * @param object Objekt, das in dem Knoten gespeichert wird
+   * @param parent direkter Vorgaenger-Knoten
+   */
+  public BinaryTreeNode(E object, BinaryTreeNode<E> parent, BinaryTreeNode<E> leftChild, BinaryTreeNode<E> rightChild) {
+    super(object,parent);
+    setLeftChild( leftChild );
+    setRightChild( rightChild );
+  }
+
+
+  /**
+   * Liefert den Wurzel-Knoten.
+   */
+  public BinaryTreeNode<E> getRoot() {
+    return (BinaryTreeNode)super.getRoot();
+  }
+
+  /**
+   * Liefert den direkten Vorgaenger-Knoten.
+   */
+  public BinaryTreeNode<E> getParent() {
+    return (BinaryTreeNode)super.getParent();
+  }
+
+  /**
+   * Prueft, ob es sich um einen Blattknoten handelt.
+   * @return <code>true<code> gdw. beide Kinder {@code null} sind
+   */
+  public boolean isLeaf() {
+    // Effizienter als die allgemeine super-Methode
+    return leftChild == null & rightChild == null;
+  }
+
+  /**
+   * Prueft, ob ein Knoten als Vater- oder Kind-Knoten fuer diesen Knoten
+   * verwendet werden kann
+   * @param node zu pruefende Knoten
+   * @exception IllegalArgumentException falls der Knoten nicht geeignet ist
+   */
+  public void checkNode(TreeNode<E> node) {
+    if ( node!=null && !(node instanceof BinaryTreeNode) )
+      throw new IllegalArgumentException("Node must be an BinaryTreeNode!");
+  }
+
+  /**
+   * Liefert die (maximale) Anzahl der Kindknoten.
+   * @return immer 2, unabhaengig davon, ob diese {@code null} sind oder nicht
+   */
+  public int getChildCount() {
+//    int count = 0;
+//    if ( leftChild != null )
+//      count++;
+//    if ( rightChild != null )
+//      count++;
+//    return count;
+      return 2;
+  }
+
+  /**
+   * Liefert einen Kindknoten.
+   * @param i Index (beginnend bei 0)
+   * @return den linken Knoten fuer i = 1, den rechten Knoten sonst
+   */
+  public BinaryTreeNode<E> getChild(int i) {
+    return ( i == 0 ) ? leftChild : rightChild;
+  }
+
+  /**
+   * Setzt einen Kindknoten. Setzt den linken Kind-Knoten fuer i = 1, den rechten
+   * Kind-Knoten sonst
+   * @param i Index (beginnend bei 0)
+   * @exception IllegalArgumentException falls {@code child} kein
+   *            {@code BinaryTreeNode<E>} ist
+   */
+  public void setChild(int i, TreeNode<E> child) {
+    checkNode(child);
+    TreeNode oldChild = null;
+    if ( i == 0 ) {
+      oldChild  = leftChild;
+      leftChild = (BinaryTreeNode<E>)child;
+    } else {
+      oldChild   = rightChild;
+      rightChild = (BinaryTreeNode<E>)child;
+    }
+    // Vater des alten Kind-Konten loeschen
+    if ( oldChild != null )
+      oldChild.setParent(null);
+    // Vater des neuen Kind-Konten setzen
+    if ( child != null )
+      child.setParent(this);
+  }
+
+  /**
+   * Liefert den linken Kindknoten.
+   */
+  public BinaryTreeNode<E> getLeftChild() {
+    return leftChild;
+  }
+
+  /**
+   * Setzt den linken Kindknoten.
+   */
+  public void setLeftChild(BinaryTreeNode<E> child) {
+    setChild(0,child);
+  }
+
+  /**
+   * Liefert den rechten Kindknoten.
+   */
+  public BinaryTreeNode<E> getRightChild() {
+    return rightChild;
+  }
+
+  /**
+   * Setzt den rechten Kindknoten.
+   */
+  public void setRightChild(BinaryTreeNode<E> child) {
+    setChild(1,child);
+  }
+
+  /**
+   * Liefert die im (Teil-)Baum des Knotens gespeicherten Objekte in der
+   * Inorder-Reihenfolge.
+   * @param v Liste in die die Objekte eingefuegt werden (kann {@code null} sein!)
+   */
+  public Vector<E> getObjectsInorder(Vector<E> v) {
+    if ( v == null )
+      v = new Vector<E>();
+
+    if ( leftChild != null )
+      leftChild.getObjectsInorder(v);
+    v.add(this.getObject());
+    if ( rightChild != null )
+      rightChild.getObjectsInorder(v);
+
+    return v;
+  }
+
+  /**
+   * Liefert die im (Teil-)Baum des Knotens gespeicherten Objekte in der
+   * Preorder-Reihenfolge.
+   * @param v Liste in die die Objekte eingefuegt werden (kann {@code null} sein!)
+   */
+  public Vector<E> getObjectsPreorder(Vector<E> v) {
+    if ( v == null )
+      v = new Vector<E>();
+
+    v.add(this.getObject());
+    if ( leftChild != null )
+      leftChild.getObjectsPreorder(v);
+    if ( rightChild != null )
+      rightChild.getObjectsPreorder(v);
+
+    return v;
+  }
+
+  /**
+   * Liefert die im (Teil-)Baum des Knotens gespeicherten Objekte in der
+   * Postorder-Reihenfolge.
+   * @param v Liste in die die Objekte eingefuegt werden (kann {@code null} sein!)
+   */
+  public Vector<E> getObjectsPostorder(Vector<E> v) {
+    if ( v == null )
+      v = new Vector<E>();
+
+    if ( leftChild != null )
+      leftChild.getObjectsPostorder(v);
+    if ( rightChild != null )
+      rightChild.getObjectsPostorder(v);
+    v.add(this.getObject());
+
+    return v;
+  }
+
+}

Added: trunk/src/schmitzm/lang/tree/OperationTree.071011.double
===================================================================
--- trunk/src/schmitzm/lang/tree/OperationTree.071011.double	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/OperationTree.071011.double	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,502 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+import java.util.Random;
+import schmitzm.lang.tree.TreeNode;
+import schmitzm.lang.tree.BinaryTreeNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse stellt einen Operator-Baum dar. Dieser unterstuetzt folgende
+ * Knoten:<br>
+ * <ul>
+ *   <li>{@link ConstantNode}: Blatt-Knoten, die einen konstanten {@code double}-Wert darstellen.</li>
+ *   <li>{@link OperatorNode}: Innere Knoten, die fuer einen 2-stelligen Operator
+ *       auf dem <i>linken</i> und <i>rechten</i> Sohn stehen:
+ *       <ul>
+ *         <li>Addition: {@code "+"}</li>
+ *         <li>Subtraktion: {@code "-"}</li>
+ *         <li>Multiplikation: {@code "*"}</li>
+ *         <li>Division: {@code "/"}</li>
+ *         <li>Potenzbildung: {@code "^"}</li>
+ *         <li>boolesches UND: {@code "&"}</li>
+ *         <li>boolesches ODER: {@code "|"}</li>
+ *         <li>Gleich: {@code "="} oder {@code "=="}</li>
+ *         <li>Ungleich: {@code "!="} oder {@code "<>"}</li>
+ *         <li>Kleiner als: {@code "<"}</li>
+ *         <li>Groesser als: {@code ">"}</li>
+ *         <li>Kleiner gleich: {@code "<="}</li>
+ *         <li>Groesser gleich: {@code ">="}</li>
+ *       </ul></li>
+ *   <li>{@link UnaryOperatorNode}: Innere Knoten, die fuer einen 1-stelligen
+ *       Operator auf dem <i>linken</i> Sohn stehen:
+ *       <ul>
+ *         <li>Betrag: {@code "abs(.)"}</li>
+ *         <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+ *         <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+ *         <li>Abrunden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+ *         <li>boolesches Nicht: {@code "!(.)"}</li>
+ *         <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+ *         <li>Sinus: {@code "sin(.)"}</li>
+ *         <li>Cosinus: {@code "cos(.)"}</li>
+ *         <li>Tangens: {@code "tan(.)"}</li>
+ *         <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+ *         <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+ *         <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+ *         <li>Exponentialfunktion: {@code "exp(.)"}</li>
+ *         <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+ *         <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+ *         </ul></li>
+ *   <li>{@link ConstantAliasNode}: Blatt-Knoten, die einen Alias fuer eine
+ *       Konstante darstellen:
+ *       <ul>
+ *         <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+ *         <li>"Not a number" (NaN): {@code "NaN"}</li>
+ *       </ul></li>
+ *   <li>{@link ITENode}: ITE-Operator:<br>
+ *       Der 3-stellige ITE-Operator {@code ITE(.,.,.)} drueckt die Bedingung
+ *       "If .. Then .. Else .." aus.</li>
+ * </ul>
+ * Damit boolesche Operatoren und arithmetische Ausdruecke kombiniert
+ * werden koennen, werden die Operanden und das Ergebis einer booleschen
+ * Operation nicht als TRUE oder FALSE, sondern numerisch codiert:<br>
+ * <b>Operanden:</b> {@code op > 0} entspricht TRUE; {@code op = 0} entspricht FALSE.<br>
+ * <b>Ergebnis:</b> TRUE wird als 1 codiert; FALSE wird als 0 codiert.
+ * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationTree {
+  /** Speichert den Wurzel-Knoten des Operator-Baums. */
+  protected TreeNode rootNode = null;
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  protected OperationTree(TreeNode root) {
+    this.rootNode = root;
+  }
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  public OperationTree(OperatorNode root) {
+    this((BinaryTreeNode)root);
+  }
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  public OperationTree(ConstantNode root) {
+    this((BinaryTreeNode) root);
+  }
+
+  /**
+   * Liefert den Wurzelknoten des Operator-Baums.
+   */
+  public TreeNode getRoot() {
+    return rootNode;
+  }
+
+  /**
+   * Wertet den Operator-Baum aus.
+   * @return double
+   */
+  public double evaluate() {
+    return evaluate(rootNode);
+  }
+
+  /**
+   * Wertet einen Knoten des Operator-Baums aus.
+   * @param opTreeNode BinaryTreeNode
+   * @return double
+   */
+  protected double evaluate(TreeNode opTreeNode) {
+    if ( opTreeNode == null )
+      throw new NullPointerException("null-TreeNode can not be evaluted");
+
+    if ( opTreeNode instanceof ConstantNode )
+      return ((ConstantNode)opTreeNode).getObject();
+
+    if ( opTreeNode instanceof ConstantAliasNode )
+      return performOperation(((ConstantAliasNode)opTreeNode).getObject());
+
+    if ( opTreeNode instanceof UnaryOperatorNode ) {
+      UnaryOperatorNode operatorNode = (UnaryOperatorNode)opTreeNode;
+      String operator = operatorNode.getObject();
+      double operand  = evaluate( operatorNode.getLeftChild() );
+      return performOperation(operator,operand);
+    }
+
+    if ( opTreeNode instanceof ITENode ) {
+      ITENode operatorNode = (ITENode)opTreeNode;
+      String operator = operatorNode.getObject();
+      if ( evaluate( operatorNode.getIfNode() ) != 0 )
+        return evaluate( operatorNode.getLeftChild() );
+      else
+        return evaluate( operatorNode.getRightChild() );
+    }
+
+    if ( opTreeNode instanceof OperatorNode ) {
+      OperatorNode operatorNode = (OperatorNode)opTreeNode;
+      String operator = operatorNode.getObject();
+      double operand1 = evaluate( operatorNode.getLeftChild() );
+      double operand2 = evaluate( operatorNode.getRightChild() );
+      return performOperation(operator,operand1,operand2);
+    }
+
+    throw new UnsupportedOperationException("Unknown operation tree node: "+opTreeNode.getClass().getName());
+  }
+
+  /**
+   * Wertet einen 2-stelligen Operator aus. Als Operator unterstuetzt diese
+   * Methode
+   * <ul>
+   *   <li>Addition: {@code "+"}</li>
+   *   <li>Subtraktion: {@code "-"}</li>
+   *   <li>Multiplikation: {@code "*"}</li>
+   *   <li>Division: {@code "/"}</li>
+   *   <li>Potenzbildung: {@code "^"}</li>
+   *   <li>Boolesches UND: {@code "&"}, {@code "AND"} oder {@code "UND"}</li>
+   *   <li>Boolesches ODER: {@code "|"}, {@code "OR"} oder {@code "ODER"}</li>
+   *   <li>Gleichheit: {@code "=="}, {@code "="} oder {@code "EQ"}</li>
+   *   <li>Ungleichheit: {@code "!="}, {@code "<>"} oder {@code "NE"}</li>
+   *   <li>Kleiner als: {@code "<"} oder {@code "LT"}</li>
+   *   <li>Groesser als: {@code ">"} oder {@code "GT"}</li>
+   *   <li>Kleiner gleich: {@code "<="} oder {@code "LE"}</li>
+   *   <li>Groesser gleich: {@code ">="} oder {@code GE"}</li>
+   * </ul>
+   * Die boolesche Logik wird im Ergebnis als 1 (TRUE) oder 0 (FALSE) codiert.
+   * Operanden {@code op} werden als FALSE interpretiert, gdw. {@code op == 0}.
+   * @param operator 2-stelliger Operator
+   * @param operand1 linker Operand, auf den der Operator angewendet wird
+   * @param operand2 linker Operand, auf den der Operator angewendet wird
+   * @return {@code operand1} <i>operator</i> {@code operand2}
+   */
+  protected double performOperation(String operator, double operand1, double operand2) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equals("+") )
+      return operand1 + operand2;
+    if ( operator.equals("-") )
+      return operand1 - operand2;
+    if ( operator.equals("*") )
+      return operand1 * operand2;
+    if ( operator.equals("/") )
+      return operand1 / operand2;
+    if ( operator.equals("^") )
+      return Math.pow(operand1,operand2);
+    if ( operator.equals("&") || operator.equals("AND") || operator.equals("UND") )
+      return operand1 != 0 && operand2 != 0 ? 1 : 0;
+    if ( operator.equals("|") || operator.equals("OR") || operator.equals("ODER") )
+      return operand1 != 0 || operand2 != 0 ? 1 : 0;
+    if ( operator.equals("=") || operator.equals("==") || operator.equals("EQ") )
+      return operand1 == operand2 ? 1 : 0;
+    if ( operator.equals("!=") || operator.equals("<>") || operator.equals("NE") )
+      return operand1 != operand2 ? 1 : 0;
+    if ( operator.equals(">") || operator.equals("GT") )
+      return operand1 > operand2 ? 1 : 0;
+    if ( operator.equals(">=") || operator.equals("GE") )
+      return operand1 >= operand2 ? 1 : 0;
+    if ( operator.equals("<") || operator.equalsIgnoreCase("LT") )
+      return operand1 < operand2 ? 1 : 0;
+    if ( operator.equals("<=") || operator.equalsIgnoreCase("LE") )
+      return operand1 <= operand2 ? 1 : 0;
+    throw new UnsupportedOperationException("Unknown operator: "+operator);
+  }
+
+  /**
+   * Wertet einen 1-stelligen Operator aus. Als Operator unterstuetzt diese
+   * Methode
+   * <ul>
+   *   <li>Betrag: {@code "abs(.)"}</li>
+   *   <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+   *   <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+   *   <li>Abrunden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+   *   <li>Boolesches NICHT: {@code "!"}, {@code "NOT"} oder {@code "NICHT"}</li>
+   *   <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+   *   <li>Sinus: {@code "sin(.)"}</li>
+   *   <li>Cosinus: {@code "cos(.)"}</li>
+   *   <li>Tangens: {@code "tan(.)"}</li>
+   *   <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+   *   <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+   *   <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+   *   <li>Exponentialfunktion: {@code "exp(.)"}</li>
+   *   <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+   *   <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+   * </ul>
+   * Die boolesche Logik wird im Ergebnis als 1 (TRUE) oder 0 (FALSE) codiert.
+   * Operanden {@code op} werden als FALSE interpretiert, gdw. {@code op == 0}.
+   * @param operator 1-stelliger Operator
+   * @param operand  Operand auf den der Operator angewendet wird
+   * @return <i>operator</li>( {@code operand} )
+   */
+  protected double performOperation(String operator, double operand) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equals("ISNAN") )
+      return Double.isNaN(operand) ? 1: 0;
+
+    if ( Double.isNaN(operand) )
+      return Double.NaN;
+
+    if ( operator.equals("ABS") )
+      return Math.abs(operand);
+    if ( operator.equals("SQR") || operator.equals("SQRT") )
+      return Math.sqrt(operand);
+    if ( operator.equals("RND") || operator.equals("ROUND") )
+      return Math.round(operand);
+    if ( operator.equals("INT") || operator.equals("TRUNC") )
+      return (long)operand;
+    if ( operator.equals("!") || operator.equals("NOT") || operator.equals("NICHT") )
+      return operand == 0 ? 1 : 0;
+    if ( operator.equals("SIN") )
+      return Math.sin(operand);
+    if ( operator.equals("COS") )
+      return Math.cos(operand);
+    if ( operator.equals("TAN") )
+      return Math.tan(operand);
+    if ( operator.equals("ARCSIN") || operator.equals("ASIN") )
+      return Math.asin(operand);
+    if ( operator.equals("ARCCOS") || operator.equals("ACOS") )
+      return Math.acos(operand);
+    if ( operator.equals("ARCTAN") || operator.equals("ATAN") )
+      return Math.atan(operand);
+    if ( operator.equals("EXP") )
+      return Math.exp(operand);
+    if ( operator.equals("LOG") )
+      return Math.log10(operand);
+    if ( operator.equals("LN") )
+      return Math.log(operand);
+
+    throw new UnsupportedOperationException("Unknown unary operator: "+operator);
+  }
+
+  /**
+   * Wertet einen 0-stelligen Operator (Alias oder Variable) aus.
+   * Als Operator unterstuetzt diese Methode
+   * <ul>
+   *   <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+   * </ul>
+   * @param operator 0-stelliger Operator
+   */
+  protected double performOperation(String operator) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equals("RAND") || operator.equals("RANDOM") )
+      return new Random().nextDouble();
+    if ( operator.equals("NAN") )
+      return Double.NaN;
+    throw new UnsupportedOperationException("Unknown alias operator: "+operator);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////   Knoten im Operator-Baum   /////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Diese Knoten repraesentiert einen 2-stelligen Operator im Operatorbaum.
+   * Der Operator wird durch eine Zeichenkette repraesentiert.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class OperatorNode extends BinaryTreeNode<String> {
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param leftOperand  Knoten der den linker Operand repraesentiert
+     * @param rightOperand Knoten der den rechten Operand repraesentiert
+     */
+    public OperatorNode(String operator, BinaryTreeNode parent, BinaryTreeNode leftOperand, BinaryTreeNode rightOperand) {
+      super(operator, parent, leftOperand, rightOperand);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param leftOperand  Knoten der den linker Operand repraesentiert
+     * @param rightOperand Knoten der den rechten Operand repraesentiert
+     */
+    public OperatorNode(String operator, BinaryTreeNode leftOperand, BinaryTreeNode rightOperand) {
+      this(operator, null, leftOperand, rightOperand);
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen 1-stelligen Operator im Operatorbaum.
+   * Dessen rechter Kind-Knoten ist immer {@code null}. Der Operator wird
+   * durch eine Zeichenkette repraesentiert.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class UnaryOperatorNode extends OperatorNode {
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param operand      Knoten der den (linker) Operand repraesentiert
+     */
+    public UnaryOperatorNode(String operator, BinaryTreeNode parent, BinaryTreeNode operand) {
+      super(operator, parent, operand, null);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param operand      Knoten der den (linker) Operand repraesentiert
+     */
+    public UnaryOperatorNode(String operator, BinaryTreeNode operand) {
+      this(operator, null, operand);
+    }
+
+    /**
+     * Setzt nur den linken Kind-Knoten. Ist der Parameter {@code i > 0}, wird
+     * der rechte Kind-Knoten auf {@code null} gesetzt, unabhaengig vom
+     * Parameter {@code child}.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<String> child) {
+      if ( i > 0 )
+        child = null;
+      super.setChild(i,child);
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert den speziellen Operator ITE
+   * (If-Then-Else) im Operatorbaum. Eigentlich ist dieser Operator 3-stellig.
+   * Um ihn in den binaeren Operator-Baum einzubetten, wird der THEN-Teil im
+   * linken, der ELSE-Teil im rechten Kind und die IF-Bedingung gesondert
+   * im Knoten hinterlegt.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ITENode extends OperatorNode {
+    /** Speichert die IF-Bedingung des ITE-Operators. */
+    protected TreeNode<String> ifNode = null;
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param ifOperand    Knoten der die IF-Bedingung repraesentiert
+     * @param thenOperand  Knoten der die THEN-Auswertung repraesentiert
+     * @param elseOperand  Knoten der die ELSE-Auswertung repraesentiert
+     */
+    public ITENode(String operator, BinaryTreeNode parent, BinaryTreeNode ifOperand, BinaryTreeNode thenOperand, BinaryTreeNode elseOperand) {
+      super(operator, parent, thenOperand, elseOperand);
+      setIfNode(ifOperand);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param ifOperand    Knoten der die IF-Bedingung repraesentiert
+     * @param thenOperand  Knoten der die THEN-Auswertung repraesentiert
+     * @param elseOperand  Knoten der die ELSE-Auswertung repraesentiert
+     */
+    public ITENode(String operator, BinaryTreeNode ifOperand, BinaryTreeNode thenOperand, BinaryTreeNode elseOperand) {
+      this(operator, null, ifOperand, thenOperand, elseOperand);
+    }
+
+    /**
+     * Setzt die IF-Bedingung.
+     * @param cond Knoten der die IF-Bedingung repraesentiert
+     */
+    public void setIfNode(TreeNode<String> cond) {
+      ifNode = cond;
+    }
+
+    /**
+     * Liefert einen Knoten, der die IF-Bedingung repraesentiert
+     */
+    public TreeNode<String> getIfNode() {
+      return ifNode;
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen konstanten numerischen Wert
+   * im Operatorbaum. Dessen Kind-Knoten sind immer {@code null}. Der Wert wird
+   * durch einen {@code double} repraesentiert.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ConstantNode extends BinaryTreeNode<Double> {
+    /**
+     * Erzeugt einen neuen Konstanten-Knoten
+     * @param constant Wert der Konstante
+     * @param parent   Vater-Knoten
+     */
+    public ConstantNode(double constant, BinaryTreeNode parent) {
+      super(constant, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Konstanten-Knoten
+     * @param constant Wert der Konstante
+     */
+    public ConstantNode(double constant) {
+      this(constant, null);
+    }
+
+    /**
+     * Macht nichts, da {@code ConstantNode} immer einen Blatt-Knoten darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<Double> child) {
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen konstanten numerischen Wert
+   * im Operatorbaum, der jedoch nicht direkt, sondern durch einen Alias
+   * dargestellt wird (z.B. "rand" fuer eine Zufallszahl oder ein Variablenname).
+   * Der Knoten hat keine Kind-Knoten (sind immer {@code null}). Der Wert wird
+   * durch einen {@code String} repraesentiert.
+   * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ConstantAliasNode extends BinaryTreeNode<String> {
+    /**
+     * Erzeugt einen neuen Alias-Knoten
+     * @param alias    Alias der Konstante
+     * @param parent   Vater-Knoten
+     */
+    public ConstantAliasNode(String alias, BinaryTreeNode parent) {
+      super(alias, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Alias-Knoten
+     * @param alias Alias der Konstante
+     */
+    public ConstantAliasNode(String alias) {
+      this(alias, null);
+    }
+
+    /**
+     * Macht nichts, da {@code ConstantAliasNode} immer einen Blatt-Knoten darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<String> child) {
+    }
+  }
+
+}

Added: trunk/src/schmitzm/lang/tree/OperationTree.java
===================================================================
--- trunk/src/schmitzm/lang/tree/OperationTree.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/OperationTree.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,793 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+import java.util.Random;
+import java.util.regex.Pattern;
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.tree.TreeNode;
+import schmitzm.lang.tree.BinaryTreeNode;
+
+import org.apache.log4j.Logger;
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt einen Operator-Baum dar. Dieser unterstuetzt folgende
+ * Knoten:<br>
+ * <ul>
+ *   <li>{@link ConstantNode}: Blatt-Knoten, die einen konstanten Wert darstellen
+ *                             (z.B. {@code double} oder {@link String}).</li>
+ *   <li>{@link OperatorNode}: Innere Knoten, die fuer einen 2-stelligen Operator
+ *       auf dem <i>linken</i> und <i>rechten</i> Sohn stehen:
+ *       <ul>
+ *         <li>Addition: {@code "+"}</li>
+ *         <li>Subtraktion: {@code "-"}</li>
+ *         <li>Multiplikation: {@code "*"}</li>
+ *         <li>Division: {@code "/"}</li>
+ *         <li>Potenzbildung: {@code "^"}</li>
+ *         <li>boolesches UND: {@code "&"}</li>
+ *         <li>boolesches ODER: {@code "|"}</li>
+ *         <li>Gleich: {@code "="} oder {@code "=="}</li>
+ *         <li>Ungleich: {@code "!="} oder {@code "<>"}</li>
+ *         <li>Kleiner als: {@code "<"}</li>
+ *         <li>Groesser als: {@code ">"}</li>
+ *         <li>Kleiner gleich: {@code "<="}</li>
+ *         <li>Groesser gleich: {@code ">="}</li>
+ *         <li>String-Konkatenation: {@code "+"}</li>
+ *         <li>Test auf regulaeren Ausdruck: {@code "regex(expression,regular expr)"}</li>
+ *         <li>n-tes Ergebnis eines {@linkplain String#split(String) String-Splits} ueber regulaeren Ausdruck: {@code "split(expression,regular expr,n)"}</li>
+ *         <li>String-Ersetzung: {@code "replAll(expression,pattern expr,repl expr)"}</li>
+ *       </ul></li>
+ *   <li>{@link UnaryOperatorNode}: Innere Knoten, die fuer einen 1-stelligen
+ *       Operator auf dem <i>linken</i> Sohn stehen:
+ *       <ul>
+ *         <li>Betrag: {@code "abs(.)"}</li>
+ *         <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+ *         <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+ *         <li>Abrunden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+ *         <li>boolesches Nicht: {@code "!(.)"}</li>
+ *         <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+ *         <li>Sinus: {@code "sin(.)"}</li>
+ *         <li>Cosinus: {@code "cos(.)"}</li>
+ *         <li>Tangens: {@code "tan(.)"}</li>
+ *         <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+ *         <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+ *         <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+ *         <li>Exponentialfunktion: {@code "exp(.)"}</li>
+ *         <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+ *         <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+ *         <li>Umwandlung Zahl-zu-String: {@code "str(.)"}</li>
+ *         <li>Umwandlung String-zu-Zahl: {@code "val(.)"}</li>
+ *         <li>Umwandlung in Gross-Buchstaben: {@code "toupper(.)"}</li>
+ *         <li>Umwandlung in Klein-Buchstaben: {@code "tolower(.)"}</li>
+ *         </ul></li>
+ *   <li>{@link ConstantAliasNode}: Blatt-Knoten, die einen Alias fuer eine
+ *       Konstante darstellen:
+ *       <ul>
+ *         <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+ *         <li>"Not a number" (NaN): {@code "NaN"}</li>
+ *         <li>Aktuelle Zeit in ms ({@link System#currentTimeMillis()}): {@code "CURR_MILLIS"}</li>
+ *       </ul></li>
+ *   <li>{@link ITENode}: ITE-Operator:<br>
+ *       Der 3-stellige ITE-Operator {@code ITE(.,.,.)} drueckt die Bedingung
+ *       "If .. Then .. Else .." aus.</li>
+ * </ul>
+ * Damit boolesche Operatoren und arithmetische Ausdruecke kombiniert
+ * werden koennen, werden die Operanden und das Ergebis einer booleschen
+ * Operation nicht als TRUE oder FALSE, sondern numerisch codiert:<br>
+ * <b>Operanden:</b> {@code op > 0} entspricht TRUE; {@code op = 0} entspricht FALSE.<br>
+ * <b>Ergebnis:</b> TRUE wird als 1 codiert; FALSE wird als 0 codiert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationTree {
+  /** Logger fuer Debug-Ausgaben */
+  protected Logger LOGGER = LangUtil.createLogger(this);
+
+  /** Speichert den Wurzel-Knoten des Operator-Baums. */
+  protected TreeNode rootNode = null;
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  protected OperationTree(TreeNode root) {
+    this.rootNode = root;
+  }
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  public OperationTree(OperatorNode root) {
+    this((BinaryTreeNode)root);
+  }
+
+  /**
+   * Erzeugt einen neuen Operator-Baum.
+   * @param root Wurzelknoten des Baums
+   */
+  public OperationTree(ConstantNode root) {
+    this((BinaryTreeNode) root);
+  }
+
+  /**
+   * Liefert den Wurzelknoten des Operator-Baums.
+   */
+  public TreeNode getRoot() {
+    return rootNode;
+  }
+
+  /**
+   * Wertet den Operator-Baum aus.
+   */
+  public Object evaluate() {
+    return evaluate(rootNode);
+  }
+
+  /**
+   * Wertet einen Knoten des Operator-Baums aus.
+   * @param opTreeNode BinaryTreeNode
+   */
+  protected Object evaluate(TreeNode opTreeNode) {
+    if ( opTreeNode == null )
+      throw new NullPointerException("null-TreeNode can not be evaluted");
+
+    if ( opTreeNode instanceof ConstantNode )
+      return ((ConstantNode)opTreeNode).getObject();
+
+    if ( opTreeNode instanceof ConstantAliasNode )
+      return performOperation(((ConstantAliasNode)opTreeNode).getObject());
+
+    if ( opTreeNode instanceof UnaryOperatorNode ) {
+      UnaryOperatorNode operatorNode = (UnaryOperatorNode)opTreeNode;
+      String operator = operatorNode.getObject();
+      Object operand  = evaluate( operatorNode.getLeftChild() );
+      return performOperation(operator,operand);
+    }
+
+    if ( opTreeNode instanceof MultiParamOperatorNode ) {
+      MultiParamOperatorNode operatorNode = (MultiParamOperatorNode)opTreeNode;
+      String operator = operatorNode.getObject();
+      // Operanden auswerten
+      Object[] operand = new Object[operatorNode.getChildCount()];
+      for (int i=0; i<operand.length; i++)
+        operand[i] = evaluate( operatorNode.getChild(i) );
+      return performOperation(operator,operand);
+    }
+
+    if ( opTreeNode instanceof ITENode ) {
+      ITENode operatorNode = (ITENode)opTreeNode;
+      Object result = evaluate( operatorNode.getIfNode() );
+      if ( result != null &&
+          (result instanceof Number && ((Number)result).doubleValue() != 0 ||
+           result instanceof Boolean && ((Boolean)result).booleanValue())
+         )
+        return evaluate( operatorNode.getLeftChild() );  // THEN
+      else
+        return evaluate( operatorNode.getRightChild() ); // ELSE
+    }
+
+    if ( opTreeNode instanceof OperatorNode ) {
+      OperatorNode operatorNode = (OperatorNode)opTreeNode;
+      String operator = operatorNode.getObject();
+      Object operand1 = evaluate( operatorNode.getLeftChild() );
+      // Optimierung (2. Operand muss nicht mehr ausgewertet werden!)
+      if ( (operator.equals("*") || operator.equals("/") ) &&
+           (operand1 instanceof Number  && ((Number)operand1).doubleValue() == 0) )
+        return 0;
+      if ( operator.equals("&") &&
+           (operand1 instanceof Number  && ((Number)operand1).doubleValue() == 0 ||
+            operand1 instanceof Boolean && !((Boolean)operand1)) )
+        return 0;
+      if ( operator.equals("|") &&
+           (operand1 instanceof Number  && ((Number)operand1).doubleValue() == 1 ||
+            operand1 instanceof Boolean && ((Boolean)operand1)) )
+        return 1;
+      Object operand2 = evaluate( operatorNode.getRightChild() );
+      return performOperation(operator,operand1,operand2);
+    }
+
+    throw new UnsupportedOperationException("Unknown operation tree node: "+opTreeNode.getClass().getName());
+  }
+
+  /**
+   * Wertet einen n-stelligen Operator aus. Als Operator unterstuetzt diese
+   * Methode
+   * <ul>
+   *   <li><b>String-Operatoren</b>
+   *       <ul>
+   *         <li>Teil-String: {@code "substr(expression,startPos_incl,endPos_excl)"}</li>
+   *       </ul></li>
+   * </ul>
+   * @param operator n-stelliger Operator
+   * @param operand Operanden auf die der Operator angewandt wird
+   * @exception UnsupportedOperationException falls der Operator {@code null} ist,
+   *            unbekannt ist, oder auf die Operanden nicht angewendet werden kann
+   */
+  protected Object performOperation(String operator, Object[] operand) {
+    if ( operator == null )
+      throw new UnsupportedOperationException("Unknown operator: "+operator);
+
+    operator = operator.toUpperCase();
+
+    if ( operator.equals("SUBSTR") ) {
+      if ( operand.length < 2 )
+        throw new UnsupportedOperationException("At least 2 function parameters expected: SUBSTR(..)");
+      if ( !(operand[0] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 1 for SUBSTR(..): String expected");
+      if ( !(operand[1] instanceof Number) )
+        throw new UnsupportedOperationException("Illegal parameter 2 for SUBSTR(..): Number expected");
+      if ( operand.length > 2 && !(operand[2] instanceof Number) )
+        throw new UnsupportedOperationException("Illegal parameter 3 for SUBSTR(..): Number expected");
+
+      String str      = (String)operand[0];
+      int    startPos = ((Number)operand[1]).intValue();
+      if ( operand.length < 3 )
+        return str.substring(startPos);
+      int    endPos = ((Number)operand[2]).intValue();
+      return str.substring(startPos, endPos);
+    }
+
+    if ( operator.equals("SPLIT") ) {
+      if ( operand.length < 2 )
+        throw new UnsupportedOperationException("At least 2 function parameters expected: SUBSTR(..)");
+      if ( !(operand[0] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 1 for SPLIT(..): String expected");
+      if ( !(operand[1] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 2 for SPLIT(..): String expected");
+      if ( operand.length > 2 && !(operand[2] instanceof Number) )
+        throw new UnsupportedOperationException("Illegal parameter 3 for SPLIT(..): Number expected");
+
+      String[] result = operand[0].toString().split(operand[1].toString());
+      int      idx    = operand.length > 2 ? ((Number)operand[2]).intValue() : 0;
+      if ( idx < 0 || idx >= result.length )
+        return "";
+      return result[idx];
+    }
+
+    if ( operator.equals("REPLALL") ) {
+      if ( operand.length != 3 )
+        throw new UnsupportedOperationException("3 function parameters expected: REPLALL(..)");
+      if ( !(operand[0] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 1 for REPLALL(..): String expected");
+      if ( !(operand[1] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 2 for REPLALL(..): String expected");
+      if ( !(operand[2] instanceof String) )
+        throw new UnsupportedOperationException("Illegal parameter 3 for REPLALL(..): String expected");
+
+      String result = operand[0].toString().replaceAll(operand[1].toString(), operand[2].toString());
+      return result;
+    }
+
+    String paramMess = "";
+    for (Object op : operand) {
+      if ( !paramMess.equals("") )
+        paramMess += ", ";
+      paramMess += (op != null) ? op.getClass().getSimpleName() : null;
+    }
+    throw new UnsupportedOperationException("Unknown operator: "+operator+" ("+paramMess+")");
+  }
+
+  /**
+   * Wertet einen 2-stelligen Operator aus. Als Operator unterstuetzt diese
+   * Methode
+   * <ul>
+   *   <li><b>Numerische Operatoren (beide Operatoren numerisch oder boolean)</b>
+   *       <ul>
+   *         <li>Addition: {@code "+"}</li>
+   *         <li>Subtraktion: {@code "-"}</li>
+   *         <li>Multiplikation: {@code "*"}</li>
+   *         <li>Division: {@code "/"}</li>
+   *         <li>Potenzbildung: {@code "^"}</li>
+   *         <li>Boolesches UND: {@code "&"}, {@code "AND"} oder {@code "UND"}</li>
+   *         <li>Boolesches ODER: {@code "|"}, {@code "OR"} oder {@code "ODER"}</li>
+   *       </ul></li>
+   *   <li><b>Vergleichsoperatoren (beide Operatoren vom gleichen Typ!)</b>
+   *       <ul>
+   *         <li>Gleichheit: {@code "=="}, {@code "="} oder {@code "EQ"}</li>
+   *         <li>Ungleichheit: {@code "!="}, {@code "<>"} oder {@code "NE"}</li>
+   *       </ul></li>
+   *   <li><b>Vergleichsoperatoren (beide Operatoren {@link Comparable})</b>
+   *       <ul>
+   *         <li>Kleiner als: {@code "<"} oder {@code "LT"}</li>
+   *         <li>Groesser als: {@code ">"} oder {@code "GT"}</li>
+   *         <li>Kleiner gleich: {@code "<="} oder {@code "LE"}</li>
+   *         <li>Groesser gleich: {@code ">="} oder {@code GE"}</li>
+   *       </ul></li>
+   *   <li><b>String-Operatoren (beide Operatoren String!)</b>
+   *       <ul>
+   *         <li>String-Konkatenation: {@code "+"}</li>
+   *         <li>Test auf regulaeren Ausdruck: {@code "regex(expression,regular expr)"}
+   *       </ul></li>
+   * </ul>
+   * Die boolesche Logik wird im Ergebnis als 1 (TRUE) oder 0 (FALSE) codiert.
+   * Operanden {@code op} werden als FALSE interpretiert, gdw. {@code op == 0}.
+   * @param operator 2-stelliger Operator
+   * @param operand1 linker Operand, auf den der Operator angewendet wird
+   * @param operand2 linker Operand, auf den der Operator angewendet wird
+   * @return {@code op1} <i>operator</i> {@code op2} oder {@code null}, wenn beide
+   *         Operanden {@code null} sind
+   * @exception UnsupportedOperationException falls der Operator {@code null} ist,
+   *            unbekannt ist, oder auf die Operanden nicht angewendet werden kann
+   */
+  protected Object performOperation(String operator, Object operand1, Object operand2) {
+    if ( operand1 == null && operand2 == null )
+      return null;
+    if ( operator == null )
+      throw new UnsupportedOperationException("Unknown operator: "+operator);
+
+    operator = operator.toUpperCase();
+
+    // numerische und boolesche Operationen
+    if ( (operand1 instanceof Number || operand1 instanceof Boolean) &&
+          operand2 instanceof Number || operand2 instanceof Boolean) {
+      // Operanden in double umwandeln
+      double op1 = operand1 instanceof Number ? ((Number)operand1).doubleValue() : (Boolean)operand1 ? 1 : 0;
+      double op2 = operand2 instanceof Number ? ((Number)operand2).doubleValue() : (Boolean)operand2 ? 1 : 0;
+      if (operator.equals("+"))
+        return op1 + op2;
+      if (operator.equals("-"))
+        return op1 - op2;
+      if (operator.equals("*"))
+        return op1 * op2;
+      if (operator.equals("/"))
+        return op1 / op2;
+      if (operator.equals("^"))
+        return Math.pow(op1, op2);
+      if (operator.equals("&") || operator.equals("AND") || operator.equals("UND"))
+        return op1 != 0 && op2 != 0 ? 1 : 0;
+      if (operator.equals("|") || operator.equals("OR") || operator.equals("ODER"))
+        return op1 != 0 || op2 != 0 ? 1 : 0;
+    }
+
+    // Boolean erst in numerischen Wert umwandeln
+    if ( operand1 instanceof Boolean )
+      operand1 = (Boolean)operand1 ? 1 : 0;
+    if ( operand2 instanceof Boolean )
+      operand2 = (Boolean)operand2 ? 1 : 0;
+    // Vergleichsoperatoren funktionieren nicht korrekt, wenn die Objekte
+    // nicht vom gleichen Typ sind (z.B. Double und Long)
+    // -> in Double umwandeln
+    if ( operand1 instanceof Number )
+      operand1 = ((Number)operand1).doubleValue();
+    if ( operand2 instanceof Number )
+      operand2 = ((Number)operand2).doubleValue();
+
+    // Un/Gleichheit (bei Typ-Gleichheit)
+    if (operator.equals("=") || operator.equals("==") || operator.equals("EQ"))
+      return operand1 != null && operand1.equals(operand2) ||
+             operand2 != null && operand2.equals(operand1) ? 1 : 0;
+    if (operator.equals("!=") || operator.equals("<>") || operator.equals("NE"))
+      return operand1 != null && !operand1.equals(operand2) ||
+             operand2 != null && !operand2.equals(operand1) ? 1 : 0;
+
+    // Vergleichsoperatoren
+    if ( operand1 instanceof Comparable && operand2 instanceof Comparable ) {
+      Comparable op1 = (Comparable)operand1;
+      Comparable op2 = (Comparable)operand2;
+      if (operator.equals(">") || operator.equals("GT"))
+        return op1 != null && op1.compareTo(op2) > 0 ? 1 : 0;
+      if (operator.equals(">=") || operator.equals("GE"))
+        return op1 != null && op1.compareTo(op2) >= 0 ? 1 : 0;
+      if (operator.equals("<") || operator.equalsIgnoreCase("LT"))
+        return op1 != null && op1.compareTo(op2) < 0 ? 1 : 0;
+      if (operator.equals("<=") || operator.equalsIgnoreCase("LE"))
+        return op1 != null && op1.compareTo(op2) <= 0 ? 1 : 0;
+    }
+
+    // String-Operationen
+    if ( operand1 instanceof String || operand2 instanceof String ) {
+      if ( operator.equals("+") )
+        return operand1.toString() + operand2.toString();
+      if ( operator.equals("REGEX") ) {
+//        LOGGER.debug(++i+"   "+operand1);
+//        boolean ret = Pattern.matches(operand2.toString(), operand1.toString());
+        return Pattern.matches(operand2.toString(), operand1.toString()) ? 1 : 0;
+      }
+    }
+
+    String op1Class = (operand1 != null) ? operand1.getClass().getSimpleName() : null;
+    String op2Class = (operand2 != null) ? operand2.getClass().getSimpleName() : null;
+    throw new UnsupportedOperationException("Unknown operator: "+operator+" ("+op1Class+", "+op2Class+")");
+  }
+
+  /**
+   * Wertet einen 1-stelligen Operator aus. Als Operator unterstuetzt diese
+   * Methode
+   * <ul>
+   *   <li><b>Numerische Operatoren (Operand numerisch oder boolean)</b>
+   *       <ul>
+   *         <li>Betrag: {@code "abs(.)"}</li>
+   *         <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+   *         <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+   *         <li>Abrunden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+   *         <li>Boolesches NICHT: {@code "!"}, {@code "NOT"} oder {@code "NICHT"}</li>
+   *         <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+   *         <li>Sinus: {@code "sin(.)"}</li>
+   *         <li>Cosinus: {@code "cos(.)"}</li>
+   *         <li>Tangens: {@code "tan(.)"}</li>
+   *         <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+   *         <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+   *         <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+   *         <li>Exponentialfunktion: {@code "exp(.)"}</li>
+   *         <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+   *         <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+   *         <li>Umwandlung Zahl-zu-String: {@code "val(.)"}</li>
+   *       </ul></li>
+   *   <li><b>String Operatoren (Operand String)</b>
+   *       <ul>
+   *         <li>Umwandlung String-zu-Zahl: {@code "str(.)"}</li>
+   *         <li>Umwandlung in Gross-Buchstaben: {@code "toupper(.)"}</li>
+   *         <li>Umwandlung in Klein-Buchstaben: {@code "tolower(.)"}</li>
+   *       </ul></li>
+   * </ul>
+   * Die boolesche Logik wird im Ergebnis als 1 (TRUE) oder 0 (FALSE) codiert.
+   * Operanden {@code op} werden als FALSE interpretiert, gdw. {@code op == 0}.
+   * @param operator 1-stelliger Operator
+   * @param operand  Operand auf den der Operator angewendet wird
+   * @return <i>operator</li>( {@code operand} )
+   * @exception UnsupportedOperationException falls der Operator {@code null} ist,
+   *            unbekannt ist, oder auf den Operanden nicht angewendet werden kann
+   */
+  protected Object performOperation(String operator, Object operand) {
+    if ( operator == null )
+      throw new UnsupportedOperationException("Unknown operator: "+operator);
+
+    operator = operator.toUpperCase();
+
+    // numerische und boolesche Operationen
+    if ( operand instanceof Number || operand instanceof Boolean ) {
+      // Operanden in double umwandeln
+      double op = operand instanceof Number ? ((Number)operand).doubleValue() : (Boolean)operand ? 1 : 0;
+      if ( operator.equals("ISNAN") )
+        return Double.isNaN(op) ? 1: 0;
+      if ( Double.isNaN(op) )
+        return Double.NaN;
+      if ( operator.equals("ABS") )
+        return Math.abs(op);
+      if ( operator.equals("SQR") || operator.equals("SQRT") )
+        return Math.sqrt(op);
+      if (operator.equals("RND") || operator.equals("ROUND"))
+        return Math.round(op);
+      if (operator.equals("INT") || operator.equals("TRUNC"))
+        return (long) op;
+      if (operator.equals("!") || operator.equals("NOT") || operator.equals("NICHT"))
+        return op == 0 ? 1 : 0;
+      if (operator.equals("SIN"))
+        return Math.sin(op);
+      if (operator.equals("COS"))
+        return Math.cos(op);
+      if (operator.equals("TAN"))
+        return Math.tan(op);
+      if (operator.equals("ARCSIN") || operator.equals("ASIN"))
+        return Math.asin(op);
+      if (operator.equals("ARCCOS") || operator.equals("ACOS"))
+        return Math.acos(op);
+      if (operator.equals("ARCTAN") || operator.equals("ATAN"))
+        return Math.atan(op);
+      if (operator.equals("EXP"))
+        return Math.exp(op);
+      if (operator.equals("LOG"))
+        return Math.log10(op);
+      if (operator.equals("LN"))
+        return Math.log(op);
+      if (operator.equals("STR"))
+        return String.valueOf(op);
+    }
+
+    // String Operationen
+    if ( operand instanceof String ) {
+      String op = (String)operand;
+      if (operator.equals("VAL"))
+        return Double.parseDouble(op);
+      if (operator.equals("LEN"))
+        return op.length();
+      if (operator.equals("TOUPPER"))
+        return op.toUpperCase();
+      if (operator.equals("TOLOWER"))
+        return op.toLowerCase();
+    }
+    String opClass = (operand != null) ? operand.getClass().getSimpleName() : null;
+    throw new UnsupportedOperationException("Unknown operator: "+operator+"("+opClass+")");
+  }
+
+  /**
+   * Wertet einen 0-stelligen Operator (Alias oder Variable) aus.
+   * Als Operator unterstuetzt diese Methode
+   * <ul>
+   *   <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+   * </ul>
+   * @param operator 0-stelliger Operator
+   */
+  protected Object performOperation(String operator) {
+    operator = operator.toUpperCase();
+
+    if ( operator.equals("RAND") || operator.equals("RANDOM") )
+      return new Random().nextDouble();
+    if ( operator.equals("NAN") )
+      return Double.NaN;
+    if ( operator.equals("CURR_MILLIS") )
+      return System.currentTimeMillis();
+    throw new UnsupportedOperationException("Unknown alias operator: "+operator);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////   Knoten im Operator-Baum   /////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Diese Knoten repraesentiert einen 2-stelligen Operator im Operatorbaum.
+   * Der Operator wird durch eine Zeichenkette repraesentiert.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class OperatorNode extends BinaryTreeNode<String> {
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param leftOperand  Knoten der den linker Operand repraesentiert
+     * @param rightOperand Knoten der den rechten Operand repraesentiert
+     */
+    public OperatorNode(String operator, BinaryTreeNode parent, BinaryTreeNode leftOperand, BinaryTreeNode rightOperand) {
+      super(operator, parent, leftOperand, rightOperand);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param leftOperand  Knoten der den linker Operand repraesentiert
+     * @param rightOperand Knoten der den rechten Operand repraesentiert
+     */
+    public OperatorNode(String operator, BinaryTreeNode leftOperand, BinaryTreeNode rightOperand) {
+      this(operator, null, leftOperand, rightOperand);
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen 1-stelligen Operator im Operatorbaum.
+   * Dessen rechter Kind-Knoten ist immer {@code null}. Der Operator wird
+   * durch eine Zeichenkette repraesentiert.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class UnaryOperatorNode extends OperatorNode {
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param operand      Knoten der den (linker) Operand repraesentiert
+     */
+    public UnaryOperatorNode(String operator, BinaryTreeNode parent, BinaryTreeNode operand) {
+      super(operator, parent, operand, null);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param operand      Knoten der den (linker) Operand repraesentiert
+     */
+    public UnaryOperatorNode(String operator, BinaryTreeNode operand) {
+      this(operator, null, operand);
+    }
+
+    /**
+     * Setzt nur den linken Kind-Knoten. Ist der Parameter {@code i > 0}, wird
+     * der rechte Kind-Knoten auf {@code null} gesetzt, unabhaengig vom
+     * Parameter {@code child}.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<String> child) {
+      if ( i > 0 )
+        child = null;
+      super.setChild(i,child);
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert den speziellen Operator ITE
+   * (If-Then-Else) im Operatorbaum. Eigentlich ist dieser Operator 3-stellig.
+   * Um ihn in den binaeren Operator-Baum einzubetten, wird der THEN-Teil im
+   * linken, der ELSE-Teil im rechten Kind und die IF-Bedingung gesondert
+   * im Knoten hinterlegt.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ITENode extends OperatorNode {
+    /** Speichert die IF-Bedingung des ITE-Operators. */
+    protected TreeNode<String> ifNode = null;
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param ifOperand    Knoten der die IF-Bedingung repraesentiert
+     * @param thenOperand  Knoten der die THEN-Auswertung repraesentiert
+     * @param elseOperand  Knoten der die ELSE-Auswertung repraesentiert
+     */
+    public ITENode(String operator, BinaryTreeNode parent, BinaryTreeNode ifOperand, BinaryTreeNode thenOperand, BinaryTreeNode elseOperand) {
+      super(operator, parent, thenOperand, elseOperand);
+      setIfNode(ifOperand);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param ifOperand    Knoten der die IF-Bedingung repraesentiert
+     * @param thenOperand  Knoten der die THEN-Auswertung repraesentiert
+     * @param elseOperand  Knoten der die ELSE-Auswertung repraesentiert
+     */
+    public ITENode(String operator, BinaryTreeNode ifOperand, BinaryTreeNode thenOperand, BinaryTreeNode elseOperand) {
+      this(operator, null, ifOperand, thenOperand, elseOperand);
+    }
+
+    /**
+     * Setzt die IF-Bedingung.
+     * @param cond Knoten der die IF-Bedingung repraesentiert
+     */
+    public void setIfNode(TreeNode<String> cond) {
+      ifNode = cond;
+    }
+
+    /**
+     * Liefert einen Knoten, der die IF-Bedingung repraesentiert
+     */
+    public TreeNode<String> getIfNode() {
+      return ifNode;
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert den speziellen Operator mit mehr als 2
+   * Operanden.<br>
+   * <b>Bemerkung:<b> Eigentlich "vergewaltigt" dieser Knoten den
+   * {@link BinaryTreeNode} (bzw. {@link OperatorNode}), da er nicht mehr
+   * binaer ist. Ich habe jedoch im Moment keine Zeit, den {@link OperationTreeParser}
+   * so umzuprogrammieren, dass er nur noch auf dem allgemeinen {@link TreeNode}
+   * basiert.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class MultiParamOperatorNode extends OperatorNode {
+    /** Speichert die Parameter des Operators. */
+    protected BinaryTreeNode<String>[] paramNodes = new BinaryTreeNode[0];
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param paramCount   Anzahl an Parameters des Operators
+     */
+    public MultiParamOperatorNode(String operator, BinaryTreeNode parent, int paramCount) {
+      super(operator, parent, null, null);
+      paramNodes = new BinaryTreeNode[paramCount];
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param paramCount   Anzahl an Parameters des Operators
+     */
+    public MultiParamOperatorNode(String operator, int paramCount) {
+      this(operator, null, paramCount);
+    }
+
+    /**
+     * Erzeugt einen neuen Operator-Knoten
+     * @param operator     Operator
+     * @param parent       Vater-Knoten
+     * @param parameter    Knoten die die Funktionsparameter darstellen
+     */
+    public MultiParamOperatorNode(String operator, BinaryTreeNode parent, BinaryTreeNode... parameter) {
+      this(operator, parent, parameter.length);
+      paramNodes = parameter;
+    }
+
+    /**
+     * Setzt den Knoten fuer einen Funktionsparameter.
+     * @param i int Nummer des Parameters
+     * @param paramNode Knoten der den Parameter darstellt
+     */
+    public void setChild(int i, TreeNode<String> paramNode) {
+      checkNode(paramNode);
+      if ( paramNodes != null && i < paramNodes.length )
+        this.paramNodes[i] = (BinaryTreeNode)paramNode;
+    }
+
+    /**
+     * Liefert den Knoten fuer einen Funktionsparameter.
+     * @param i int Nummer des Parameters
+     */
+    public BinaryTreeNode getChild(int i) {
+      return paramNodes != null && i < paramNodes.length ? this.paramNodes[i] : null;
+    }
+
+    /**
+     * Liefert die Anzahl an Parametern.
+     */
+    public int getChildCount() {
+      return paramNodes != null ? this.paramNodes.length : 0;
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen konstanten Wert
+   * im Operatorbaum. Dessen Kind-Knoten sind immer {@code null}. Der Wert wird
+   * durch einen {@code double} repraesentiert.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ConstantNode extends BinaryTreeNode {
+    /**
+     * Erzeugt einen neuen Konstanten-Knoten
+     * @param constant Wert der Konstante
+     * @param parent   Vater-Knoten
+     */
+    public ConstantNode(Object constant, BinaryTreeNode parent) {
+      super(constant, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Konstanten-Knoten
+     * @param constant Wert der Konstante
+     */
+    public ConstantNode(Object constant) {
+      this(constant, null);
+    }
+
+    /**
+     * Macht nichts, da {@code ConstantNode} immer einen Blatt-Knoten darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode child) {
+    }
+  }
+
+  /**
+   * Diese Knoten repraesentiert einen konstanten numerischen Wert
+   * im Operatorbaum, der jedoch nicht direkt, sondern durch einen Alias
+   * dargestellt wird (z.B. "rand" fuer eine Zufallszahl oder ein Variablenname).
+   * Der Knoten hat keine Kind-Knoten (sind immer {@code null}). Der Wert wird
+   * durch einen {@code String} repraesentiert.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class ConstantAliasNode extends BinaryTreeNode<String> {
+    /**
+     * Erzeugt einen neuen Alias-Knoten
+     * @param alias    Alias der Konstante
+     * @param parent   Vater-Knoten
+     */
+    public ConstantAliasNode(String alias, BinaryTreeNode parent) {
+      super(alias, parent);
+    }
+
+    /**
+     * Erzeugt einen neuen Alias-Knoten
+     * @param alias Alias der Konstante
+     */
+    public ConstantAliasNode(String alias) {
+      this(alias, null);
+    }
+
+    /**
+     * Macht nichts, da {@code ConstantAliasNode} immer einen Blatt-Knoten darstellt.
+     * @param i Index (beginnend bei 0)
+     * @param child neuer Kind-Knoten
+     */
+    public void setChild(int i, TreeNode<String> child) {
+    }
+  }
+
+}

Added: trunk/src/schmitzm/lang/tree/OperationTreeParser.071011.double
===================================================================
--- trunk/src/schmitzm/lang/tree/OperationTreeParser.071011.double	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/OperationTreeParser.071011.double	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,825 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.PushbackStringTokenizer;
+import schmitzm.lang.tree.OperationTree.*;
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse stellt einen Parser fuer einen {@linkplain OperationTree Operator-Baum}
+ * dar. Dieser erstellt einen {@linkplain OperationTree Operator-Baum} aus einem
+ * Formel-String, der folgende Teilen bestehen darf:
+ * <ul>
+ *   <li>konstante numerische Werte: {@link ConstantNode}</li>
+ *       <ul>
+ *           <li>{@code rand} oder {@code random} als Alias fuer eine Zufallszahl
+ *                zwischen 0 und 1: {@link ConstantAliasNode}</li>
+ *           <li>"Not a number" (NaN): {@code "NaN"}</li>
+ *       </ul></li>
+ *   <li>den 2-stelligen arithmetischen Operatoren: {@link OperatorNode}
+ *       <ul>
+ *          <li>Addition: {@code "+"}</li>
+ *          <li>Subtraktion: {@code "-"}</li>
+ *          <li>Multiplikation: {@code "*"}</li>
+ *          <li>Division: {@code "/"}</li>
+ *          <li>Potenzbildung: {@code "^"}</li>
+ *        </ul></li>
+ *   <li>den 2-stelligen booleschen Operatoren: {@link OperatorNode}
+ *       <ul>
+ *          <li>boolesches UND: {@code "&"}</li>
+ *          <li>boolesches ODER: {@code "|"}</li>
+ *          <li>Gleich: {@code "="} oder {@code "=="}</li>
+ *          <li>Ungleich: {@code "!="} oder {@code "<>"}</li>
+ *          <li>Kleiner als: {@code "<"}</li>
+ *          <li>Groesser als: {@code ">"}</li>
+ *          <li>Kleiner gleich: {@code "<="}</li>
+ *          <li>Groesser gleich: {@code ">="}</li>
+ *        </ul></li>
+ *   <li>die 1-stelligen Operatoren: {@link UnaryOperatorNode}
+ *       <ul>
+ *          <li>Betrag: {@code "abs(.)"}</li>
+ *          <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+ *          <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+ *          <li>Abschneiden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+ *          <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+ *          <li>Sinus: {@code "sin(.)"}</li>
+ *          <li>Cosinus: {@code "cos(.)"}</li>
+ *          <li>Tangens: {@code "tan(.)"}</li>
+ *          <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+ *          <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+ *          <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+ *          <li>Exponentialfunktion: {@code "exp(.)"}</li>
+ *          <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+ *          <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+ *        </ul></li>
+ *   <li>den 1-stelligen booleschen Operatoren: {@link UnaryOperatorNode}
+ *       <ul>
+ *          <li>boolesches NICHT: {@code "!(.)"}</li>
+ *        </ul></li>
+ *   <li>dem 3-stelligen Operator ITE(.,.,.): {@link ITENode}<br>
+ *       Der ITE-Operator drueckt eine Bedingung "If .. Then .. Else .." aus.</li>
+ * </ul>
+ * <br>
+ * Damit boolesche Operatoren und arithmetische Ausdruecke kombiniert
+ * werden koennen, werden die Operanden und das Ergebis einer booleschen
+ * Operation nicht als TRUE oder FALSE, sondern numerisch codiert:<br>
+ * <b>Operanden:</b> {@code op > 0} entspricht TRUE; {@code op = 0} entspricht FALSE.<br>
+ * <b>Ergebnis:</b> TRUE wird als 1 codiert; FALSE wird als 0 codiert.
+ * @author <a href="mailto:schmitzm at bonn.edu">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationTreeParser {
+  private Logger LOG = Logger.getLogger(this.getClass());
+
+  /** Stellt einen Stack dar, auf den die geoeffneten Klammern gelegt werden.
+   *  Zeichen 0 stellt das oberste Stack-Element dar. */
+  protected StringBuffer openBrackets = null;
+
+  /** Tokenizer auf den Formel-String. Wird beim Starten des Parse-Vorgangs
+   *  erzeugt.
+   *  @see #parse(String)
+   */
+  protected PushbackStringTokenizer tok = null;
+  /** Entaehlt die Delimiter des Tokenizers {@link #tok}. Diese setzen sich
+   *  aus den oeffnenden und schliessenden Klammern zusammen, sowie aus den
+   *  Operatoren. Wird bei der Instanziierung des {@code OperationTreeParser}
+   *  befuellt.
+   *  @see #getOpeningBracketChars()
+   *  @see #getClosingBracketChars()
+   *  @see #getOperatorChars()
+   */
+  protected String                  delimiter    = null;
+
+  public OperationTreeParser() {
+    delimiter = getOpeningBracketChars() +
+                getClosingBracketChars() +
+                getParameterSeperatorChars() +
+                getOperatorChars() + "\n";
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////   Konfigurations-Methoden   /////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert die Zeichen, die als oeffnende Klammern verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"([{"</code>
+   */
+  public String getOpeningBracketChars() {
+    return "([{";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine oeffnende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getOpeningBracketChars()
+   */
+  public boolean isOpeningBracket(String token) {
+    return getOpeningBracketChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als schliessende Klammern verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>")]}"</code>
+   */
+  public String getClosingBracketChars() {
+    return ")]}";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine schliessende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getClosingBracketChars()
+   */
+  public boolean isClosingBracket(String token) {
+    return getClosingBracketChars().contains(token);
+  }
+
+  /**
+   * Prueft, ob eine oeffnende und eine schliessende Klammer zueinander
+   * korrespondieren.
+   * Unterklassen muessen diese Methode ueberschreiben und erweitern, wenn
+   * weitere Klammer-Zeichen verwendet werden.
+   * @param openBracket oeffnende Klammer
+   * @param closeBracket schliessende Klammer
+   */
+  public boolean checkCorrespondingBrackets(char openBracket, char closeBracket) {
+    return openBracket == '(' && closeBracket == ')' ||
+           openBracket == '[' && closeBracket == ']' ||
+           openBracket == '{' && closeBracket == '}';
+  }
+
+  /**
+   * Liefert die Zeichen, die als Trennzeichen zwischen Funktionsparametern
+   * verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>",;"</code>
+   */
+  public String getParameterSeperatorChars() {
+    return ",;";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine schliessende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getClosingBracketChars()
+   */
+  public boolean isParameterSeperator(String token) {
+    return getParameterSeperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als 2-stellige Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   */
+  public String getOperatorChars() {
+    return getAdditionOperatorChars() +
+           getMultiplyOperatorChars() +
+           getCompareOperatorChars() +
+           getBooleanOperatorChars();
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Operator handelt.
+   * @param token Zeichenkette
+   * @see #getOperatorChars()
+   */
+  public boolean isOperatorChar(String token) {
+    return getOperatorChars().contains(token);
+  }
+
+
+  /**
+   * Liefert die Zeichen, die als arithmetische Punkt-Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"/*^"</code>
+   */
+  public String getMultiplyOperatorChars() {
+    return "/*^";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Punkt-Operator handelt.
+   * @param token Zeichenkette
+   * @see #getMultiplyOperatorChars()
+   */
+  public boolean isMultiplyOperator(String token) {
+    return getMultiplyOperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als arithmetische Strich-Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"+-"</code>
+   */
+  public String getAdditionOperatorChars() {
+    return "+-";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Strich-Operator handelt.
+   * @param token Zeichenkette
+   * @see #getAdditionOperatorChars()
+   */
+  public boolean isAdditionOperator(String token) {
+    return getAdditionOperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als boolesche Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return {@code "&|!"}
+   */
+  public String getBooleanOperatorChars() {
+    return "&|!";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen booleschen Operator handelt.
+   * @param token Zeichenkette
+   */
+  public boolean isBooleanOperator(String token) {
+    return getBooleanOperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die fuer Vergleichsoperatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return {@code "=<>!"}
+   */
+  public String getCompareOperatorChars() {
+    return "=<>!";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Vergleichsoperator handelt.
+   * @param token Zeichenkette
+   */
+  public boolean isCompareOperator(String token) {
+    return token.equals("=") ||
+           token.equals("==") ||
+           token.equals("!=") ||
+           token.equals("<>") ||
+           token.equals(">") ||
+           token.equals(">=") ||
+           token.equals("<") ||
+           token.equals("<=");
+  }
+
+
+
+  /**
+   * Liefert einen Wert, der die Konnektivitaet (Bindung) eines Operators ausdrueckt (je
+   * groesser, desto groesser die Bindung). Punkt-Operatoren haben die
+   * groesste Bindung fuer die Operanden, werden also als erstes ausgewertet.
+   * Darauf folgen die Strich-Operatoren, dann die Vergleichsoperatoren und
+   * am Ende die booleschen Operatoren
+   * @param operator Operator
+   * @return 0, falls kein bekannter Operator uebergeben wird
+   */
+  public int getOperatorConnectivity(String operator) {
+    // Punkt-Operatoren haben den hoechste verbindende Prioritaet
+    if ( isMultiplyOperator(operator) )
+      return 1000;
+    // Strich-Operatoren haben Vorrang vor Vergleichen oder booleschen Operatoren
+    if ( isAdditionOperator(operator) )
+      return 100;
+    // Vergleichs-Operatoren haben Vorrang vor booleschen Operatoren
+    if ( isCompareOperator(operator) )
+      return 10;
+    // boolesche Operatoren haben den geringesten Rang
+    if ( isBooleanOperator(operator) ) {
+      if ( operator.equals("!") )
+        return 3;
+      if ( operator.equals("&") )
+        return 2;
+      if ( operator.equals("|") )
+        return 1;
+    }
+
+    return 0;
+  }
+
+  /**
+   * Liefert den Konnektivitaetswert des am meisten bindenden Operators.
+   * Unterklasse muessen diese Methode u.U. ueberschreiben, wenn durch
+   * Ueberschreiben der Methode {@link #getOperatorConnectivity(String)}
+   * die Prioritaeten der Operatoren veraendert werden.
+   */
+  protected int getMaximumOperatorConnectivity() {
+    return 1000;
+  }
+
+  /**
+   * Liefert die 2-stellige Operatoren, die zwei Operanden aneinander
+   * binden (Punkt-Operatoren).
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"/*^"</code>
+   * @deprecated wegen booleschen Operatoren muss eine Verbindungs-Prioritaet
+                 verwendet werden; ersetzt durch #getOperatorConnectivity(String)
+   */
+  public String getConnectingOperatorChars() {
+    return "/*^";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem 2-stelligen Operator um einen verbindenen
+   * Operator handelt (Punkt-Operator).
+   * @param operator 2-stelliger Operator
+   * @see #getConnectingOperatorChars()
+   * @deprecated wegen booleschen Operatoren muss eine Verbindungs-Prioritaet
+                 verwendet werden; ersetzt durch #getOperatorConnectivity(String)
+   */
+  public boolean isConnectingOperator(String operator) {
+    return getConnectingOperatorChars().contains(operator);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  ///////////////////   Methoden fuer Parsen  ///////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Erstellt einen Operator-Baum aus einem Formel-String.
+   * @param rule Formel
+   */
+  public OperationTree parse(String rule) {
+    openBrackets = new StringBuffer();
+    // Probleme, wenn Formel mit einem geklammerten Ausdruck beginnt
+    // z.B. "(17+7)*2+1" -> *2+1 wird nicht ausgewertet
+    // Loesung: Gesamten Ausdruck nochmal klammern
+    rule = "("+rule.trim()+")";
+    tok  = new PushbackStringTokenizer(rule,delimiter,true);
+    return new OperationTree( parseRulePart() );
+  }
+
+//  /**
+//   * Parst die naechste Teil-Formel aus dem Tokenizer {@link #tok}. Eine (geklammerter)
+//   * Teilformel ist eine durch Strich-Operatoren verbundene Kette von
+//   * {@linkplain #parseOperand() Operanden}.
+//   */
+//  protected final BinaryTreeNode parseRulePart() {
+//    BinaryTreeNode rulePart    = null;
+//    BinaryTreeNode nextOperand = null;
+//    String         operator    = null;
+//
+//    // Ersten (linken) Operand parsen
+//    String token = nextNonWSToken();
+//    // oeffnende Klammer
+//    if ( isOpeningBracket(token) )
+//      openBrackets.insert(0,token);
+//    else
+//      tok.pushback();
+//
+//    rulePart = parseOperand();
+//    while ( tok.hasMoreTokens() ) {
+//      // Operator parsen (kann nur ein Strich- oder Bool-Operator sein!)
+//      operator = nextNonWSToken();
+//
+//      // Sonderfall: schliessende Klammer
+//      // -> Klammerung ueberpruefen und Teil-Formel beenden
+//      if ( isClosingBracket(operator) ) {
+//        if ( !checkCorrespondingBrackets(openBrackets.charAt(0),operator.charAt(0)) )
+//          throwParseError("Bracket '" + operator + "' can not close '" + openBrackets.charAt(0) + "'");
+//        openBrackets.deleteCharAt(0);
+//        return rulePart;
+//      }
+//
+//      // Pruefen, ob es wirklich ein Operator ist
+//      if ( !isOperatorChar(operator) )
+//        throwParseError("Illegal operator '" + operator + "'");
+//
+//      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+//      String nToken = nextNonWSToken();
+//      if ( operator.equals("<") && nToken.equals("=") ||
+//           operator.equals(">") && nToken.equals("=") ||
+//           operator.equals("=") && nToken.equals("=") ||
+//           operator.equals("!") && nToken.equals("=") ||
+//           operator.equals("<") && nToken.equals(">") )
+//        operator += nToken;
+//      else
+//        tok.pushback();
+//
+//      // Naechsten Operand parsen
+//      nextOperand = parseOperand();
+//
+//      // Teil zusammensetzen
+//      rulePart = new OperationTree.OperatorNode(operator,rulePart,nextOperand);
+//    }
+//    // keine Token mehr, aber noch offene Klammern
+//    if ( openBrackets.length() > 0 )
+//      throwParseError("Bracket"+(openBrackets.length() > 1 ? "s" : "")+" '"+openBrackets.toString()+"' not closed!");
+//    return rulePart;
+//  }
+//
+//  /**
+//   * Parst den naechsten Operand aus dem Tokenizer {@link #tok}. Ein Operand ist eine
+//   * durch Punkt-Operatoren verbundene Kette von {@linkplain #parseLiteral() Literalen}
+//   * und geklammerten {@linkplain #parseRulePart() Teil-Formeln}.<br>
+//   */
+//  protected final BinaryTreeNode parseOperand() {
+//    BinaryTreeNode operand = null;
+//    BinaryTreeNode nextLiteral = null;
+//    String operator = null;
+//
+//    // Erstes (linkes) Literal parsen
+//    String token = nextNonWSToken();
+//    // oeffnende Klammer direkt am Anfang
+//    // -> Literal besteht aus kompletter Teilformel
+//    if (isOpeningBracket(token)) {
+//      tok.pushback();
+//      operand = parseRulePart();
+//    } else {
+//      // Sonderfall: Operand startet mit Minus-Zeichen (z.B. '-(1+2)' oder '-abs(-4)')
+//      //             -> folgende
+//      if ( token.equals("-") )
+//        operand = new OperationTree.OperatorNode("*", new OperationTree.ConstantNode(-1.0),  parseOperand());
+//      else {
+//        tok.pushback();
+//        operand = parseLiteral();
+//      }
+//    }
+//    while (tok.hasMoreTokens()) {
+//      // Operator parsen
+//      operator = nextNonWSToken();
+//      // wenn schliessende Klammer oder Strich-/Bool-Operator
+//      // -> Operand beenden
+//      if (isClosingBracket(operator) || !isMultiplyOperator(operator)) {
+//        tok.pushback();
+//        return operand;
+//      }
+//      // Pruefen, ob es wirklich ein Operator ist
+//      if ( !isOperatorChar(operator) )
+//        throwParseError("Illegal operator '" + operator + "'");
+//
+//      // Naechstes Literal
+//      token = nextNonWSToken();
+//      tok.pushback();
+//      // oeffnende Klammer
+//      // -> naechstes Literal ist eine komplette Teilformel
+//      if (isOpeningBracket(token))
+//        nextLiteral = parseRulePart();
+//      else
+//        nextLiteral = parseLiteral();
+//
+//      // Operand zusammensetzen
+//      operand = new OperationTree.OperatorNode(operator, operand, nextLiteral);
+//    }
+//
+//    return operand;
+//  }
+
+  /**
+   * Liesst ein weiteres Zeichen aus dem Tokenizer und fuegt dies gegebenfalls
+   * dem Operator hinzu. Dies ist z.B. bei den Vergleichsoperatoren
+   * <=, >=, ==, != und == notwendig.
+   * @param operator Operator
+   */
+  private String expandOperator(String operator) {
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      String nToken = nextNonWSToken();
+      if ( operator.equals("<") && nToken.equals("=") ||
+           operator.equals(">") && nToken.equals("=") ||
+           operator.equals("=") && nToken.equals("=") ||
+           operator.equals("!") && nToken.equals("=") ||
+           operator.equals("<") && nToken.equals(">") )
+        operator += nToken;
+      else
+        tok.pushback();
+      return operator;
+  }
+
+  /**
+   * Prueft, ob der aktuelle Operand oder die Teil-Formel aufrund einer
+   * schliessende Klammer oder einem Parameter-Trennzeichen abgeschlossen
+   * werden muss.
+   * @param token aktuelles Zeichen
+   */
+  private boolean checkRuleClosing(String token) {
+    // Operand/Teil-Formel wird nur bei schliessender Klammer oder
+    // Parameter-Trennzeichen beendet
+    if ( !isClosingBracket(token) &&
+         !isParameterSeperator(token) )
+      return false;
+
+    // aktuelle Klammerung ermitteln
+    if ( openBrackets.length() == 0 )
+        throwParseError("'" + token + "' not expected!");
+    char openBracket = openBrackets.charAt(0);
+
+    // Schliessende Klammer
+    if ( isClosingBracket(token) ) {
+      // Wenn keine oeffnende Klammer auf Stack liegt (sondern ein Parameter-Zaehler!)
+      // kann Klammer nicht schliessen, da noch ein Parameter erwartet wird
+      if ( !isOpeningBracket(String.valueOf(openBracket)) )
+        throwParseError(openBracket+" more function parameters expected. Bracket can not close!'");
+      if ( !checkCorrespondingBrackets(openBracket,token.charAt(0)) )
+        throwParseError("Bracket '" + token + "' can not close '" + openBracket + "'");
+      return true;
+    }
+
+    // Trenner zwischen Funktions-Parametern
+    if ( isParameterSeperator(token) ) {
+      if ( isOpeningBracket(String.valueOf(openBracket)) )
+        throwParseError(token+" not expected!'");
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Parst die naechste Teil-Formel aus dem Tokenizer {@link #tok}. Eine (geklammerter)
+   * Teilformel ist eine durch Strich-Operatoren verbundene Kette von
+   * {@linkplain #parseOperand(int) Operanden}.
+   */
+  protected final BinaryTreeNode parseRulePart() {
+    BinaryTreeNode rulePart    = null;
+    BinaryTreeNode nextOperand = null;
+    String         operator    = null;
+
+    // Ersten (linken) Operand parsen
+    String token = nextNonWSToken();
+    // oeffnende Klammer
+    if ( isOpeningBracket(token) )
+      openBrackets.insert(0,token);
+    else
+      tok.pushback();
+
+    rulePart = parseOperand( getMaximumOperatorConnectivity() );
+    while ( tok.hasMoreTokens() ) {
+      // Operator parsen
+      operator = nextNonWSToken();
+
+      // schliessende Klammer oder Parameter-Trenner beendet die Teil-Formel
+      if ( checkRuleClosing(operator) ) {
+        // schliessende Klammer vom Stack entfernen
+        if ( isClosingBracket(operator) )
+          openBrackets.deleteCharAt(0);
+        return rulePart;
+      }
+
+      // Pruefen, ob es wirklich ein Operator ist
+      if ( !isOperatorChar(operator) )
+        throwParseError("Illegal operator '" + operator + "'");
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      operator = expandOperator(operator);
+
+      // Naechsten Operand parsen
+      nextOperand = parseOperand( getOperatorConnectivity(operator) );
+
+      // Teil zusammensetzen
+      rulePart = new OperationTree.OperatorNode(operator,rulePart,nextOperand);
+    }
+    // keine Token mehr, aber noch offene Klammern
+    if ( openBrackets.length() > 0 )
+      throwParseError("Bracket"+(openBrackets.length() > 1 ? "s" : "")+" '"+openBrackets.toString()+"' not closed!");
+    return rulePart;
+  }
+
+  /**
+   * Parst den naechsten Operand aus dem Tokenizer {@link #tok}. Ein Operand ist eine
+   * durch Operatoren gleicher Konnektivitaet verbundene Kette von
+   * {@linkplain #parseLiteral() Literalen} und geklammerten
+   * {@linkplain #parseRulePart() Teil-Formeln}.<br>
+   * @param operandConn Konnektivitaet fuer den Operanden
+   */
+  protected final BinaryTreeNode parseOperand(int operandConn) {
+    BinaryTreeNode operand = null;
+    BinaryTreeNode nextLiteral = null;
+    String operator = null;
+    // Erstes (linkes) Literal parsen
+    String token = nextNonWSToken();
+    // oeffnende Klammer direkt am Anfang
+    // -> Literal besteht aus kompletter Teilformel
+    if (isOpeningBracket(token)) {
+      tok.pushback();
+      operand = parseRulePart();
+    } else {
+      // Sonderfall: Operand startet mit Minus-Zeichen (z.B. '-(1+2)' oder '-abs(-4)')
+      //             -> folgende
+      if ( token.equals("-") )
+        operand = new OperationTree.OperatorNode("*", new OperationTree.ConstantNode(-1.0),  parseOperand(getOperatorConnectivity("*")));
+      else {
+        tok.pushback();
+        operand = parseLiteral();
+      }
+    }
+    while (tok.hasMoreTokens()) {
+      // Operator parsen
+      operator = nextNonWSToken();
+      // schliessende Klammer oder Parameter-Trenner beendet den Operand
+      if ( checkRuleClosing(operator) ) {
+        tok.pushback();
+        return operand;
+      }
+
+      // Pruefen, ob es wirklich ein Operator ist
+      if ( !isOperatorChar(operator) )
+        throwParseError("Illegal operator '" + operator + "'");
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      operator = expandOperator(operator);
+
+      int operatorConn = getOperatorConnectivity(operator);
+      // Operator mit geringerer Konnektivitaet beendet den Operand
+      if ( operatorConn < operandConn ) {
+        for (int i=0; i<operator.length(); i++)
+          tok.pushback();
+        return operand;
+      }
+
+      // Operator mit hoeherer Konnektivitaet erzeugt einen neuen Operand
+      // Ausnahme: erste Operation im Operand ('operand' hat noch keine Kinder)!
+      if ( operatorConn > operandConn && !operand.isLeaf() ) {
+        BinaryTreeNode operandHigher = parseOperand(operatorConn);
+        // Das letzte Literal ist der erste Operand von 'operandHigher'
+        operand.setRightChild(
+          new OperationTree.OperatorNode(
+                  operator,
+                  operand.getRightChild(),
+                  operandHigher
+          )
+        );
+        // mit naechstem Operator fortfahren
+        continue;
+      }
+
+      // Naechstes Literal
+      token = nextNonWSToken();
+      tok.pushback();
+      // oeffnende Klammer
+      // -> naechstes Literal ist eine komplette Teilformel
+      if (isOpeningBracket(token))
+        nextLiteral = parseRulePart();
+      else
+        nextLiteral = parseLiteral();
+
+      // Operand zusammensetzen
+      operand = new OperationTree.OperatorNode(operator, operand, nextLiteral);
+    }
+
+    return operand;
+  }
+
+
+
+  /**
+   * Parst das naechste Literal aus dem Tokenizer {@link #tok}. Ein Literal ist eine numerische
+   * Konstante, ein Alias oder ein 1-stelliger Operator auf einer geklammerten
+   * {@linkplain #parseRulePart() Teil-Formel}. Diese Methode unterstuetzt
+   * <ul>
+   *   <li>als 1-stellige Operatoren
+   *      <ul>
+   *         <li>Betrag: {@code "abs(.)"}</li>
+   *         <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+   *         <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+   *         <li>Abschneiden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+   *         <li>boolesches NICHT: {@code "!(.)"} oder {@code "not(.)"}</li>
+   *         <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+   *         <li>Sinus: {@code "sin(.)"}</li>
+   *         <li>Cosinus: {@code "cos(.)"}</li>
+   *         <li>Tangens: {@code "tan(.)"}</li>
+   *         <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+   *         <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+   *         <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+   *         <li>Exponentialfunktion: {@code "exp(.)"}</li>
+   *         <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+   *         <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+   *       </ul></li>
+   *   <li>als Alias (0-stellige Operatoren)
+   *       <ul>
+   *         <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+   *         <li>"Not a number" (NaN): {@code "NaN"}</li>
+   *       </ul></li>
+   *   <li>als Sonderfaelle
+   *       <ul>
+   *         <li>IF .. THEN .. ELSE ..: {@code "ITE(.,.,.)"}</li>
+   *       </ul></li>
+   * </ul>
+   * <b>Sub-Klassen muessen diese Methode erweitern, wenn z.B. (alphanumerische)
+   * Variablen oder zusaetzliche 1-stellige Operatoren beruecksichtigt werden
+   * sollen!!</b>
+   */
+  protected BinaryTreeNode parseLiteral() {
+    String token = nextNonWSToken().toLowerCase();
+
+    // Betrag, Wurzel, Runden
+    if ( token.equals("abs") ||
+         token.equals("sqr") || token.equals("sqrt") ||
+         token.equals("rnd") || token.equals("round") ||
+         token.equals("int") || token.equals("trunc") ||
+         token.equals("!")   || token.equals("not") ||
+         token.equals("isnan") ||
+         token.equals("sin") ||
+         token.equals("cos") ||
+         token.equals("tan") ||
+         token.equals("arcsin") || token.equals("asin") ||
+         token.equals("arccos") || token.equals("acos") ||
+         token.equals("arctan") || token.equals("atan") ||
+         token.equals("exp") ||
+         token.equals("log") ||
+         token.equals("ln") ) {
+      return new OperationTree.UnaryOperatorNode(token, parseRulePart());
+    }
+    // Zufallszahl oder NaN-Konstante
+    if ( token.equals("rand") || token.equals("random") ||
+         token.equals("nan") ) {
+      return new OperationTree.ConstantAliasNode(token);
+    }
+    // ITE = If..Then..Else
+    if ( token.equals("ite") ) {
+      // naechstes Token muss eine oeffnende Klammer sein!
+      token = nextNonWSToken();
+      if ( !token.equals("(") )
+        throwParseError("Illegal ITE syntax '" + token + "'");
+      openBrackets.insert(0,"(");
+      // Parameter fuer ITE lesen
+      openBrackets.insert(0,"2"); // noch 2 weitere Parameter (Trenner) erwartet
+      BinaryTreeNode ite1 = parseRulePart();
+      openBrackets.replace(0,1,"1"); // noch 1 weiterer Parameter (Trenner) erwartet
+      BinaryTreeNode ite2 = parseRulePart();
+      openBrackets.deleteCharAt(0); // keine weiteren Parameter (Trenner), sondern schliessende Klammer erwartet
+      BinaryTreeNode ite3 = parseRulePart();
+      // Baum fuer ITE
+      return new OperationTree.ITENode(token,ite1,ite2,ite3);
+    }
+    // sonst: Konstante
+    return new OperationTree.ConstantNode(getDoubleFromString(token));
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  //////////////////////   Hilfsmethoden   //////////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Wirft eine {@link IllegalArgumentException} mit der angegebenen Fehlermeldung.
+   * Als zusaetzlicher Hinweis wird das Formel-Praefix ausgegeben nach dem
+   * der Fehler aufgetreten ist.
+   * @param mess Fehler-Meldung
+   */
+  protected void throwParseError(String mess) {
+    String errorPos = tok.getReadTokens();
+    // Die Klammer entfernen, die am Anfang kuenstlich eingefuegt wurde
+    errorPos = errorPos.substring(1);
+    throw new IllegalArgumentException("Parse-Error at '"+errorPos+"': "+mess);
+  }
+
+  /**
+   * Liefert einen Integer-Wert aus einem String.
+   * @param token umzuwandelnde Zeichenkette
+   * @exception IllegalArgumentException falls die Umwandlung nicht vorgenommen
+   *            werden kann
+   */
+  protected int getIntFromString(String token) {
+    token = token.trim();
+    try {
+      return Integer.parseInt(token);
+    }
+    catch (Exception err) {
+      throwParseError("Illegal character '" + token + "' (int expected)");
+    }
+    return 0;
+  }
+
+  /**
+   * Liefert einen Double-Wert aus einem String.
+   * @param token umzuwandelnde Zeichenkette
+   * @exception IllegalArgumentException falls die Umwandlung nicht vorgenommen
+   *            werden kann
+   */
+  protected double getDoubleFromString(String token) {
+    token = token.trim();
+    // Sonderfall: Bei alleinstehender negativer Konstante ist im Token nur
+    //             das Minus-Zeichen enthalten! Zahl-Token muss angehaengt werden.
+    if ( token.equals("-") )
+      token += nextNonWSToken();
+
+    try {
+      return Double.parseDouble(token);
+    }
+    catch (Exception err) {
+      throwParseError("Illegal character '" + token + "' (number expected)");
+    }
+    return 0;
+  }
+
+  /**
+   * Liefert das naechste Token aus dem Tokenizer, das nicht ein White-Space
+   * ist.
+   * @param tok ein Tokenizer
+   */
+  private String nextNonWSToken() {
+    String token = null;
+    while ( (token = tok.nextToken()).trim().equals("") );
+    return token.trim();
+  }
+}

Added: trunk/src/schmitzm/lang/tree/OperationTreeParser.java
===================================================================
--- trunk/src/schmitzm/lang/tree/OperationTreeParser.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/OperationTreeParser.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,1083 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+import schmitzm.lang.tree.BinaryTreeNode;
+import schmitzm.lang.PushbackStringTokenizer;
+import schmitzm.lang.tree.OperationTree.*;
+import org.apache.log4j.Logger;
+
+/**
+ * Diese Klasse stellt einen Parser fuer einen {@linkplain OperationTree Operator-Baum}
+ * dar. Dieser erstellt einen {@linkplain OperationTree Operator-Baum} aus einem
+ * Formel-String, der folgende Teilen bestehen darf:
+ * <ul>
+ *   <li>konstante numerische Werte: {@link ConstantNode}</li>
+ *       <ul>
+ *           <li>Zahlen im {@code double}-Bereich</li>
+ *           <li>{@code rand} oder {@code random} als Alias fuer eine Zufallszahl
+ *                zwischen 0 und 1: {@link ConstantAliasNode}</li>
+ *           <li>"Not a number" (NaN): {@code "NaN"}</li>
+ *           <li>boolesches WAHR (1): {@code "TRUE"}</li>
+ *           <li>boolesches FALSCH (0): {@code "FALSE"}</li>
+ *           <li>Aktuelle Zeit in ms ({@link System#currentTimeMillis()}): {@code "CURR_MILLIS"}</li>
+ *       </ul></li>
+ *   <li>konstante String Werte: {@link ConstantNode}</li>
+ *       <ul>
+ *           <li>Zeichenketten, sie durch " oder ' eingeleitet und abgeschlossen
+ *               werden</li>
+ *       </ul></li>
+ *   <li>den 2-stelligen arithmetischen Operatoren: {@link OperatorNode}
+ *       <ul>
+ *          <li>Addition: {@code "+"}</li>
+ *          <li>Subtraktion: {@code "-"}</li>
+ *          <li>Multiplikation: {@code "*"}</li>
+ *          <li>Division: {@code "/"}</li>
+ *          <li>Potenzbildung: {@code "^"}</li>
+ *        </ul></li>
+ *   <li>den 2-stelligen booleschen Operatoren: {@link OperatorNode}
+ *       <ul>
+ *          <li>boolesches UND: {@code "&"}</li>
+ *          <li>boolesches ODER: {@code "|"}</li>
+ *          <li>Gleich: {@code "="} oder {@code "=="}</li>
+ *          <li>Ungleich: {@code "!="} oder {@code "<>"}</li>
+ *          <li>Kleiner als: {@code "<"}</li>
+ *          <li>Groesser als: {@code ">"}</li>
+ *          <li>Kleiner gleich: {@code "<="}</li>
+ *          <li>Groesser gleich: {@code ">="}</li>
+ *        </ul></li>
+ *   <li>den 2-stelligen Zeichenketten-Operatoren: {@link OperatorNode}
+ *       <ul>
+ *          <li>String-Konkatenation: {@code "+"}</li>
+ *          <li>Test auf regulaeren Ausdruck: {@code "regex(expression,regular expr)"}</li>
+ *          <li>n-tes Ergebnis eines {@linkplain String#split(String) String-Splits} ueber regulaeren Ausdruck: {@code "split(expression,regular expr,n)"}</li>
+ *       </ul></li>
+ *   <li>den mehr-stelligen Zeichenketten-Operatoren: {@link MultiParamOperatorNode}
+ *       <ul>
+ *          <li>Teil-String: {@code "substr(expression,startPos_inkl,endPos_exkl)"}</li>
+ *          <li>String-Ersetzung: {@code "replAll(expression,pattern,replacement)"}</li>
+ *       </ul></li>
+ *   <li>die 1-stelligen Operatoren: {@link UnaryOperatorNode}
+ *       <ul>
+ *          <li>Betrag: {@code "abs(.)"}</li>
+ *          <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+ *          <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+ *          <li>Abschneiden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+ *          <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+ *          <li>Sinus: {@code "sin(.)"}</li>
+ *          <li>Cosinus: {@code "cos(.)"}</li>
+ *          <li>Tangens: {@code "tan(.)"}</li>
+ *          <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+ *          <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+ *          <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+ *          <li>Exponentialfunktion: {@code "exp(.)"}</li>
+ *          <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+ *          <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+ *        </ul></li>
+ *   <li>den 1-stelligen booleschen Operatoren: {@link UnaryOperatorNode}
+ *       <ul>
+ *          <li>boolesches NICHT: {@code "!(.)"}</li>
+ *        </ul></li>
+ *   <li>den 1-stelligen String-Operatoren: {@link UnaryOperatorNode}
+ *       <ul>
+ *          <li>Umwandlung Zahl-zu-String: {@code "str(.)"}</li>
+ *          <li>Umwandlung String-zu-Zahl: {@code "val(.)"}</li>
+ *          <li>Umwandlung in Gross-Buchstaben: {@code "toupper(.)"}</li>
+ *          <li>Umwandlung in Klein-Buchstaben: {@code "tolower(.)"}</li>
+ *          <li>Laenge eines Strings: {@code "len(.)"}</li>
+ *        </ul></li>
+ *   <li>dem 3-stelligen Operator {@code ITE(.,.,.)}: {@link ITENode}<br>
+ *       Der ITE-Operator drueckt eine Bedingung "IF .. THEN .. ELSE .." aus.</li>
+ * </ul>
+ * <br>
+ * Damit boolesche Operatoren und arithmetische Ausdruecke kombiniert
+ * werden koennen, werden die Operanden und das Ergebis einer booleschen
+ * Operation nicht als TRUE oder FALSE, sondern numerisch codiert:<br>
+ * <b>Operanden:</b> {@code op > 0} entspricht TRUE; {@code op = 0} entspricht FALSE.<br>
+ * <b>Ergebnis:</b> TRUE wird als 1 codiert; FALSE wird als 0 codiert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationTreeParser {
+  private Logger LOG = Logger.getLogger(this.getClass());
+
+  /** Ergebnis-Typ von {@link #checkRuleClosing(String)}. */
+  static private enum RuleClosingType {
+    none,       // Formel wird nicht beendet
+    rule_part,  // Formel wird beendet
+    func_pram   // Formel wird beendet UND glz. ein Funktions-Parameter
+  };
+
+  /** Stellt einen Stack dar, auf den die geoeffneten Klammern gelegt werden.
+   *  Zeichen 0 stellt das oberste Stack-Element dar. */
+  protected StringBuffer openBrackets = null;
+
+  /** Tokenizer auf den Formel-String. Wird beim Starten des Parse-Vorgangs
+   *  erzeugt.
+   *  @see #parse(String)
+   */
+  protected PushbackStringTokenizer tok = null;
+  /** Entaehlt die Delimiter des Tokenizers {@link #tok}. Diese setzen sich
+   *  aus den oeffnenden und schliessenden Klammern zusammen, sowie aus den
+   *  Operatoren. Wird bei der Instanziierung des {@code OperationTreeParser}
+   *  befuellt.
+   *  @see #getOpeningBracketChars()
+   *  @see #getClosingBracketChars()
+   *  @see #getOperatorChars()
+   */
+  protected String delimiter    = null;
+
+  /**
+   * Erzeugt eine neue Instanz des Parsers.
+   */
+  public OperationTreeParser() {
+    delimiter = getOpeningBracketChars() +
+                getClosingBracketChars() +
+                getStringEncapsulationChars() +
+                getParameterSeperatorChars() +
+                getOperatorChars() + "\n";
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /////////////////   Konfigurations-Methoden   /////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Liefert die Zeichen, die als oeffnende Klammern verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"([{"</code>
+   */
+  public String getOpeningBracketChars() {
+    return "([{";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine oeffnende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getOpeningBracketChars()
+   */
+  public boolean isOpeningBracket(String token) {
+    return getOpeningBracketChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als schliessende Klammern verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>")]}"</code>
+   */
+  public String getClosingBracketChars() {
+    return ")]}";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine schliessende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getClosingBracketChars()
+   */
+  public boolean isClosingBracket(String token) {
+    return getClosingBracketChars().contains(token);
+  }
+
+  /**
+   * Prueft, ob eine oeffnende und eine schliessende Klammer zueinander
+   * korrespondieren.
+   * Unterklassen muessen diese Methode ueberschreiben und erweitern, wenn
+   * weitere Klammer-Zeichen verwendet werden.
+   * @param openBracket oeffnende Klammer
+   * @param closeBracket schliessende Klammer
+   */
+  public boolean checkCorrespondingBrackets(char openBracket, char closeBracket) {
+    return openBracket == '(' && closeBracket == ')' ||
+           openBracket == '[' && closeBracket == ']' ||
+           openBracket == '{' && closeBracket == '}';
+  }
+
+  /**
+   * Liefert die Zeichen, zwischen denen String-Konstanten gekapselt werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"'</code>
+   */
+  public String getStringEncapsulationChars() {
+    return "\"'";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um ein Zeichenketten-kapselndes
+   * Zeichen handelt.
+   * @param token Zeichenkette
+   * @see #getStringEncapsulationChars()
+   */
+  public boolean isStringEncapsulationChar(String token) {
+    return getStringEncapsulationChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als Trennzeichen zwischen Funktionsparametern
+   * verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>",;"</code>
+   */
+  public String getParameterSeperatorChars() {
+    return ",;";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um eine schliessende Klammer handelt.
+   * @param token Zeichenkette
+   * @see #getClosingBracketChars()
+   */
+  public boolean isParameterSeperator(String token) {
+    return getParameterSeperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als 2-stellige Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   */
+  public String getOperatorChars() {
+    return getAdditionOperatorChars() +
+           getMultiplyOperatorChars() +
+           getCompareOperatorChars() +
+           getBooleanOperatorChars();
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Operator handelt.
+   * @param token Zeichenkette
+   * @see #getOperatorChars()
+   */
+  public boolean isOperatorChar(String token) {
+//    return getOperatorChars().contains(token);
+    return isAdditionOperator(token)
+        || isBooleanOperator(token)
+        || isCompareOperator(token)
+        || isMultiplyOperator(token);
+  }
+
+
+  /**
+   * Liefert die Zeichen, die als arithmetische Punkt-Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"/*^"</code>
+   */
+  public String getMultiplyOperatorChars() {
+    return "/*^";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Punkt-Operator handelt.
+   * @param token Zeichenkette
+   * @see #getMultiplyOperatorChars()
+   */
+  public boolean isMultiplyOperator(String token) {
+    return getMultiplyOperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als arithmetische Strich-Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"+-"</code>
+   */
+  public String getAdditionOperatorChars() {
+    return "+-";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Strich-Operator handelt.
+   * @param token Zeichenkette
+   * @see #getAdditionOperatorChars()
+   */
+  public boolean isAdditionOperator(String token) {
+    return getAdditionOperatorChars().contains(token);
+  }
+
+  /**
+   * Liefert die Zeichen, die als boolesche Operatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return {@code "&|!"}
+   */
+  public String getBooleanOperatorChars() {
+    return "&|!";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen booleschen Operator handelt.
+   * @param token Zeichenkette
+   */
+  public boolean isBooleanOperator(String token) {
+    return getBooleanOperatorChars().contains(token)
+        || token.equalsIgnoreCase("AND")
+        || token.equals("OR");
+  }
+
+  /**
+   * Liefert die Zeichen, die fuer Vergleichsoperatoren verwendet werden.
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return {@code "=<>!"}
+   */
+  public String getCompareOperatorChars() {
+    return "=<>!";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem String um einen Vergleichsoperator handelt.
+   * @param token Zeichenkette
+   */
+  public boolean isCompareOperator(String token) {
+    return token.equals("=") ||
+           token.equals("==") ||
+           token.equals("!=") ||
+           token.equals("<>") ||
+           token.equals(">") ||
+           token.equals(">=") ||
+           token.equals("<") ||
+           token.equals("<=");
+  }
+
+
+
+  /**
+   * Liefert einen Wert, der die Konnektivitaet (Bindung) eines Operators ausdrueckt (je
+   * groesser, desto groesser die Bindung). Punkt-Operatoren haben die
+   * groesste Bindung fuer die Operanden, werden also als erstes ausgewertet.
+   * Darauf folgen die Strich-Operatoren, dann die Vergleichsoperatoren und
+   * am Ende die booleschen Operatoren
+   * @param operator Operator
+   * @return 0, falls kein bekannter Operator uebergeben wird
+   */
+  public int getOperatorConnectivity(String operator) {
+    // Punkt-Operatoren haben den hoechste verbindende Prioritaet
+    if ( isMultiplyOperator(operator) )
+      return 1000;
+    // Strich-Operatoren haben Vorrang vor Vergleichen oder booleschen Operatoren
+    if ( isAdditionOperator(operator) )
+      return 100;
+    // Vergleichs-Operatoren haben Vorrang vor booleschen Operatoren
+    if ( isCompareOperator(operator) )
+      return 10;
+    // boolesche Operatoren haben den geringesten Rang
+    if ( isBooleanOperator(operator) ) {
+      if ( operator.equals("!") )
+        return 3;
+      if ( operator.equals("&") )
+        return 2;
+      if ( operator.equals("|") )
+        return 1;
+    }
+
+    return 0;
+  }
+
+  /**
+   * Liefert den Konnektivitaetswert des am meisten bindenden Operators.
+   * Unterklasse muessen diese Methode u.U. ueberschreiben, wenn durch
+   * Ueberschreiben der Methode {@link #getOperatorConnectivity(String)}
+   * die Prioritaeten der Operatoren veraendert werden.
+   */
+  protected int getMaximumOperatorConnectivity() {
+    return 1000;
+  }
+
+  /**
+   * Liefert die 2-stellige Operatoren, die zwei Operanden aneinander
+   * binden (Punkt-Operatoren).
+   * Unterklassen koennen diese Methode ueberschreiben und Zeichen hinzufuegen.
+   * @return <code>"/*^"</code>
+   * @deprecated wegen booleschen Operatoren muss eine Verbindungs-Prioritaet
+                 verwendet werden; ersetzt durch {@link #getOperatorConnectivity(String)}
+   */
+  public String getConnectingOperatorChars() {
+    return "/*^";
+  }
+
+  /**
+   * Prueft, ob es sich bei einem 2-stelligen Operator um einen verbindenen
+   * Operator handelt (Punkt-Operator).
+   * @param operator 2-stelliger Operator
+   * @see #getConnectingOperatorChars()
+   * @deprecated wegen booleschen Operatoren muss eine Verbindungs-Prioritaet
+                 verwendet werden; ersetzt durch {@link #getOperatorConnectivity(String)}
+   */
+  public boolean isConnectingOperator(String operator) {
+    return getConnectingOperatorChars().contains(operator);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  ///////////////////   Methoden fuer Parsen  ///////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Erstellt einen Operator-Baum aus einem Formel-String.
+   * @param rule Formel
+   */
+  public OperationTree parse(String rule) {
+    openBrackets = new StringBuffer();
+//    // Probleme, wenn Formel mit einem geklammerten Ausdruck beginnt
+//    // z.B. "(17+7)*2+1" -> *2+1 wird nicht ausgewertet
+//    // Loesung: Gesamten Ausdruck nochmal klammern
+//    rule = "("+rule.trim()+")";
+//    tok  = new PushbackStringTokenizer(rule,delimiter,true);
+//    return new OperationTree( parseRulePart() );
+    tok  = new PushbackStringTokenizer(rule,delimiter,true);
+    return new OperationTree( parseRule() );
+  }
+
+//  /**
+//   * Parst die naechste Teil-Formel aus dem Tokenizer {@link #tok}. Eine (geklammerter)
+//   * Teilformel ist eine durch Strich-Operatoren verbundene Kette von
+//   * {@linkplain #parseOperand() Operanden}.
+//   */
+//  protected final BinaryTreeNode parseRulePart() {
+//    BinaryTreeNode rulePart    = null;
+//    BinaryTreeNode nextOperand = null;
+//    String         operator    = null;
+//
+//    // Ersten (linken) Operand parsen
+//    String token = nextNonWSToken();
+//    // oeffnende Klammer
+//    if ( isOpeningBracket(token) )
+//      openBrackets.insert(0,token);
+//    else
+//      tok.pushback();
+//
+//    rulePart = parseOperand();
+//    while ( tok.hasMoreTokens() ) {
+//      // Operator parsen (kann nur ein Strich- oder Bool-Operator sein!)
+//      operator = nextNonWSToken();
+//
+//      // Sonderfall: schliessende Klammer
+//      // -> Klammerung ueberpruefen und Teil-Formel beenden
+//      if ( isClosingBracket(operator) ) {
+//        if ( !checkCorrespondingBrackets(openBrackets.charAt(0),operator.charAt(0)) )
+//          throwParseError("Bracket '" + operator + "' can not close '" + openBrackets.charAt(0) + "'");
+//        openBrackets.deleteCharAt(0);
+//        return rulePart;
+//      }
+//
+//      // Pruefen, ob es wirklich ein Operator ist
+//      if ( !isOperatorChar(operator) )
+//        throwParseError("Illegal operator '" + operator + "'");
+//
+//      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+//      String nToken = nextNonWSToken();
+//      if ( operator.equals("<") && nToken.equals("=") ||
+//           operator.equals(">") && nToken.equals("=") ||
+//           operator.equals("=") && nToken.equals("=") ||
+//           operator.equals("!") && nToken.equals("=") ||
+//           operator.equals("<") && nToken.equals(">") )
+//        operator += nToken;
+//      else
+//        tok.pushback();
+//
+//      // Naechsten Operand parsen
+//      nextOperand = parseOperand();
+//
+//      // Teil zusammensetzen
+//      rulePart = new OperationTree.OperatorNode(operator,rulePart,nextOperand);
+//    }
+//    // keine Token mehr, aber noch offene Klammern
+//    if ( openBrackets.length() > 0 )
+//      throwParseError("Bracket"+(openBrackets.length() > 1 ? "s" : "")+" '"+openBrackets.toString()+"' not closed!");
+//    return rulePart;
+//  }
+//
+//  /**
+//   * Parst den naechsten Operand aus dem Tokenizer {@link #tok}. Ein Operand ist eine
+//   * durch Punkt-Operatoren verbundene Kette von {@linkplain #parseLiteral() Literalen}
+//   * und geklammerten {@linkplain #parseRulePart() Teil-Formeln}.<br>
+//   */
+//  protected final BinaryTreeNode parseOperand() {
+//    BinaryTreeNode operand = null;
+//    BinaryTreeNode nextLiteral = null;
+//    String operator = null;
+//
+//    // Erstes (linkes) Literal parsen
+//    String token = nextNonWSToken();
+//    // oeffnende Klammer direkt am Anfang
+//    // -> Literal besteht aus kompletter Teilformel
+//    if (isOpeningBracket(token)) {
+//      tok.pushback();
+//      operand = parseRulePart();
+//    } else {
+//      // Sonderfall: Operand startet mit Minus-Zeichen (z.B. '-(1+2)' oder '-abs(-4)')
+//      //             -> folgende
+//      if ( token.equals("-") )
+//        operand = new OperationTree.OperatorNode("*", new OperationTree.ConstantNode(-1.0),  parseOperand());
+//      else {
+//        tok.pushback();
+//        operand = parseLiteral();
+//      }
+//    }
+//    while (tok.hasMoreTokens()) {
+//      // Operator parsen
+//      operator = nextNonWSToken();
+//      // wenn schliessende Klammer oder Strich-/Bool-Operator
+//      // -> Operand beenden
+//      if (isClosingBracket(operator) || !isMultiplyOperator(operator)) {
+//        tok.pushback();
+//        return operand;
+//      }
+//      // Pruefen, ob es wirklich ein Operator ist
+//      if ( !isOperatorChar(operator) )
+//        throwParseError("Illegal operator '" + operator + "'");
+//
+//      // Naechstes Literal
+//      token = nextNonWSToken();
+//      tok.pushback();
+//      // oeffnende Klammer
+//      // -> naechstes Literal ist eine komplette Teilformel
+//      if (isOpeningBracket(token))
+//        nextLiteral = parseRulePart();
+//      else
+//        nextLiteral = parseLiteral();
+//
+//      // Operand zusammensetzen
+//      operand = new OperationTree.OperatorNode(operator, operand, nextLiteral);
+//    }
+//
+//    return operand;
+//  }
+
+  /**
+   * Liesst ein weiteres Zeichen aus dem Tokenizer und fuegt dies gegebenfalls
+   * dem Operator hinzu. Dies ist z.B. bei den Vergleichsoperatoren
+   * <=, >=, ==, != und == notwendig.
+   * @param operator Operator
+   */
+  private String expandOperator(String operator) {
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      String nToken = nextNonWSToken();
+      if ( operator.equals("<") && nToken.equals("=") ||
+           operator.equals(">") && nToken.equals("=") ||
+           operator.equals("=") && nToken.equals("=") ||
+           operator.equals("!") && nToken.equals("=") ||
+           operator.equals("<") && nToken.equals(">") )
+        operator += nToken;
+      else
+        pushbackWithWSToken();
+      return operator;
+  }
+
+  /**
+   * Prueft, ob der aktuelle Operand oder die Teil-Formel aufrund einer
+   * schliessende Klammer oder einem Parameter-Trennzeichen abgeschlossen
+   * werden muss.
+   * @param token aktuelles Zeichen
+   */
+  private RuleClosingType checkRuleClosing(String token) {
+    // Operand/Teil-Formel wird nur bei schliessender Klammer oder
+    // Parameter-Trennzeichen beendet
+    if ( !isClosingBracket(token) &&
+         !isParameterSeperator(token) )
+      return RuleClosingType.none;
+
+    // aktuelle Klammerung ermitteln
+    if ( openBrackets.length() == 0 )
+        throwParseError(token + " not expected!");
+    char openBracket = openBrackets.charAt(0);
+
+    // Schliessende Klammer
+    if ( isClosingBracket(token) ) {
+        // Flag, ob Klammer eine Funktion mit Parametern f(.,.,...) beendet.
+        // In diesem Fall liegt die verbleibende Anzahl (0) an Parametern
+        // auf dem Stack
+        boolean funcEnding = (openBracket == '0');
+        if ( funcEnding )
+          // korrespondierende oeffnende Klammer liegt vor Parameter-Wert
+          // auf Stack
+          openBracket = openBrackets.charAt(1);
+
+      // Wenn keine oeffnende Klammer auf Stack liegt (sondern ein Parameter-Zaehler)
+      // kann Klammer nicht schliessen, da noch ein Parameter erwartet wird
+      if ( !isOpeningBracket(String.valueOf(openBracket)) )
+        throwParseError(openBracket+" more function parameters expected. Bracket can not close!'");
+      if ( !checkCorrespondingBrackets(openBracket,token.charAt(0)) )
+        throwParseError("Bracket " + token + " can not close " + openBracket);
+
+      return funcEnding ? RuleClosingType.func_pram : RuleClosingType.rule_part;
+    }
+
+    // Trenner zwischen Funktions-Parametern
+    if ( isParameterSeperator(token) ) {
+      if ( isOpeningBracket(String.valueOf(openBracket)) )
+        throwParseError(token+" not expected!'");
+      return RuleClosingType.func_pram;
+    }
+
+    return RuleClosingType.none;
+  }
+
+  /**
+   * Prueft, ob noch offene Klammern auf dem Stack liegen, obwohl keine
+   * Token mehr zu parsen sind. Ist dies der Fall wird eine Exception
+   * geworfen.
+   */
+  private void checkRemainingBrackets() {
+    // keine Token mehr, aber noch offene Klammern
+    if ( !tok.hasMoreTokens() && openBrackets.length() > 0 )
+      throwParseError("Bracket"+(openBrackets.length() > 1 ? "s" : "")+" "+openBrackets.toString()+" not closed!");
+  }
+
+  /**
+   * Parst eine komplette Formel, bis zum Ende der Eingabe-Formel oder
+   * einem Parameter-Trenner.
+   */
+  protected final BinaryTreeNode parseRule() {
+    String         operator     = null;
+    BinaryTreeNode rulePart     = null;
+    BinaryTreeNode nextOperand  = null;
+
+    // Ersten Formel-Teil parsen
+    rulePart = parseRulePart();
+
+    while ( tok.hasMoreTokens() ) {
+      // Operator parsen
+      operator = nextNonWSToken();
+
+      // Ende der Formel pruefen, kann eigentlich nur das Ende eines
+      // Funktions-Parameters sein, da schliessende Klammern sonst bereits
+      // entfernt wurden...
+      if ( checkRuleClosing(operator) != RuleClosingType.none ) {
+        pushbackWithWSToken();
+        return rulePart;
+      }
+
+      // Pruefen, ob es wirklich ein Operator ist
+      if ( !isOperatorChar(operator) )
+        throwParseError("Illegal operator " + operator);
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      operator = expandOperator(operator);
+
+      // Naechsten Operand parsen
+      nextOperand = parseOperand( getOperatorConnectivity(operator) );
+
+      // Teil zusammensetzen
+      rulePart = new OperationTree.OperatorNode(operator,rulePart,nextOperand);
+    }
+
+    checkRemainingBrackets();
+
+    return rulePart;
+  }
+
+  /**
+   * Parst die naechste Teil-Formel aus dem Tokenizer {@link #tok}. Eine (geklammerter)
+   * Teilformel ist eine durch Strich-Operatoren verbundene Kette von
+   * {@linkplain #parseOperand(int) Operanden}.
+   */
+  protected final BinaryTreeNode parseRulePart() {
+    BinaryTreeNode rulePart    = null;
+    BinaryTreeNode nextOperand = null;
+    String         operator    = null;
+
+    // Ersten (linken) Operand parsen
+    String token = nextNonWSToken();
+    // oeffnende Klammer
+    if ( isOpeningBracket(token) )
+      openBrackets.insert(0,token);
+    else
+      pushbackWithWSToken();
+
+    rulePart = parseOperand( getMaximumOperatorConnectivity() );
+    while ( tok.hasMoreTokens() ) {
+      // Operator parsen
+      operator = nextNonWSToken();
+
+      // schliessende Klammer oder Parameter-Trenner beendet die Teil-Formel
+      RuleClosingType closingType = checkRuleClosing(operator);
+      if ( closingType != RuleClosingType.none ) {
+        // Wenn Teil-Formel beendet wird, dann schliessende Klammer
+        // vom Stack entfernen
+        if ( closingType == RuleClosingType.rule_part )
+          openBrackets.deleteCharAt(0);
+        // Wenn Functions-Parameter beendet wird, dann Token zuruecklegen,
+        // damit Beendigung der Formel (in uebergeordneter Methode)
+        // erkannt wird
+        if ( closingType == RuleClosingType.func_pram )
+          pushbackWithWSToken();
+        // (Teil-)Formel beenden
+        return rulePart;
+      }
+
+      // Pruefen, ob es wirklich ein Operator ist
+      if ( !isOperatorChar(operator) )
+        throwParseError("Illegal operator " + operator);
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      operator = expandOperator(operator);
+
+      // Naechsten Operand parsen
+      nextOperand = parseOperand( getOperatorConnectivity(operator) );
+
+      // Teil zusammensetzen
+      rulePart = new OperationTree.OperatorNode(operator,rulePart,nextOperand);
+    }
+
+    checkRemainingBrackets();
+
+    return rulePart;
+  }
+
+  /**
+   * Parst den naechsten Operand aus dem Tokenizer {@link #tok}. Ein Operand ist eine
+   * durch Operatoren gleicher Konnektivitaet verbundene Kette von
+   * {@linkplain #parseLiteral() Literalen} und geklammerten
+   * {@linkplain #parseRulePart() Teil-Formeln}.<br>
+   * @param minOperandConn minimale Konnektivitaet fuer den Operanden (bei einem
+   *                       Operator mit kleinerer Konnektivitaet wird der
+   *                       Operand beendet)
+   */
+  protected final BinaryTreeNode parseOperand(int minOperatorConn) {
+    BinaryTreeNode operand = null;
+    BinaryTreeNode nextLiteral = null;
+    String operator = null;
+    int lastOperatorConn = this.getMaximumOperatorConnectivity();
+
+    // Erstes (linkes) Literal parsen
+    String token = nextNonWSToken();
+    // oeffnende Klammer direkt am Anfang
+    // -> Literal besteht aus kompletter Teilformel
+    if (isOpeningBracket(token)) {
+      pushbackWithWSToken();
+      operand = parseRulePart();
+    } else {
+      // Sonderfall: Operand startet mit Minus-Zeichen (z.B. '-(1+2)' oder '-abs(-4)')
+      //             -> folgende
+      if ( token.equals("-") )
+        operand = new OperationTree.OperatorNode("*", new OperationTree.ConstantNode(-1.0),  parseOperand(getOperatorConnectivity("*")));
+      else {
+        pushbackWithWSToken();
+        operand = parseLiteral();
+      }
+    }
+    while (tok.hasMoreTokens()) {
+      // Operator parsen
+      operator = nextNonWSToken();
+      // schliessende Klammer oder Parameter-Trenner beendet den Operand
+      if ( checkRuleClosing(operator) != RuleClosingType.none) {
+        pushbackWithWSToken();
+        return operand;
+      }
+
+      // Pruefen, ob es wirklich ein Operator ist
+      if ( !isOperatorChar(operator) )
+        throwParseError("Illegal operator '" + operator + "'");
+      // Sonderfall: boolesche Operatoren u.U. 2-stellig (<=, >=, !=, <>, ==)
+      operator = expandOperator(operator);
+
+      int operatorConn = getOperatorConnectivity(operator);
+      // Operator mit geringerer Konnektivitaet beendet den Operand
+      if ( operatorConn < minOperatorConn ) {
+        for (int i=0; i<operator.length(); i++)
+        pushbackWithWSToken();
+        return operand;
+      }
+
+//      // Operator mit hoeherer Konnektivitaet erzeugt einen neuen Operand
+//      // Ausnahme: erste Operation im Operand ('operand' hat noch keine Kinder)!
+//      if ( operatorConn > minOperandConn && !operand.isLeaf() ) {
+
+      // Operator mit hoeherer Konnektivitaet, als der letzte Operator, erzeugt
+      // einen neuen Operand
+      if ( operatorConn > lastOperatorConn ) {
+        BinaryTreeNode operandHigher = parseOperand(operatorConn);
+        // Das letzte Literal ist der erste Operand von 'operandHigher'
+        operand.setRightChild(
+          new OperationTree.OperatorNode(
+                  operator,
+                  operand.getRightChild(),
+                  operandHigher
+          )
+        );
+        // mit naechstem Operator fortfahren
+        lastOperatorConn = operatorConn;
+        continue;
+      }
+
+      // Naechstes Literal
+      token = nextNonWSToken();
+      pushbackWithWSToken();
+      // oeffnende Klammer
+      // -> naechstes Literal ist eine komplette Teilformel
+      if (isOpeningBracket(token))
+        nextLiteral = parseRulePart();
+      else
+        nextLiteral = parseLiteral();
+
+      // Operand zusammensetzen
+      operand = new OperationTree.OperatorNode(operator, operand, nextLiteral);
+
+      lastOperatorConn = operatorConn;
+    }
+
+    return operand;
+  }
+
+
+
+  /**
+   * Parst das naechste Literal aus dem Tokenizer {@link #tok}. Ein Literal ist eine numerische
+   * Konstante, ein Alias oder ein 1-stelliger Operator auf einer geklammerten
+   * {@linkplain #parseRulePart() Teil-Formel}. Diese Methode unterstuetzt
+   * <ul>
+   *   <li>als 1-stellige Operatoren
+   *      <ul>
+   *         <li>Betrag: {@code "abs(.)"}</li>
+   *         <li>Wurzel: {@code "sqr(.)"} oder {@code "sqrt(.)"}</li>
+   *         <li>Runden: {@code "rnd(.)"} oder {@code "round(.)"}</li>
+   *         <li>Abschneiden: {@code "int(.)"} oder {@code "trunc(.)"}</li>
+   *         <li>boolesches NICHT: {@code "!(.)"} oder {@code "not(.)"}</li>
+   *         <li>Test auf "Not a Number" (NaN): {@code "isNaN(.)"}</li>
+   *         <li>Sinus: {@code "sin(.)"}</li>
+   *         <li>Cosinus: {@code "cos(.)"}</li>
+   *         <li>Tangens: {@code "tan(.)"}</li>
+   *         <li>Arcus-Sinus: {@code "arcsin(.)"} oder {@code "asin(.)"}</li>
+   *         <li>Arcus-Cosinus: {@code "arccos(.)"} oder {@code "acos(.)"}</li>
+   *         <li>Arcus-Tangens: {@code "arctan(.)"} oder {@code "atan(.)"}</li>
+   *         <li>Exponentialfunktion: {@code "exp(.)"}</li>
+   *         <li>Logarithmus zur Basis e: {@code "ln(.)"}</li>
+   *         <li>Logarithmus zur Basis 10: {@code "log(.)"}</li>
+   *         <li>Umwandlung Zahl-zu-String: {@code "str(.)"}</li>
+   *         <li>Umwandlung String-zu-Zahl: {@code "val(.)"}</li>
+   *         <li>Laenge eines Strings: {@code "len(.)"}</li>
+   *         <li>Umwandlung in Gross-Buchstaben: {@code "toupper(.)"}</li>
+   *         <li>Umwandlung in Klein-Buchstaben: {@code "tolower(.)"}</li>
+   *       </ul></li>
+   *   <li>als Alias (0-stellige Operatoren)
+   *       <ul>
+   *         <li>Zufallszahl zwischen 0 und 1: {@code "rand"} oder {@code "random"}</li>
+   *         <li>"Not a number" (NaN): {@code "NaN"}</li>
+   *       </ul></li>
+   *   <li>als Sonderfaelle
+   *       <ul>
+   *         <li>IF .. THEN .. ELSE ..: {@code "ITE(.,.,.)"}</li>
+   *       </ul></li>
+   * </ul>
+   * <b>Sub-Klassen muessen diese Methode erweitern, wenn z.B. (alphanumerische)
+   * Variablen oder zusaetzliche 1-stellige Operatoren beruecksichtigt werden
+   * sollen!!</b>
+   */
+  protected BinaryTreeNode parseLiteral() {
+    String token = nextNonWSToken().toLowerCase();
+
+    // Sonderfall: in Anfuehrungsstriche gekapselte String-Konstante
+    if ( isStringEncapsulationChar(token) ) {
+      // Token (Anfuehrungsstrichen) erweitern um alles, bis zum naechsten
+      // Anfuehrungsstrich gleicher Art
+      String encChar = token;
+      // Bemerkung: tok.nextToken(encChar) funktioniert nicht so richtig,
+      //            wohl wegen PushBack-Funktionalitaet. Zudem aendert
+      //            dieser Aufruf die Delimiter dauerhaft!
+      do {
+        token += tok.nextToken();
+      } while (!token.endsWith(encChar));
+    }
+
+
+    // Betrag, Wurzel, Runden
+    if ( token.equals("abs") ||
+         token.equals("sqr") || token.equals("sqrt") ||
+         token.equals("rnd") || token.equals("round") ||
+         token.equals("int") || token.equals("trunc") ||
+         token.equals("!")   || token.equals("not") ||
+         token.equals("isnan") ||
+         token.equals("sin") ||
+         token.equals("cos") ||
+         token.equals("tan") ||
+         token.equals("arcsin") || token.equals("asin") ||
+         token.equals("arccos") || token.equals("acos") ||
+         token.equals("arctan") || token.equals("atan") ||
+         token.equals("exp") ||
+         token.equals("log") ||
+         token.equals("ln") ||
+         token.equals("str")     || token.equals("val") ||
+         token.equals("len") ||
+         token.equals("toupper") || token.equals("tolower") ) {
+      return new OperationTree.UnaryOperatorNode(token, parseRulePart());
+    }
+    // Zufallszahl oder NaN-Konstante
+    if ( token.equals("rand") || token.equals("random") ||
+         token.equals("nan")  || token.equals("curr_millis")) {
+      return new OperationTree.ConstantAliasNode(token);
+    }
+    // ITE(a,b,c) = IF a THEN b ELSE c
+    if ( token.equals("ite") ) {
+        // Parameter fuer ITE parsen
+        BinaryTreeNode[] iteParam = parseFunctionParameters("ITE",3);
+        // Baum fuer ITE
+        return new OperationTree.ITENode(token,iteParam[0],iteParam[1],iteParam[2]);
+    }
+
+    // REGEX(string,regexpr) = Regular Expression
+    if ( token.equals("regex") ) {
+      // Parameter fuer REGEX parsen
+      BinaryTreeNode[] regexParam = parseFunctionParameters("REGEX",2);
+      // Baum fuer REGEX
+      return new OperationTree.OperatorNode(token,regexParam[0],regexParam[1]);
+    }
+
+    // SPLIT(string,regexpr) = String-Split
+    if ( token.equals("split") ) {
+      // Parameter fuer SPLIT parsen
+      BinaryTreeNode[] splitParam = parseFunctionParameters("SPLIT",3);
+      // Baum fuer SPLIT
+      return new OperationTree.MultiParamOperatorNode(token,null,splitParam);
+    }
+
+    // SUBSTR(string,startPos_inkl,endPos_exkl) = Teil-String
+    if ( token.equals("substr") ) {
+      // Parameter fuer SUBSTR parsen
+      BinaryTreeNode[] substrParam = parseFunctionParameters("SUBSTR",3);
+      // Baum fuer SPLIT
+      return new OperationTree.MultiParamOperatorNode(token,null,substrParam);
+    }
+
+    // REPLALL(string,pattern,replacement) = String-Ersetzung
+    if ( token.equals("replall") ) {
+      // Parameter fuer REPLALL parsen
+      BinaryTreeNode[] substrParam = parseFunctionParameters("REPLALL",3);
+      // Baum fuer REPLALL
+      return new OperationTree.MultiParamOperatorNode(token,null,substrParam);
+    }
+
+    // sonst: Konstante
+    return new OperationTree.ConstantNode(getConstantFromString(token));
+  }
+
+  /**
+   * Parst die Funktions-Parameter fuer eine Funktion F(.,.,.,.).
+   * @param operator Name des Operators (nur fuer Exception-Beschreibung)
+   * @param paramCount Anzahl der erwarteten Parameter
+   */
+  private BinaryTreeNode[] parseFunctionParameters(String operator, int paramCount) {
+    BinaryTreeNode[] paramNode = new BinaryTreeNode[paramCount];
+
+    // naechstes Token muss eine oeffnende Klammer sein!
+    String token = nextNonWSToken();
+    if ( !token.equals("(") )
+      throwParseError("Illegal "+operator+" syntax '" + token + "'");
+    openBrackets.insert(0,"(");
+
+    openBrackets.insert(0, String.valueOf(paramCount-1)); // noch X weitere Parameter (Trenner) erwartet
+    // parCount-1 Parameter parsen
+    for (int i=0; i<paramCount-1; i++) {
+      // Parameter fuer ITE lesen
+      paramNode[i] = parseRule();
+      if ( !isParameterSeperator( tok.nextToken() ) ) // Seperator entfernen
+        throwParseError("Parameter-Seperator expected");
+      openBrackets.replace(0, 1, String.valueOf(paramCount-(i+2))); // noch X-1 weiterer Parameter (Trenner) erwartet
+    }
+    // den letzten Parameter parsen
+    paramNode[paramNode.length-1] = parseRule();
+    // Funktion-schliessende Klammer entfernen (Pruefung auf Konsistenz mit
+    // oeffnender Klammer erfolgte bereits in checkRuleClosing(.)
+    tok.nextToken();
+    // Parameter-Zaehler von Stack entfernen
+    openBrackets.deleteCharAt(0);
+    // Funktion-oeffnende Klammer von Stack entfernen
+    openBrackets.deleteCharAt(0);
+
+    return paramNode;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  //////////////////////   Hilfsmethoden   //////////////////////////
+  ///////////////////////////////////////////////////////////////////
+  /**
+   * Wirft eine {@link IllegalArgumentException} mit der angegebenen Fehlermeldung.
+   * Als zusaetzlicher Hinweis wird das Formel-Praefix ausgegeben nach dem
+   * der Fehler aufgetreten ist.
+   * @param mess Fehler-Meldung
+   */
+  protected void throwParseError(String mess) {
+    String errorPos = tok.getReadTokens();
+//    // Die Klammer entfernen, die am Anfang kuenstlich eingefuegt wurde
+//    errorPos = errorPos.substring(1);
+    throw new IllegalArgumentException("Parse-Error at '"+errorPos+"': "+mess);
+  }
+
+  /**
+   * Liefert einen Double- oder String-Wert aus einem String. String-Konstanten
+   * muessen in " oder ' gekapselt sein, um als solche interpretiert zu werden.
+   * Die Wildcards {@code TRUE} und {@code FALSE} werden in 1 bzw. 0 umgewandelt.
+   * @param token umzuwandelnde Zeichenkette
+   * @exception IllegalArgumentException falls die Umwandlung nicht vorgenommen
+   *            werden kann
+   */
+  protected Object getConstantFromString(String token) {
+    // Sonderfall: String-Konstante in Anfuehrungsstrichen
+    if ( token.length()>0 && isStringEncapsulationChar(token.substring(0,1)) )
+      return token.substring(1, token.length()-1);
+    // Sonderfall: TRUE/FALSE als Wildcards fuer 1/0
+    if ( token.equalsIgnoreCase("TRUE") )
+      return 1;
+    if ( token.equalsIgnoreCase("FALSE") )
+      return 0;
+    // Sonst eine numerische Konstante
+    return getDoubleFromString(token);
+  }
+
+  /**
+   * Liefert einen Integer-Wert aus einem String.
+   * @param token umzuwandelnde Zeichenkette
+   * @exception IllegalArgumentException falls die Umwandlung nicht vorgenommen
+   *            werden kann
+   */
+  protected int getIntFromString(String token) {
+    token = token.trim();
+    try {
+      return Integer.parseInt(token);
+    }
+    catch (Exception err) {
+      throwParseError("Illegal character '" + token + "' (int expected)");
+    }
+    return 0;
+  }
+
+  /**
+   * Liefert einen Double-Wert aus einem String.
+   * @param token umzuwandelnde Zeichenkette
+   * @exception IllegalArgumentException falls die Umwandlung nicht vorgenommen
+   *            werden kann
+   */
+  protected double getDoubleFromString(String token) {
+    token = token.trim();
+    // Sonderfall: Bei alleinstehender negativer Konstante ist im Token nur
+    //             das Minus-Zeichen enthalten! Zahl-Token muss angehaengt werden.
+    if ( token.equals("-") )
+      token += nextNonWSToken();
+
+    try {
+      return Double.parseDouble(token);
+    }
+    catch (Exception err) {
+      throwParseError("Illegal character '" + token + "' (number expected)");
+    }
+    return 0;
+  }
+
+  /**
+   * Liefert das naechste Token aus dem Tokenizer, das nicht ein White-Space
+   * ist.
+   */
+  protected String nextNonWSToken() {
+    String token = null;
+    while ( (token = tok.nextToken()).trim().equals("") );
+    return token.trim();
+  }
+
+  /**
+   * Legt solange Token zurueck auf den Tokenizer, bis das zurueckgelegte
+   * Token <b>kein</b> White-Space ist.
+   */
+  protected void pushbackWithWSToken() {
+    while ( tok.pushback().trim().equals("") );
+  }
+
+}

Added: trunk/src/schmitzm/lang/tree/TreeNode.java
===================================================================
--- trunk/src/schmitzm/lang/tree/TreeNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/lang/tree/TreeNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,150 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.lang.tree;
+
+/**
+ * Diese Klasse stellt einen Knoten in einem Baum dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class TreeNode<E> {
+  /** Speichert den Vater-Knoten. */
+  protected TreeNode<E> parent = null;
+  /** Speichert das Objekt, das in dem Knoten gespeichert wird. */
+  protected E object = null;
+
+  /**
+   * Erzeugt einen Wurzel-Knoten ohne Nachfolger.
+   */
+  public TreeNode() {
+    this((E)null);
+  }
+
+  /**
+   * Erzeugt einen Wurzel-Knoten ohne Nachfolger.
+   * @param object Objekt, das in dem Knoten gespeichert wird
+   */
+  public TreeNode(E object) {
+    this(object, null);
+  }
+
+  /**
+   * Erzeugt einen Blatt-Knoten.
+   * @param parent direkter Vorgaenger-Knoten
+   */
+  public TreeNode(TreeNode<E> parent) {
+    this(null, parent);
+  }
+
+  /**
+   * Erzeugt einen Blatt-Knoten.
+   * @param object Objekt, das in dem Knoten gespeichert wird
+   * @param parent direkter Vorgaenger-Knoten
+   */
+  public TreeNode(E object, TreeNode<E> parent) {
+    setObject( object );
+    setParent( parent );
+  }
+
+  /**
+   * Liefert den Wurzel-Knoten.
+   */
+  public TreeNode<E> getRoot() {
+    if ( this.isRoot() )
+      return this;
+    return parent.getRoot();
+  }
+
+  /**
+   * Prueft, ob ein Knoten als Vater- oder Kind-Knoten fuer diesen Knoten
+   * verwendet werden kann
+   * @param node zu pruefende Knoten
+   * @exception RuntimeException falls der Knoten nicht geeignet ist
+   */
+  public abstract void checkNode(TreeNode<E> node);
+
+  /**
+   * Prueft, ob es sich um einen Wurzelknoten handelt.
+   * @return <code>true<code> gdw. der Knoten keinen Vater hat
+   * @see #getParent()
+   */
+  public boolean isRoot() {
+    return getParent() == null;
+  }
+
+  /**
+   * Prueft, ob es sich um einen Blattknoten handelt.
+   * @return <code>true<code> gdw. alle Kinder {@code null} sind
+   */
+  public boolean isLeaf() {
+    for (int i=0; i<getChildCount(); i++)
+      if ( getChild(i) != null )
+        return false;
+    return true;
+  }
+
+  /**
+   * Prueft, ob es sich um einen inneren Knoten handelt.
+   * @return <code>true<code> gdw. der Knoten keine Wurzel und kein Blatt ist
+   */
+  public boolean isInnerNode() {
+    return !isRoot() && !isLeaf();
+  }
+
+  /**
+   * Liefert den direkten Vaterknoten.
+   */
+  public TreeNode<E> getParent() {
+    return parent;
+  }
+
+  /**
+   * Setzt den direkten Vorgaenger-Knoten
+   * @param parent neuer direkter Vorgaenger
+   */
+  public void setParent(TreeNode<E> parent) {
+    checkNode(parent);
+    this.parent = parent;
+  }
+
+  /**
+   * Liefert die Anzahl der Kindknoten.
+   */
+  public abstract int getChildCount();
+
+  /**
+   * Liefert einen Kindknoten.
+   * @param i Index (beginnend bei 0)
+   */
+  public abstract TreeNode<E> getChild(int i);
+
+  /**
+   * Setzt einen Kindknoten.
+   * @param i Index (beginnend bei 0)
+   * @param child ein Kindknoten
+   */
+  public abstract void setChild(int i, TreeNode<E> child);
+
+  /**
+   * Liefert das Objekt, das in dem Knoten gespeichert ist.
+   */
+  public E getObject() {
+    return object;
+  }
+
+  /**
+   * Setzt das Objekt, das in dem Knoten gespeichert ist.
+   */
+  public void setObject(E object) {
+    this.object = object;
+  }
+}

Added: trunk/src/schmitzm/swing/BooleanInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/BooleanInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/BooleanInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,102 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.JCheckBox;
+import javax.swing.JTextField;
+import schmitzm.swing.InputOption;
+
+/**
+ * Diese Klasse stellt eine boolsche Eingabe-Option fuer das {@link MultipleOptionPane}
+ * dar. Diese wird durch eine {@link JCheckBox} dargestellt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BooleanInputOption extends InputOption {
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label    Beschreibung
+   * @param defValue gibt an, ob die Checkbox standardmaessig aktiviert ist
+   */
+  public BooleanInputOption(String label, boolean defValue) {
+    super(label,false);
+    // Bei einer Checkbox soll die Beschreibung nicht oberhalb
+    // des Eingabefeldes zu stehen, sondern mit der Checkbox in
+    // einer Zeile
+    // -> JLabel entfernen und statt dessen den Text der Checkbox setzen
+    this.remove( this.descLabel );
+    ((JCheckBox)inpComp).setText(label);
+    // Standardwert setzen
+    setValue( defValue );
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option. Diese ist standardmaessig deaktiviert.
+   * @param label Beschreibung
+   */
+  public BooleanInputOption(String label) {
+    this(label,false);
+  }
+
+  /**
+   * Erzeugt eine neue Instanz von {@link JCheckBox}.
+   */
+  protected JCheckBox createInputComponent() {
+    return new JCheckBox();
+  }
+
+  /**
+   * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+   * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+   * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+   *         (siehe {@link #isInputValid()})
+   */
+  public Boolean getValue() {
+    return (Boolean)super.getValue();
+  }
+
+  /**
+   * Liefert immer <code>true</code>, da keine speziellen Anforderungen
+   * an die Text-Eingabe gestellt werden.
+   */
+  protected boolean performIsInputValid() {
+    return true;
+  }
+
+  /**
+   * Liefert immer <code>false</code>, da es keine Leereingabe gibt. Eine nicht
+   * aktivierte Checkbox bedeutet den Wert FALSE.
+   */
+  protected boolean performIsInputEmpty() {
+    return false;
+  }
+
+  /**
+   * Liefert <code>true</code>, falls die Checkbox aktiviert ist.
+   * @return einen <code>boolean</code>-Wert (<b>kein</b> {@link Boolean}-Objekt!)
+   */
+  protected Boolean performGetValue() {
+    return ((JCheckBox)inpComp).isSelected();
+  }
+
+  /**
+   * (De)aktiviert die Checkbox.
+   * @param active definiert den neuen Status (muss vom Typ {@link Boolean} sein!)
+   * @return <code>false</code> falls kein {@link Boolean} uebergeben wird.
+   */
+  protected boolean performSetValue(Object active) {
+    if ( !(active instanceof Boolean) )
+      return false;
+    ((JCheckBox)inpComp).setSelected((Boolean)active);
+    return true;
+  }
+}

Added: trunk/src/schmitzm/swing/BrowseInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/BrowseInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/BrowseInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,145 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Container;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import javax.swing.JButton;
+import javax.swing.JTextField;
+
+
+import schmitzm.swing.ManualInputOption;
+import schmitzm.swing.event.InputOptionAdapter;
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.ExceptionDialog;
+
+/**
+ * Die <code>BrowseInputOption</code> erweitert die {@linkplain ManualInputOption manuelle Text-Eingabe}
+ * um eine Browse-Aktion. Welche konkrete Aktion beim Klick auf den Browse-Button
+ * ausgefuehrt wird, bestimmt die jeweinige Implementierung.<br>
+ * Der Wert dieser Eingabe-Option stellt keinen Text dar, sondern ein
+ * zugehoeriges Objekt. Um ein Objekt sowohl ueber eine manuelle Texteingabe,
+ * als auch ueber die Browse-Aktion angeben zu koennen, spezifiziert
+ * <code>BrowseInputOption</code> zwei Methoden ({@link #convertFromString(String)},
+ * {@link #convertToString(Object)}), ueber die eine entsprechende Umwandung zu
+ * implementieren ist.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class BrowseInputOption  extends ManualInputOption {
+  /** Speichert den aktuellen Wert der Eingabe-Option. */
+  protected Object object = null;
+  /** Container enthaelt das Text-Eingabefeld und den Browse-Button. */
+  protected Container inpCompCont = new Container();
+  /** Der Button fuer die Browse-Aktion. */
+  protected JButton browseButton = new JButton("...");
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label Beschreibung der Eingabe
+   * @param inputNeeded gibt an, ob eine Eingabe in der Option erforderlich ist
+   * @param defValue Standard-Belegung fuer die Eingabe-Option
+   */
+  public BrowseInputOption(String label, boolean inputNeeded, Object defValue) {
+    super(label, inputNeeded, null);
+    setValue(defValue);
+
+    // Browse-Button formatieren
+    browseButton.setPreferredSize( new Dimension(30,10) );
+    browseButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        browseAction();
+      }
+    });
+
+    // Eingabe-Feld formatieren
+    SwingUtil.setPreferredWidth(inpComp,200);
+    this.addInputOptionListener( new InputOptionAdapter() {
+      public void optionLostFocus(InputOption option) {
+        try {
+          setValue( convertFromString( ((JTextField)inpComp).getText() ) );
+        } catch (Exception err) {
+          ExceptionDialog.show(OPTION_COMPONENT,err,SwingUtil.RESOURCE.getString("Error"),SwingUtil.RESOURCE.getString("InvalidInputMess"));
+          setValue( getValue() );
+        }
+      }
+    });
+
+    // Neben dem Eingabefeld noch ein Browse-Button
+    inpCompCont.setLayout( new BorderLayout(10,0) );
+    inpCompCont.add(this.inpComp, BorderLayout.CENTER);
+    inpCompCont.add(this.browseButton, BorderLayout.EAST);
+    // normale Eingabe-Komponente durch Container ersetzen
+    remove(inpComp);
+    add(inpCompCont,BorderLayout.SOUTH);
+  }
+
+  /**
+   * Aktion des Browse-Button.
+   */
+  private void browseAction() {
+    Object newObj = performBrowse(this.getValue());
+    // Browse-Aktion abgebrochen
+    if ( newObj == null )
+      return;
+    this.setValue(newObj);
+  }
+
+  /**
+   * Liefert das zu der Text-Eingabe gehoerende Objekt.
+   */
+  protected Object performGetValue() {
+    return object;
+  }
+
+  /**
+   * Setzt den Wert der Eingabe-Option und befuellt entsprechend das
+   * Text-Feld der Eingabe-Option.
+   * @param newValue neuer Wert der Eingabe-Option (ein Objekt, <b>nicht</b> ein
+   *                 Wert fuer das Textfeld!)
+   * @return <code>false</code> falls das Objekt nicht in einen String umgewandelt
+   *         werden konnte
+   */
+  protected boolean performSetValue(Object newValue) {
+    try {
+      ((JTextField)inpComp).setText(convertToString(newValue));
+      this.object = newValue;
+      return true;
+    } catch (Exception err) {
+      return false;
+    }
+  }
+
+  /**
+   * Implementiert die Browse-Aktion die ausgefuehrt wird, wenn der Button
+   * der Eingabe-Option gedrueckt wird. Liefert ein neues Objekt fuer die
+   * Eingabe-Option.
+   * @param actValue aktueller Wert der Eingabe-Option
+   * @return <code>null</code> falls die Browse-Aktion abgebrochen wird
+   */
+  public abstract Object performBrowse(Object actValue);
+
+  /**
+   * Liefert das Objekt zu der Text-Eingabe der Option.
+   * @param objectStr Objekt-String
+   */
+  public abstract Object convertFromString(String objectStr);
+
+  /**
+   * Erzeugt einen (eindeutigen) String fuer ein Objekt.
+   * @param object Objekt
+   */
+  public abstract String convertToString(Object object);
+}

Added: trunk/src/schmitzm/swing/ButtonGroup.java
===================================================================
--- trunk/src/schmitzm/swing/ButtonGroup.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ButtonGroup.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,73 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.JRadioButton;
+import javax.swing.ButtonModel;
+import javax.swing.AbstractButton;
+
+/**
+ * Diese Klasse erweitert die {@link javax.swing.ButtonGroup javax.swing.ButtonGroup}
+ * um die Option, alle Button der Gruppe wieder zu deaktivieren. Dies geschieht,
+ * in dem immer ein "unsichtbarer" Dummy-Button Bestandteil der Gruppe ist,
+ * der aktiviert wird, wenn alle anderen Butten deaktiviert sein sollen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ButtonGroup extends javax.swing.ButtonGroup {
+  private JRadioButton deactivationButton = new JRadioButton();
+
+  /**
+   * Erzeugt eine neue Button-Gruppe.
+   */
+  public ButtonGroup() {
+    super();
+    this.add(deactivationButton);
+  }
+
+  /**
+   * Deaktiviert alle Button der Gruppe.
+   */
+  public void setUnselected() {
+    deactivationButton.setSelected(true);
+  }
+
+//  /**
+//   * Liefert die Anzahl der Button innerhalb der Gruppe. Der Dummy-Button wird
+//   * dabei nicht mit gerechnet!
+//   */
+//  public int getButtonCount() {
+//    return super.getButtonCount()-1;
+//  }
+
+  /**
+   * Prueft, ob irgendein Button der Gruppe aktiviert ist. Der Dummy-Button wird
+   * dabei nicht beruecksichtigt!
+   */
+  public boolean isSelected(ButtonModel model) {
+    return super.isSelected(model) && !deactivationButton.isSelected();
+  }
+
+  /**
+   * Liefert den Button der Gruppe, der selektiert ist. Der Dummy-Button wird
+   * dabei nicht beruecksichtigt!
+   * @return <code>null</code> wenn kein Button selektiert ist
+   */
+  public AbstractButton getSelectedButton() {
+    for (int i=0; i<this.buttons.size(); i++) {
+      AbstractButton button = buttons.elementAt(i);
+      if ( button.isSelected() && button != deactivationButton )
+        return buttons.elementAt(i);
+    }
+    return null;
+  }
+}

Added: trunk/src/schmitzm/swing/CaptionsChangeable.java
===================================================================
--- trunk/src/schmitzm/swing/CaptionsChangeable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/CaptionsChangeable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,45 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.util.Map;
+
+// fuer Doku
+import javax.swing.JPanel;
+import schmitzm.swing.CaptionsChangeablePanel;
+
+/**
+ * Dieses Interface ermoeglicht es die Beschriftungen in einer Swing-Komponente
+ * (Labels und Buttons) nachtraeglich zu aendern. Zum Beispiel, wenn in einer
+ * uebergeordneten Anwendung die Sprache gewechselt wird.<br>
+ * Beim Aufruf von {@link #resetCaptions(Map)} muss die Komponente, welche
+ * {@code CaptionsChangeable} implmentiert, gewaehrleisten, dass alle Beschriftungen
+ * entsprechend der uebergebenen {@link Map} geaendert werden. Hierfuer sollte die
+ * {@code CaptionsChangeable}-Komponente eindeutige Key-Konstanten implementieren,
+ * ueber die die uebergeordnete Anwendung die neuen Beschriftungen in der Map
+ * hinterlegen kann.<br>
+ * Zudem sollte eine {@code CaptionsChangeable}-Komponente den {@link #resetCaptions(Map)}-Aufruf
+ * an alle in ihr enthaltenen {@code CaptionsChangeable}-Komponenten weiterleiten.
+ * {@link CaptionsChangeablePanel} stellt ein ansonsten normales {@link JPanel}
+ * dar, welches diese Funktionalitaet bereits implementiert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of
+ *   Bonn/Germany)
+ * @version 1.0
+ */
+public interface CaptionsChangeable {
+  /**
+   * Aktualisiert alle Beschriftungen der Komponente entsprechend der uebergebenen
+   * Map.
+   * @param captionMap enthaelt die neuen Beschriftungen
+   */
+  public void resetCaptions(Map<String, Object> captionMap);
+}

Added: trunk/src/schmitzm/swing/CaptionsChangeablePanel.java
===================================================================
--- trunk/src/schmitzm/swing/CaptionsChangeablePanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/CaptionsChangeablePanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,105 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.LayoutManager;
+import java.util.Map;
+import java.util.HashMap;
+
+// fuer Doku
+import java.awt.FlowLayout;
+
+/**
+ * Diese Klasse stellt ein normales {@link JPanel} dar, welches
+ * jedoch die {@link CaptionsChangeable}-Funktionaelitaet beim Aufruf von
+ * {@link #resetCaptions(Map)} an alle im Panel enthaltenen
+ * {@link CaptionsChangeable}-Komponenten weiterleitet.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class CaptionsChangeablePanel extends JPanel implements CaptionsChangeable {
+  /**
+   * Erzeugt ein neues Panel.
+   * @param layout Layout fuer das Panel
+   * @param isDoubleBuffered {@code true} fuer "double-buffering", welches zwar
+   *        zusaetzlichen Speicher benoetigt, aber schnelle und flicker-freie
+   *        Updates gewaehrleistet
+   */
+  public CaptionsChangeablePanel(LayoutManager layout, boolean isDoubleBuffered) {
+    super(layout, isDoubleBuffered);
+  }
+
+  /**
+   * Erzeugt ein gepuffertes Panel.
+   * @param layout Layout fuer das Panel
+   */
+  public CaptionsChangeablePanel(LayoutManager layout) {
+    super(layout);
+  }
+
+  /**
+   * Erzeugt ein {@link FlowLayout}-Panel.
+   * @param isDoubleBuffered {@code true} fuer "double-buffering", welches zwar
+   *        zusaetzlichen Speicher benoetigt, aber schnelle und flicker-freie
+   *        Updates gewaehrleistet
+   */
+  public CaptionsChangeablePanel(boolean isDoubleBuffered) {
+    super(isDoubleBuffered);
+  }
+
+  /**
+   * Erzeugt ein gepuffertes {@link FlowLayout}-Panel.
+   */
+  public CaptionsChangeablePanel() {
+    super();
+  }
+
+  /**
+   * Startet die Aktualisierung der Beschriftungen an alle im Panel
+   * enthaltenen {@link CaptionsChangeable}-Komponenten mit einer leeren
+   * {@link HashMap}.
+   */
+  public void resetCaptions() {
+    resetCaptions(new HashMap<String,Object>());
+  }
+
+  /**
+   * Leitet die Aktualisierung der Beschriftungen an alle im Panel
+   * enthaltenen {@link CaptionsChangeable}-Komponenten weiter.
+   * @param captionMap enthaelt die neuen Beschriftungen
+   */
+  public void resetCaptions(Map<String, Object> captionMap) {
+    resetCaptions(this,captionMap);
+  }
+
+  /**
+   * Leitet die Aktualisierung der Beschriftungen an alle im Container {@code container}
+   * enthaltenen {@link CaptionsChangeable}-Komponenten weiter.
+   * Zudem wird die Auswertung rekursiv fuer alle in {@code container} enthaltenen
+   * {@link Container} fortgefuehrt.
+   * @param container  ein Container der weitere {@link Component Components}
+   *                   enthaelt
+   * @param captionMap enthaelt die neuen Beschriftungen
+   */
+  public static void resetCaptions(Container container, Map<String, Object> captionMap) {
+    Component[] innerComponents = container.getComponents();
+    for (Component c : innerComponents) {
+      if (c instanceof CaptionsChangeable)
+        ( (CaptionsChangeable) c).resetCaptions(captionMap);
+      if (c instanceof Container)
+        resetCaptions((Container)c, captionMap);
+    }
+  }
+
+}

Added: trunk/src/schmitzm/swing/CircleIcon.java
===================================================================
--- trunk/src/schmitzm/swing/CircleIcon.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/CircleIcon.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,71 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.Color;
+import java.awt.Graphics;
+import javax.swing.Icon;
+
+/**
+ * Diese Klasse stellt ein Icon in Form eines ausgefuellten Kreises
+ * dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class CircleIcon implements Icon
+{
+    private int   width;
+    private int   height;
+    private Color color;
+
+    /**
+     * Erzeugt ein Icon mit einem ausgefuellten Kreis.
+     * @param width Breite des Kreises
+     * @param height Hoehe des Kreises
+     * @param color Farbe des Kreises
+     */
+    public CircleIcon(int width, int height, Color color)
+    {
+        this.width  = width;
+        this.height = height;
+        this.color  = color;
+    }
+
+    /**
+     * Malt den Kreis an die angegeben Stelle (x,y) in das Graphics-
+     * Objekt.
+     */
+    public void paintIcon(Component c, Graphics g, int x, int y)
+    {
+        g.setColor(color);
+        g.fillOval(x,y,width,height);
+    }
+
+    /**
+     * Liefert die Hoehe des Kreises.
+     * @return Hoehe des Kreises in Pixeln
+     */
+    public int getIconHeight()
+    {
+        return height;
+    }
+
+    /**
+     * Liefert die Breite des Kreises.
+     * @return Breite des Kreises in Pixeln
+     */
+    public int getIconWidth()
+    {
+        return width;
+    }
+}

Added: trunk/src/schmitzm/swing/ColorInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/ColorInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ColorInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,156 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Color;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import javax.swing.JLabel;
+import javax.swing.JComponent;
+import javax.swing.JTextField;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.BorderFactory;
+
+/**
+ * Diese Klasse stellt eine Farb-Eingabe-Option fuer das {@link MultipleOptionPane}
+ * dar. Diese wird durch eine {@link JLabel} in der jeweiligen Farbe dargestellt.
+ * Bei einem Klick auf das Label erscheint ein {@link JColorChooser}, ueber den
+ * der Optionswert (Farbe) geaendert werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ColorInputOption extends InputOption {
+  private   JColorChooser colorChooser;
+  private   JDialog       dialog;
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label     Beschreibung
+   * @param inpNeeded gibt an, ob eine Leer-Eingabe erlaubt ist
+   */
+  public ColorInputOption(String label, boolean inpNeeded) {
+    this(label,inpNeeded,null);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label     Beschreibung
+   * @param inpNeeded gibt an, ob eine Leer-Eingabe erlaubt ist
+   * @param defColor  vorgeblendeter Optionswert
+   */
+  public ColorInputOption(String label, boolean inpNeeded, Color defColor) {
+    super(label, inpNeeded);
+    setValue( (defColor != null) ? defColor : new Color(0, 0, 0, 0));
+
+    // Dialog zum Farbe auswaehlen
+    colorChooser = new JColorChooser();
+    dialog = JColorChooser.createDialog(
+        this,
+        "Farbe auswaehlen",
+        true, //modal
+        colorChooser,
+        new ActionListener() { //OK button handler
+             public void actionPerformed(ActionEvent e) {
+                 // wird auf OK geklickt wird die ausgewaehlte Farbe
+                 // uebernommen
+                 setValue(colorChooser.getColor());
+               }
+             },
+        null // CANCEL button handler
+    );
+  }
+
+  /**
+   * Erzeugt eine neue Instanz von {@link JLabel}. Dieses ist transparent und
+   * schwart umrandet.<br>
+   * Bei einem Klick auf das Label oeffnet sich ein {@link JColorChooser}
+   * ueber den eine neue Farbe ausgewaehlt werden kann.
+   */
+  protected JComponent createInputComponent() {
+//    // Eigentlich sollte ein JLabel verwendet werden. Dieses nimmt aber
+//    // nicht automatisch die maximale Container-Breite des uebergeordneten
+//    // Containers (bei SpringLayout) an. Deshalb wird ein TextField "vergewaltigt".
+//    JTextField l = new JTextField("");
+//    l.setEditable(false);
+//    l.setBorder(null);
+    JLabel l = new JLabel(" ");
+    //////////////
+    l.setOpaque(true);
+    //l.setBorder(BorderFactory.createLineBorder(Color.BLACK));
+    l.setBorder(BorderFactory.createRaisedBevelBorder());
+    //l.setBorder(BorderFactory.createLoweredBevelBorder());
+
+    // Beim Klick auf das Label erscheint der Auswahl-Dialog
+    // fuer die Farben
+    l.addMouseListener( new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+        if ( e.getButton() == MouseEvent.BUTTON1 ) {
+          colorChooser.setColor( inpComp.getBackground() );
+          dialog.setVisible(true);
+        }
+      }
+    });
+    return l;
+  }
+
+  /**
+   * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+   * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+   * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+   *         (siehe {@link #isInputValid()})
+   */
+  public Color getValue() {
+    return (Color)super.getValue();
+  }
+
+  /**
+   * Liefert immer <code>true</code>, da keine speziellen Anforderungen
+   * an die Farb-Eingabe gestellt werden.
+   */
+  protected boolean performIsInputValid() {
+    return true;
+  }
+
+  /**
+   * Liefert immer <code>true</code>, wenn keine Farbe (<code>null</code>)
+   * ausgewaehlt ist.
+   */
+  protected boolean performIsInputEmpty() {
+    return inpComp.getBackground() == null;
+  }
+
+  /**
+   * Liefert die aktuell in der Option ausgewaehlte Farbe.
+   */
+  protected Color performGetValue() {
+    return inpComp.getBackground();
+  }
+
+  /**
+   * Setzt den Wert der Farb-Option.
+   * @param newValue neue Farbe (muss eine {@link Color}-Instanz sein)
+   * @return <code>false</code> falls keine {@link Color} uebergeben wird.
+   */
+  protected boolean performSetValue(Object newValue) {
+    if ( !(newValue instanceof Color) )
+      return false;
+    Color color = (Color)newValue;
+    this.inpComp.setBackground(color);
+    this.inpComp.setToolTipText("RGB value: " + color.getRed() + ", "
+                                              + color.getGreen() + ", "
+                                              + color.getBlue());
+    return true;
+  }
+}

Added: trunk/src/schmitzm/swing/Compass.java
===================================================================
--- trunk/src/schmitzm/swing/Compass.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/Compass.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,220 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Color;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.CompassPlot;
+import org.jfree.data.general.DefaultValueDataset;
+import org.jfree.data.general.ValueDataset;
+
+import schmitzm.swing.JPanel;
+
+// fuer Doku
+import java.awt.event.MouseListener;
+import java.util.EventListener;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D;
+import org.jfree.chart.plot.PlotState;
+import org.jfree.chart.plot.PlotRenderingInfo;
+import java.awt.geom.AffineTransform;
+
+/**
+ * Diese Komponente stellt eine Kompass-Nadel dar, deren Ausrichtung ueber
+ * eine Grad-Angabe zwischen 0° und 360° (Nord = 0°; West = 90°) eingestellt wird.
+ * {@link MouseListener} oder aehnliches koennen aus technischen Gruenden
+ * nicht der Komponente direkt zugewiesen werden, sondern muessen dem
+ * {@linkplain #getCompassPane() Content-Pane} zugewiesen werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public  class Compass extends JPanel {
+  /** Ereignis, welches an die Listener gesendet wird, wenn sich der Kompass-Wert
+   *  aendert. */
+  protected ChangeEvent changeEvent = new ChangeEvent(this);
+
+  private ChartPanel chartPanel;
+  private double degree = 0;
+
+  /**
+   * Erzeugt eine neue Kompass-Komponente.
+   */
+  public Compass() {
+    this(0);
+  }
+
+  /**
+   * Erzeugt eine neue Kompass-Komponente.
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   */
+  public Compass(double degree) {
+    super();
+    setLayout( new BorderLayout() );
+    chartPanel = new ChartPanel(
+      null,      // Chart
+      100,       // Width
+      100,       // Height
+      0,         // Min. draw width
+      0,         // Min. draw height
+      9999999,   // Max. draw width
+      9999999,   // Max. draw height
+      true,      // Use buffer
+      true,     // Properties
+      false,     // Save
+      false,     // Print
+      false,     // Zoom
+      false      // Tooltips
+    );
+    setValue(degree);
+    this.add(chartPanel,BorderLayout.CENTER);
+  }
+
+  /**
+   * Setzt die Hintergrund-Farbe (auch fuer alle Unterkomponenten).
+   * @param bgColor Hintergrund-Farbe
+   */
+  public void setBackground(Color bgColor) {
+    setBackground(bgColor, true);
+    if ( chartPanel != null && chartPanel.getChart() != null )
+      chartPanel.getChart().setBackgroundPaint( bgColor );
+  }
+
+  /**
+   * Liefert die Komponente des Kompass fuer {@link MouseListener} oder aehnliches.
+   */
+  public Component getCompassPane() {
+    return chartPanel;
+  }
+
+  /**
+   * Setzt die angezeigte Grad-Angabe und erzeugt ein {@link ChangeEvent} fuer
+   * alle angeschlossenen {@link ChangeListener}.
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   */
+  public void setValue(double degree) {
+    double oldDegree = this.degree;
+    this.degree = degree % 360; // auf ]-360,+360[ normieren
+    this.degree = (this.degree+360) % 360; // auf [0,360[ normierern
+    chartPanel.setChart( createCompassChart( this.degree ) );
+    if (this.degree != oldDegree)
+      fireStateChanged();
+  }
+
+  /**
+   * Liefert die angezeigte Grad-Angabe aus [0°,360°[.
+   */
+  public double getValue() {
+    return degree;
+  }
+
+  /**
+   * Fuegt dem Kompass einen {@link ChangeListener} hinzu. Dieser wird jedesmal
+   * informtiert, wenn sich der Wert des Kompass aendert.
+   * @param listener neuer {@link ChangeListener}
+   */
+  public void addChangeListener(ChangeListener listener) {
+    this.listenerList.add( ChangeListener.class, listener );
+  }
+
+  /**
+   * Entfernt einen {@link ChangeListener} vom Kompass.
+   * @param listener zu entfernender {@link ChangeListener}
+   */
+  public void removeChangeListener(ChangeListener listener) {
+    this.listenerList.remove( ChangeListener.class, listener );
+  }
+
+  /**
+   * Liefert die Anzahl der an den Kompass angeschlossenen {@link ChangeListener}.
+   */
+  public int getListenerCount() {
+    return listenerList.getListenerCount(ChangeListener.class);
+  }
+
+  /**
+   * Liefert an den Kompass angeschlossenen {@link ChangeListener}.
+   */
+  public ChangeListener[] getChangeListeners() {
+    return listenerList.getListeners(ChangeListener.class);
+  }
+
+  /**
+   * Leitet ein {@link ChangeEvent} an alle angeschlossenen {@link ChangeListener}.
+   * @see #addChangeListener(ChangeListener)
+   * @see #removeChangeListener(ChangeListener)
+   */
+  protected void fireStateChanged() {
+    ChangeListener[] listeners = getChangeListeners();
+    for (ChangeListener l : listeners)
+      l.stateChanged(changeEvent);
+  }
+
+//  public void setPreferredSize(Dimension d) {
+//    int min = Math.min(d.height,d.width);
+//    chartPanel.setPreferredSize( new Dimension(min,min) );
+//    super.setPreferredSize(d);
+//  }
+//
+//  public void setSize(Dimension d) {
+//    int min = Math.min(d.height,d.width);
+//    chartPanel.setPreferredSize( new Dimension(min,min) );
+//    super.setSize(d);
+//  }
+//
+//  public void setSize(int w, int h) {
+//    int min = Math.min(w,h);
+//    chartPanel.setSize( new Dimension(min,min) );
+//    super.setSize(w,h);
+//  }
+
+  /**
+   * Erzeugt ein Chart in Form einer Kompass-Nadel.
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   */
+  private JFreeChart createCompassChart(double degree) {
+    // Bei der Chart-Komponente ist Ost = 90°
+    ValueDataset dataset = new DefaultValueDataset(-degree);
+    CompassPlot plot = new CompassPlot(dataset) {
+      public void draw(Graphics2D g, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) {
+//        System.out.println( area + "     " + g.getClipBounds() + "   " + g.getTransform().getScaleX() + "  " + g.getTransform().getScaleY());
+//        double minSize  = Math.min(area.getWidth(), area.getHeight());
+//        double minScale = Math.min(g.getTransform().getScaleX(), g.getTransform().getScaleY() );
+//        double x        = (chartPanel.getWidth()-minSize)/2;
+//        double y        = (chartPanel.getHeight()-minSize)/2;
+//        area.setFrame( x, y, minSize, minSize);
+//        g.setTransform( AffineTransform.getScaleInstance(minScale,minScale) );
+//        System.out.println( area + "     " + g.getClipBounds() + "   " + g.getTransform().getScaleX() + "  " + g.getTransform().getScaleY());
+//        System.out.println();
+        super.draw(g,area,anchor,parentState,info);
+      }
+    };
+    plot.setSeriesNeedle(4);
+    plot.setSeriesPaint(0, Color.black);
+    plot.setSeriesOutlinePaint(0, Color.black);
+    plot.setRosePaint(Color.red);
+    plot.setRoseHighlightPaint(Color.gray);
+    plot.setRoseCenterPaint(Color.white);
+    plot.setDrawBorder(false);
+    JFreeChart chart = new JFreeChart(plot);
+    chart.setBackgroundPaint( getBackground() );
+    return chart;
+  }
+}

Added: trunk/src/schmitzm/swing/ExceptionDialog.java
===================================================================
--- trunk/src/schmitzm/swing/ExceptionDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ExceptionDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,197 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JLabel;
+import javax.swing.JButton;
+import javax.swing.JToggleButton;
+
+import schmitzm.swing.SwingUtil;
+
+/**
+ * Diese Klasse stellt eine modale Fehler-Meldung dar. Diese besteht neben
+ * einer Meldung aus einem Text-Feld, in dem der StackTrace des Fehlers
+ * angezeigt wird.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ExceptionDialog extends JDialog {
+  /** Speichert den angezeigten Fehler. */
+  protected Throwable err = null;
+  /** Label in dem die Meldung angezeigt wird. */
+  protected JLabel  messageLabel = null;
+  /** Button um den Dialog zu schliessen. */
+  protected JButton okButton = null;
+  /** Button um Details anzuzeigen. */
+  protected JToggleButton detailsButton = null;
+  /** Bereich, in dem die Details angezeigt werden. */
+  protected JTextArea detailsTextArea = null;
+  /** ScrollPane, in dem sich die TextArea fuer die Details befinden. */
+  protected JScrollPane detailsScrollPane = null;
+  /** Verbindung zwischen StackTrace-PrintStream und TextArea*/
+  private TextAreaPrintStream detailsPrintStream= null;
+
+  /**
+   * Erzeugt einen neuen Fehler-Dialog. Dem Dialog wird zunaechst noch keine
+   * Fehlermeldung zugeordnet.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @see #setError(Throwable)
+   */
+  public ExceptionDialog(Component parent) {
+    this(parent,null,null,null);
+  }
+
+  /**
+   * Erzeugt einen neuen Fehler-Dialog. Der Dialog wird relativ zum Parent-Fenster
+   * zentriert.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param err      darzustellender Fehler
+   */
+  public ExceptionDialog(Component parent, Throwable err) {
+    this(parent,err,null,null);
+  }
+
+  /**
+   * Erzeugt einen neuen Fehler-Dialog. Der Dialog wird relativ zum Parent-Fenster
+   * zentriert.
+   * @param parent     uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param err        darzustellender Fehler
+   * @param title      Titel fuer das Fenster (kann <code>null</code> sein!)
+   * @param errMessage Meldung, die zu dem Fehler angezeigt angezeigt wird
+   *                   (kann <code>null</code> sein!)
+   */
+  public ExceptionDialog(Component parent, Throwable err, String title, String errMessage) {
+    super((Frame)null,true);
+    if ( err != null && (title==null || title.trim().equals("")) )
+      title = err.getClass().getSimpleName();
+    if ( err != null && (errMessage==null || errMessage.trim().equals("")) )
+      errMessage = err.getMessage();
+
+    // Vorlagen-Dialog erzeugen
+    this.err               = err;
+    this.messageLabel      = new JLabel(errMessage);
+    this.okButton          = new JButton( SwingUtil.RESOURCE.getString("Ok") );
+    this.detailsButton     = new JToggleButton( SwingUtil.RESOURCE.getString("Details"), false );
+    this.detailsTextArea   = new JTextArea(10,60);
+    this.detailsTextArea.setEditable(false);
+    this.detailsPrintStream = new TextAreaPrintStream(detailsTextArea);
+    this.detailsScrollPane  = new JScrollPane(detailsTextArea);
+    this.detailsScrollPane.setVisible(false);
+    this.setError(err);
+    JOptionPane  pane = new JOptionPane(
+      messageLabel,
+      JOptionPane.ERROR_MESSAGE,
+      JOptionPane.DEFAULT_OPTION,
+      null,
+      new Object[] {okButton, detailsButton}
+    );
+    JDialog dialog = pane.createDialog(parent,title);
+
+    // Dialog nach Vorlage initialisieren
+    this.setTitle( dialog.getTitle() );
+    this.setAlwaysOnTop(true); // Fehler soll auch oberhalb von "alwaysOnTop"-
+                               // Komponenten erscheinen
+    this.getContentPane().setLayout(new BorderLayout());
+    this.getContentPane().add(dialog.getContentPane(), BorderLayout.NORTH);
+    this.getContentPane().add(detailsScrollPane, BorderLayout.CENTER);
+//    this.setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
+    this.okButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        setVisible(false);
+      }
+    } );
+    this.detailsButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        detailsScrollPane.setVisible( detailsButton.isSelected() );
+        pack();
+      }
+    } );
+
+    pack();
+    SwingUtil.setRelativeFramePosition(this,SwingUtil.getParentWindow(parent),0.5,0.5);
+  }
+
+  /**
+   * Liefert die angezeigte Fehlermeldung.
+   */
+  public String getMessage() {
+    return  messageLabel.getText();
+  }
+
+  /**
+   * Setzt die anzuzeigende Fehlermeldung.
+   */
+  public void setMessage(String mess) {
+    if ( mess == null || mess.trim().equals("") )
+      mess = getError() != null ? getError().getMessage() : SwingUtil.RESOURCE.getString("Error");
+    messageLabel.setText(mess);
+  }
+
+  /**
+   * Liefert den angezeigten Fehler.
+   */
+  public Throwable getError() {
+    return err;
+  }
+
+  /**
+   * Setzt den angezeigten Fehler.
+   */
+  public void setError(Throwable err) {
+    this.err = err;
+    if ( err == null )
+      detailsTextArea.setText("");
+    else
+      err.printStackTrace(detailsPrintStream);
+    detailsTextArea.select(0,0);
+  }
+
+  /**
+   * Zeigt einen Fehler-Dialog an.
+   * @param parent      uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param err         darzustellender Fehler
+   * @param title       Titel fuer das Fenster (kann <code>null</code> sein!)
+   * @param errMessage  Meldung, die zu dem Fehler angezeigt angezeigt wird
+   *                    (kann <code>null</code> sein!)
+   */
+  public static void show(Component parent, Throwable err, String title, String errMessage) {
+    new ExceptionDialog(parent,err,title,errMessage).setVisible(true);
+  }
+
+  /**
+   * Zeigt einen Fehler-Dialog an.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param err      darzustellender Fehler
+   */
+  public static void show(Component parent, Throwable err) {
+    new ExceptionDialog(parent,err).setVisible(true);
+  }
+
+  /**
+   * Zeigt einen Fehler-Dialog an.
+   * @param err darzustellender Fehler
+   */
+  public static void show(Throwable err) {
+    new ExceptionDialog(null,err).setVisible(true);
+  }
+
+}

Added: trunk/src/schmitzm/swing/ExpansionBar.java
===================================================================
--- trunk/src/schmitzm/swing/ExpansionBar.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ExpansionBar.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,583 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.Dimension;
+import java.awt.Container;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.geom.AffineTransform;
+import javax.swing.JProgressBar;
+import javax.swing.JLabel;
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+
+/**
+ * Ein ExpansionBar aehnelt einem {@link JProgressBar}, mit dem Unterschied,
+ * dass die Auspraegung des ExpansionBar nicht nur in eine Richtung gehen
+ * kann (unten nach oben, oder links nach rechts), sondern ausgehend von einem
+ * vorgegebenen Mitte-Wert in beide Richtungen, je nachdem, ob der gesetzte
+ * Wert groesser oder kleiner ist als der Mitte-Wert.<br>
+ * Waehrend der {@link JProgressBar} als Status-Balken aufgefasst werden kann,
+ * der stetig fortschreitet, dient der der ExpansionBar auch der Anzeige von
+ * negativen Werten (z.B. Abweichungswerten oder Amplituden). Fuer die
+ * Abweichung koennen je ein oberer und unterer Grenzwert festgelegt werden.
+ * Liegt der aktuelle Wert des Balken innerhalb dieser Toleranz, wird er
+ * gruen angezeigt, ansonsten rot.<br>
+ * Optional koennen fuer den Balken Labels mit den Grenz-Werten (Minimum,
+ * Mitte, Maximum) angezeigt werden (siehe {@link #setValueLabelsPainted(boolean)}.
+ * Dies ist per Default jedoch deaktiviert.<br>
+ * <b>Bemerke:</b><br>
+ * Bei vertikaler Darstellung gibt es noch Probleme mit der Label-Darstellung!
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ * @todo Probleme mit vertikaler Label-Darstellung beheben
+ */
+public class ExpansionBar extends Container {
+  /** Konstante fuer eine lineare Darstellung des Balken. */
+  public static final int LINEAR = 9;
+  /** Konstante fuer eine logarithmische Darstellung des Balken. */
+  public static final int LOGARITHMIC = 8;
+  /** Konstante fuer eine vertikale Ausrichtung des Balken.
+   *  @see JProgressBar#VERTICAL */
+  public static final int VERTICAL = JProgressBar.VERTICAL;
+  /** Konstante fuer eine horizontale Ausrichtung des Balken.
+   *  @see JProgressBar#HORIZONTAL */
+  public static final int HORIZONTAL = JProgressBar.HORIZONTAL;
+
+  /** Speichert den Umrechnungsfaktor, um von den gesetzten double-Werten
+   *  auf Integer fuer die {@link JProgressBar}-Instanzen umzurechnen.
+   *  @see #convertToProgressBarValue(double)
+   */
+  protected int fracFact = 0;
+
+  /** Speichert die Farbe, in der der Balken angezeigt wird, wenn der
+   *  Wert innerhalb der Toleranz liegt. <br>
+   *  Default: {@linkplain Color#RED Gruen}*/
+  protected Color toleranceColor = Color.GREEN;
+
+  /** Speichert die Farbe, in der der Balken angezeigt wird, wenn der
+   *  Wert ausserhalb der Toleranz liegt.<br>
+   *  Default: {@linkplain Color#RED Rot} */
+  protected Color intoleranceColor = Color.RED;
+
+  // Beispiel-Schriftart fuer die Labels
+  private Font sampleFont = new JLabel().getFont();
+
+  // Label fuer Wert-Anzeige
+  private JLabel       minLabel = new JLabel();
+  private JLabel       midLabel = new JLabel();
+  private JLabel       maxLabel = new JLabel();
+  // Progressbars fuer die positive und negative Richtung
+  private JProgressBar negBar;
+  private JProgressBar posBar;
+  private JPanel       barContainer = new JPanel();
+
+  // Konfigurationsparameter
+  private double       min           = -100;
+  private int          barMin        = -100;
+  private double       max           = 100;
+  private int          barMax        = 100;
+  private double       mid           = 0;
+  private int          barMid        = 0;
+  private double       minTol        = 0;
+  private double       maxTol        = 0;
+  private int          orient        = HORIZONTAL;
+  private int          type          = LINEAR;
+  private double       value         = 0;
+  private boolean      stringPainted = true;
+  private NumberFormat barNumFormat  = new DecimalFormat("0.00%");
+  private NumberFormat infoNumFormat = new DecimalFormat("0.00%");
+
+  /**
+   * Erzeugt einen neuen linearen Balken. Als Mitte-Wert wird 0 gesetzt und die
+   * Toleranz ist nach oben und unten gleich.<br>
+   * Die Minimum-, Mitte- und Maximum-Label werden
+   * standardmaessig nicht angezeigt, wohl aber der aktuelle Balkenwert.
+   * @param orient    Orientierung des Balken (<code>ExpansionBar.HORIZONTAL</code>
+   *                  oder <code>ExpansionBar.VERTICAL</code>
+   * @param min       Minimalwert des Balkens
+   * @param max       Maximalwert des Balkens
+   * @param tol       Toleranzwert nach oben/unten bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>0</code> und
+   *                  <code>max</code> liegen)
+   * @param form      Bestimmt die Darstellung der Werte im Anzeige-Label des
+   *                  Balken und in der etwaigen Info-Leiste
+   * @see #setValueLabelsPainted(boolean)
+   * @see #setStringPainted(boolean)
+   * @exception IllegalArgumentException falls die Konstellation der angegeben
+   *            Werte unzulaessig ist
+   */
+  public ExpansionBar(int orient, double min, double max, double tol, NumberFormat form) {
+    this(orient,LINEAR,min,max,tol,form);
+  }
+
+  /**
+   * Erzeugt einen neuen Balken. Als Mitte-Wert wird 0 gesetzt und die
+   * Toleranz ist nach oben und unten gleich.<br>
+   * Die Minimum-, Mitte- und Maximum-Label werden
+   * standardmaessig nicht angezeigt, wohl aber der aktuelle Balkenwert.
+   * @param orient    Orientierung des Balken (<code>ExpansionBar.HORIZONTAL</code>
+   *                  oder <code>ExpansionBar.VERTICAL</code>
+   * @param type      Darstellung des Balken (<code>ExpansionBar.LINEAR</code>
+   *                  oder <code>ExpansionBar.LOGARITHMIC</code>
+   * @param min       Minimalwert des Balkens
+   * @param max       Maximalwert des Balkens
+   * @param tol       Toleranzwert nach oben/unten bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>0</code> und
+   *                  <code>max</code> liegen)
+   * @param form      Bestimmt die Darstellung der Werte im Anzeige-Label des
+   *                  Balken und in der etwaigen Info-Leiste
+   * @see #setValueLabelsPainted(boolean)
+   * @see #setStringPainted(boolean)
+   * @exception IllegalArgumentException falls die Konstellation der angegeben
+   *            Werte unzulaessig ist
+   */
+  public ExpansionBar(int orient, int type, double min, double max, double tol, NumberFormat form) {
+    this(orient,type,min,max,0.0,-tol,tol,0.0,form,form);
+  }
+
+  /**
+   * Erzeugt einen neuen linearen Balken. Die Toleranz vom Mitte-Wert ist nach oben
+   * und unten gleich.<br>
+   * Die Minimum-, Mitte- und Maximum-Label werden
+   * standardmaessig nicht angezeigt, wohl aber der aktuelle Balkenwert.
+   * @param orient    Orientierung des Balken (<code>ExpansionBar.HORIZONTAL</code>
+   *                  oder <code>ExpansionBar.VERTICAL</code>
+   * @param min       Minimalwert des Balkens
+   * @param max       Maximalwert des Balkens
+   * @param mid       Mitte-Wert des Balkens (muss zwischen <code>min</code> und
+   *                  <code>max</code> liegen)
+   * @param tol       Toleranzwert (relativ zur Mitte!) bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>0</code> und
+   *                  <code>max</code> liegen)
+   * @param form      Bestimmt die Darstellung der Werte im Anzeige-Label des
+   *                  Balken und in der etwaigen Info-Leiste
+   * @see #setValueLabelsPainted(boolean)
+   * @see #setStringPainted(boolean)
+   * @exception IllegalArgumentException falls die Konstellation der angegeben
+   *            Werte unzulaessig ist
+   */
+  public ExpansionBar(int orient, double min, double max, double mid, double tol, NumberFormat form) {
+    this(orient,LINEAR,min,max,mid,tol,form);
+  }
+
+  /**
+   * Erzeugt einen neuen Balken. Die Toleranz vom Mitte-Wert ist nach oben
+   * und unten gleich.<br>
+   * Die Minimum-, Mitte- und Maximum-Label werden
+   * standardmaessig nicht angezeigt, wohl aber der aktuelle Balkenwert.
+   * @param orient    Orientierung des Balken (<code>ExpansionBar.HORIZONTAL</code>
+   *                  oder <code>ExpansionBar.VERTICAL</code>
+   * @param type      Darstellung des Balken (<code>ExpansionBar.LINEAR</code>
+   *                  oder <code>ExpansionBar.LOGARITHMIC</code>
+   * @param min       Minimalwert des Balkens
+   * @param max       Maximalwert des Balkens
+   * @param mid       Mitte-Wert des Balkens (muss zwischen <code>min</code> und
+   *                  <code>max</code> liegen)
+   * @param tol       Toleranzwert (relativ zur Mitte!) bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>0</code> und
+   *                  <code>max</code> liegen)
+   * @param form      Bestimmt die Darstellung der Werte im Anzeige-Label des
+   *                  Balken und in der etwaigen Info-Leiste
+   * @see #setValueLabelsPainted(boolean)
+   * @see #setStringPainted(boolean)
+   * @exception IllegalArgumentException falls die Konstellation der angegeben
+   *            Werte unzulaessig ist
+   */
+  public ExpansionBar(int orient, int type, double min, double max, double mid, double tol, NumberFormat form) {
+    this(orient,type,min,max,mid,mid-tol,mid+tol,mid,form,form);
+  }
+
+  /**
+   * Erzeugt einen neuen Balken. Die Minimum-, Mitte- und Maximum-Label werden
+   * standardmaessig nicht angezeigt, wohl aber der aktuelle Balkenwert.
+   * @param orient    Orientierung des Balken (<code>ExpansionBar.HORIZONTAL</code>
+   *                  oder <code>ExpansionBar.VERTICAL</code>
+   * @param type      Darstellung des Balken (<code>ExpansionBar.LINEAR</code>
+   *                  oder <code>ExpansionBar.LOGARITHMIC</code>
+   * @param min       Minimalwert des Balkens
+   * @param max       Maximalwert des Balkens
+   * @param mid       Mitte-Wert des Balkens (muss zwischen <code>min</code> und
+   *                  <code>max</code> liegen)
+   * @param minTol    <b>Absoluter</b> Toleranzwert nach unten bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>min</code> und
+   *                  <code>mid</code> liegen)
+   * @param maxTol    <b>Absoluter</b> Toleranzwert nach oben bis zu dem der Balken gruen
+   *                  dargestellt wird (muss zwischen <code>mid</code> und
+   *                  <code>max</code> liegen)
+   * @param initValue Initialer Wert den der Balken annimmt
+   * @param barForm   Bestimmt die Darstellung der Werte im Anzeige-Label des
+   *                  Balken
+   * @param infoForm  Bestimmt die Darstellung der Werte im etwaigen Info-Label
+   *                  unterhalb des Balken
+   * @see #setValueLabelsPainted(boolean)
+   * @see #setStringPainted(boolean)
+   * @exception IllegalArgumentException falls die Konstellation der angegeben
+   *            Werte unzulaessig ist
+   */
+  public ExpansionBar(int orient, int type, double min, double max, double mid, double minTol, double maxTol, double initValue, NumberFormat barForm, NumberFormat infoForm) {
+    super();
+    this.min       = min;
+    this.max       = max;
+    this.mid       = mid;
+    this.minTol    = minTol;
+    this.maxTol    = maxTol;
+    this.orient    = orient;
+    this.type      = type;
+    if ( barForm != null )
+      this.barNumFormat = barForm;
+    if ( infoForm != null )
+      this.infoNumFormat = infoForm;
+    checkValues();
+
+    // Der Progress-Bar kann nur Integers darstellen.
+    // Damit auch kleinere Bereiche (z.B. zwischen 0 und 1) fliessend
+    // dargestellt werden koennen, wird der Bereich auf (mind. 1000 Werte)
+    // skaliert
+    this.fracFact = 1;
+    while(  (max-mid)*fracFact < 1000 || (mid-min)*fracFact < 1000 )
+      fracFact *= 10;
+    this.barMin   = convertToProgressBarValue(min);
+    this.barMax   = convertToProgressBarValue(max);
+    this.barMid   = convertToProgressBarValue(mid);
+
+    // Label erzeugen
+    if (orient == VERTICAL)
+      // Label bekommen vertikale Schrift
+      sampleFont = sampleFont.deriveFont(AffineTransform.getRotateInstance(Math.PI / 2));
+    this.minLabel = new JLabel(infoNumFormat.format(min));
+    this.midLabel = new JLabel(infoNumFormat.format(mid));
+    this.maxLabel = new JLabel(infoNumFormat.format(max));
+    minLabel.setFont(sampleFont);
+    midLabel.setFont(sampleFont);
+    maxLabel.setFont(sampleFont);
+    setValueLabelsPainted(false); // Wert-Labels werden per Default NICHT angezeigt
+
+    // Das Ausgeben des Wert-Strings bewirkt, dass die Balken nicht in
+    // kleinen Bloecken dargestellt werden, sondern durchgehend (letztes
+    // ist notwendig, damit das "Tricksen" mit den Vorder/Hintergrundfarben
+    // beim Negatv-Balken den gewuenschten visuellen Effekt hat!)
+    // --> Es soll aber trotzdem keine (oder hoechstens beim Positiv-Balken)
+    //     Strings angezeigt werden
+    // --> getString()-Methode ueberschreiben und NICHTS zurueckgeben
+    this.posBar = new JProgressBar(orient,barMid,barMax) {
+      public String getString() { return stringPainted ? barNumFormat.format(value) : ""; }
+    };
+    this.negBar = new JProgressBar(orient,barMin,barMid) {
+      public String getString() { return ""; } };
+     // WICHTIG wegen Farbeffekt!!
+    posBar.setStringPainted(true);
+    negBar.setStringPainted(true);
+    posBar.setMinimumSize( new Dimension(0,0) );
+    negBar.setMinimumSize( new Dimension(0,0) );
+    // Negativ-Balken wird invertiert dargestellt, so dass es den Anschein
+    // bekommt, er wuerde sich von rechts nach links (oben nach unten)
+    // bewegen!!!
+    negBar.setForeground( posBar.getBackground() );
+
+    // ProgressBars erhalten keine eigene Border, sondern werden in einen
+    // Container eingefuegt, der eine Border erhaelt (WCIHTIG, da sonst die
+    // Border des Nagativ-Balken in der "Vordergrundfarbe" angezeigt wird und
+    // immer sichtbar ist)
+    posBar.setBorder( BorderFactory.createEmptyBorder() );
+    negBar.setBorder( BorderFactory.createEmptyBorder() );
+    barContainer.setBorder( BorderFactory.createLoweredBevelBorder() );
+    barContainer.setLayout( new GridBagLayout() );
+    // Zwischen die beiden Progress-Bars kommt ein Trennstrich
+    JLabel divComp = new JLabel();
+    divComp.setBorder( BorderFactory.createLineBorder(Color.DARK_GRAY));
+
+    // Komponenten in Container einfuegen
+    this.setLayout( new GridBagLayout() );
+    if ( orient == HORIZONTAL ) {
+      barContainer.add(negBar,new GridBagConstraints(0,0,1,1,0.5,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      barContainer.add(divComp,new GridBagConstraints(1,0,1,1,0.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      barContainer.add(posBar,new GridBagConstraints(2,0,1,1,0.5,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      add(barContainer, new GridBagConstraints(0,0,1,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      add(minLabel,new GridBagConstraints(0,1,1,1,1.0,1.0,GridBagConstraints.NORTHWEST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+      add(midLabel,new GridBagConstraints(0,1,1,1,1.0,1.0,GridBagConstraints.NORTH,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+      add(maxLabel,new GridBagConstraints(0,1,1,1,1.0,1.0,GridBagConstraints.NORTHEAST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    } else {
+      barContainer.add(posBar,new GridBagConstraints(0,0,1,1,1.0,0.5,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      barContainer.add(divComp,new GridBagConstraints(0,1,1,1,1.0,0.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      barContainer.add(negBar,new GridBagConstraints(0,2,1,1,1.0,0.5,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      add(barContainer, new GridBagConstraints(0,0,1,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+      add(minLabel,new GridBagConstraints(1,0,1,1,1.0,1.0,GridBagConstraints.NORTH,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+      add(midLabel,new GridBagConstraints(1,0,1,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+      add(maxLabel,new GridBagConstraints(1,0,1,1,1.0,1.0,GridBagConstraints.SOUTH,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0));
+    }
+    // Default-Wert setzen
+    setValue(initValue);
+  }
+
+  /**
+   * Der ExpansionBar ist ueber zwei {@link JProgressBar}s realisiert. Diese
+   * koennen nur Integers darstellen. Damit auch kleinere Bereiche (z.B.
+   * zwischen 0 und 1) fliessend dargestellt werden koennen, wird der Bereich
+   * auf (mind. 1000 Werte) skaliert. Desweiteren wird ggf. auf eine
+   * logarithmische Darstellung umgerechnet.
+   * @param val umzurechnender Wert
+   * @see #fracFact
+   */
+  protected int convertToProgressBarValue(double val) {
+    if ( type == LOGARITHMIC ) {
+      if ( val >=0 )
+        val = Math.log(1 + val);
+      else
+        val = -Math.log(1 + -val);
+    }
+    return (int)Math.round(val*fracFact);
+  }
+
+  /**
+   * Prueft die durch den Konstruktor gesetzten Initialisierungswerte auf
+   * korrektheit.
+   * <ol>
+   * <li><code>min <= max</code></li>
+   * <li><code>min <= mid <= max</code></li>
+   * <li><code>min <= minTol <= mid</code></li>
+   * <li><code>mid <= maxTol <= max</code></li>
+   * <li><code>orient == ExpansionBar.HORIZONTAL || orient == ExpansionBar.VERTICAL</code></li>
+   * </ol>
+   */
+  protected void checkValues() {
+    if ( min > max )
+      throw new IllegalArgumentException("Minimum value must be less than maximum!");
+    if ( mid < min || mid > max )
+      throw new IllegalArgumentException("Middle value must be between minimum and maximum!");
+    if ( minTol < min || minTol > mid )
+      throw new IllegalArgumentException("Minimum tolerance must be between minimum and middle!");
+    if ( maxTol < mid || maxTol > max )
+      throw new IllegalArgumentException("Maximum tolerance must be between middle and maximum!");
+    if ( orient != HORIZONTAL && orient != VERTICAL )
+      throw new IllegalArgumentException("Orientation value must be ExpansionBar.HORIZONTAL or ExpansionBar.VERTICAL!");
+  }
+
+  /**
+   * Liefert den Minimum-Wert des Balkens.
+   */
+  public double getMinimum() {
+    return min;
+  }
+
+  /**
+   * Liefert den Maximum-Wert des Balkens.
+   */
+  public double getMaximum() {
+    return max;
+  }
+
+  /**
+   * Liefert den Mitte-Wert des Balkens.
+   */
+  public double getMiddle() {
+    return mid;
+  }
+
+  /**
+   * Liefert den (absoluten) Wert nach unten, bis zu dessen Wert der
+   * Balkens in gruen dargstellt wird.
+   */
+  public double getMinimumTolerance() {
+    return minTol;
+  }
+
+  /**
+   * Setzt den (absoluten) Wert nach unten, bis zu dessen Wert der
+   * Balkens in gruen dargstellt wird.
+   */
+  public void setMinimumTolerance(double minTol) {
+    if ( minTol < min || minTol > mid )
+      throw new IllegalArgumentException("Minimum tolerance must be between minimum and middle!");
+    this.minTol = minTol;;
+    updateForeground();
+  }
+
+  /**
+   * Liefert den (absoluten) Wert nach oben, bis zu dessen Wert der
+   * Balkens in gruen dargstellt wird.
+   */
+  public double getMaximumTolerance() {
+    return maxTol;
+  }
+
+  /**
+   * Setzt den (absoluten) Wert nach oben, bis zu dessen Wert der
+   * Balkens in gruen dargstellt wird.
+   */
+  public void setMaximumTolerance(double maxTol) {
+    if ( maxTol < mid || maxTol > max )
+      throw new IllegalArgumentException("Maximum tolerance must be between middle and maximum!");
+    this.maxTol = maxTol;
+    updateForeground();
+  }
+
+  /**
+   * Liefert den aktuellen Wert des Balkens.
+   */
+  public double getValue() {
+    return value;
+  }
+
+  /**
+   * Setzt den aktuellen Wert des Balkens.
+   */
+  public void setValue(double value) {
+    this.value = value;
+    // Farbe aktualsieren
+    updateForeground();
+
+    // angezeigt wird nur bis zum Minimum/Maximum
+    value = Math.min(value,max);
+    value = Math.max(value,min);
+
+    if (value > mid) {
+      // Bei werden oberhalb der Mitte wird der Negativ-Balken auf
+      // die Mitte (also "nichts") gesetzt und der Positiv-Balken
+      // auf den Wert oberhalb der Mitte
+      negBar.setValue(barMid);
+      posBar.setValue(convertToProgressBarValue(value)-barMid);
+    } else {
+      // Bei werden unterhalb der Mitte wird der Positiv-Balken auf
+      // die Mitte (also "nichts") gesetzt und der Negativ-Balken
+      // auf den Wert oberhalb der Mitte
+      posBar.setValue(barMid);
+      negBar.setValue(convertToProgressBarValue(value));
+    }
+    // String neu setzten, damit er neu angezeigt wird. Diese Aktualisierung
+    // erfolgt sonst nicht immer korrekt?!
+    posBar.setString( posBar.getString() );
+  }
+
+  /**
+   * Prueft, ob die Minimum/Mitte/Maximum-Labels angezeigt werden.
+   */
+  public boolean getValueLabelsPainted() {
+    return minLabel.isVisible();
+  }
+
+  /**
+   * Zeigt die Minimum/Mitte/Maximum-Labels an oder verbirgt sie.
+   */
+  public void setValueLabelsPainted(boolean visible) {
+    minLabel.setVisible(visible);
+    midLabel.setVisible(visible);
+    maxLabel.setVisible(visible);
+  }
+
+  /**
+   * Prueft, ob der Balken-Wert angezeigt wird.
+   */
+  public boolean getStringPainted() {
+    return this.stringPainted;
+  }
+
+  /**
+   * Zeigt den Balken-Wert an oder verbirgt ihn.
+   */
+  public void setStringPainted(boolean strPaint) {
+    this.stringPainted = strPaint;
+    // irgendwas "anzeigen" damit die Darstellung des
+    // Progressbars aktualisiert wird
+    posBar.setString("");
+  }
+
+  /**
+   * Aktualisiert die Vordergrundfarbe des Balkens, je nachdem, ob der
+   * Balkenwert aktuell innerhalb oder ausserhalb der Toleranz liegt.
+   */
+  protected void updateForeground() {
+    if ( minTol <= value && value <= maxTol )
+      setForeground( toleranceColor );
+    else
+      setForeground( intoleranceColor );
+  }
+
+  /**
+   * Setzt die Farbe, die angezeigt wird, wenn der Balkenwert innerhalb der
+   * Toleranz liegt.
+   */
+  public void setToleranceColor(Color color) {
+    this.toleranceColor = color;
+    updateForeground();
+  }
+
+  /**
+   * Liefert die Farbe, die angezeigt wird, wenn der Balkenwert innerhalb der
+   * Toleranz liegt.
+   */
+  public Color getToleranceColor() {
+    return toleranceColor;
+  }
+
+  /**
+   * Setzt die Farbe, die angezeigt wird, wenn der Balkenwert ausserhalb der
+   * Toleranz liegt.
+   */
+  public void setIntoleranceColor(Color color) {
+    this.intoleranceColor = color;
+    updateForeground();
+  }
+
+  /**
+   * Liefert die Farbe, die angezeigt wird, wenn der Balkenwert ausserhalb der
+   * Toleranz liegt.
+   */
+  public Color getIntoleranceColor() {
+    return intoleranceColor;
+  }
+
+  /**
+   * Liefert die aktuelle Vordergrund-Farbe des Balkens.
+   */
+  public Color getForeground() {
+    return posBar.getForeground();
+  }
+
+  /**
+   * Setzt die Vordergrundfarbe des Balken unabhaengig davon, ob der
+   * Wert innerhalb oder ausserhalb der Toleranz liegt.<br>
+   * <b>Bemerke:</b><br>
+   * Dies beeinflusst die (In)Toleranz-Farben nicht! Bei der naechsten
+   * Aktualisierung (z.B. {@link #setValue(double)}) wird die Farbe wieder
+   * entsprechend der (In)Toleranz-Einstellungen gesetzt.
+   * @see #setToleranceColor(Color)
+   * @see #setIntoleranceColor(Color)
+   */
+  public void setForeground(Color color) {
+    posBar.setForeground( color );
+    negBar.setBackground( color );
+  }
+
+  /**
+   * Liefert die Hintergrund-Farbe des Balkens.
+   */
+  public Color getBackground() {
+    return posBar.getBackground();
+  }
+
+  /**
+   * Setzt die Hintergrund-Farbe des Balkens.
+   */
+  public void setBackground(Color color) {
+    posBar.setBackground( color );
+    negBar.setForeground( color );
+  }
+
+}

Added: trunk/src/schmitzm/swing/FileInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/FileInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/FileInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,103 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.io.File;
+import javax.swing.JFileChooser;
+
+/**
+ * Diese Eingabe-Option dient dazu eine Datei-Angabe zu vorzunehmen. Dies
+ * kann ueber manuelle Eingabe erfolgen, oder durch Browsen mittels einem
+ * {@link JFileChooser}. Der Wert, den die Eingabe-Option repraesentiert stellt
+ * ein {@link File}-Objekt dar.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class FileInputOption extends BrowseInputOption {
+  private JFileChooser fileChooser = new JFileChooser();
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label Beschreibung der Eingabe-Option
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   * @param defValue Standard-Wert der vorgeblendet wird
+   */
+  public FileInputOption(String label, boolean inputNeeded, File defValue) {
+    super(label,inputNeeded,/*defValue == null ? new File(".") :*/ defValue);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label Beschreibung der Eingabe-Option
+   * @param inputNeeded bestimmt, ob eine Eingabe erforderlich ist
+   */
+  public FileInputOption(String label, boolean inputNeeded) {
+    this(label,inputNeeded,null);
+  }
+
+  /**
+   * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+   * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+   * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+   *         (siehe {@link #isInputValid()})
+   */
+  public File getValue() {
+    return (File)super.getValue();
+  }
+
+  /**
+   * Liefert den Dateiauswahl-Dialog fuer die Browse-Aktion.
+   * @see JFileChooser
+   */
+  public JFileChooser getFileChooser() {
+    return fileChooser;
+  }
+
+  /**
+   * Liefert das ein {@link File}-Objekt zu der Text-Eingabe (Dateipfad)
+   * der Option.
+   * @param objectStr Objekt-String
+   */
+  public File convertFromString(String objectStr) {
+    return objectStr == null || objectStr.trim().equals("") ? null : new File(objectStr);
+  }
+
+  /**
+   * Liefert den kompletten Datei-Pfad fuer die durch die Eingabe-Option
+   * repraesentierten {@link File}.
+   * @param object {@link File}-Instanz
+   * @return {@code null}, fall kein {@link File}-Objekt uebergeben wird
+   */
+  public String convertToString(Object object) {
+//    if ( !(object instanceof File) )
+//      return null;
+    return ((File)object).getAbsolutePath();
+  }
+
+  /**
+   * Implementiert die Browse-Aktion die ausgefuehrt wird, wenn der Button der
+   * Eingabe-Option gedrueckt wird. Oeffnet einen {@link JFileChooser}.
+   * @param actValue aktueller Wert der Eingabe-Option
+   * @return <code>null</code> falls die Browse-Aktion abgebrochen wird
+   */
+  public File performBrowse(Object actValue) {
+    fileChooser.setSelectedFile( (File)actValue );
+    int ret = -1;
+    if ( fileChooser.getDialogType() == JFileChooser.OPEN_DIALOG )
+      ret = fileChooser.showOpenDialog(this.getInputComponent());
+    else
+      ret = fileChooser.showSaveDialog(this.getInputComponent());
+    if ( ret  == JFileChooser.APPROVE_OPTION )
+      return fileChooser.getSelectedFile();
+    return (File)actValue;
+  }
+}

Added: trunk/src/schmitzm/swing/InputCompass.java
===================================================================
--- trunk/src/schmitzm/swing/InputCompass.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/InputCompass.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,159 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Point;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseAdapter; // Java 1.6
+//import schmitzm.swing.event.MouseAdapter; // Java 1.5
+import javax.swing.JSpinner;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.BorderLayout;
+
+
+// fuer Doku
+
+/**
+ * Diese Komponente stellt eine Kompass-Nadel dar, deren Ausrichtung
+ * (Nord = 0°; West = 90°) ueber die Maus eingestellt werden kann. Daneben
+ * besitzt das Panel einen {@link JSpinner}, um den Kompass-Wert manuell
+ * einzustellen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class InputCompass extends Compass {
+  /** Spinner zum Einstellen der Kompass-Einstellung. */
+  protected JSpinner spinner = null;
+
+  /**
+   * Erzeugt einen neuen Kompass. Der Spinner wird unterhalb des Kompass
+   * angezeigt.
+   */
+  public InputCompass() {
+    this(0);
+  }
+
+  /**
+   * Erzeugt einen neuen Kompass. Der Spinner wird unterhalb des Kompass
+   * angezeigt.
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   */
+  public InputCompass(double degree) {
+    this(degree, true);
+  }
+
+  /**
+   * Erzeugt einen neuen Kompass. Wird {@code null} als Spinner-Position
+   * angegeben, wird KEIN Spinnter angezeigt!
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   * @param showSpinner wenn {@code false}, wird KEIN Spinner angezeigt
+   */
+  public InputCompass(double degree, boolean showSpinner) {
+    this(degree,showSpinner,null,null);
+  }
+
+  /**
+   * Erzeugt einen neuen Kompass. Wird {@code null} als Spinner-Position
+   * angegeben, wird KEIN Spinnter angezeigt!
+   * @param degree angezeigte Grad-Angabe (Nord = 0°, West = 90°)
+   * @param showSpinner wenn {@code false}, wird KEIN Spinner angezeigt
+   * @param compassConstr Layout-Constraints fuer den Kompass
+   * @param spinnerConstr Layout-Constraints fuer den Spinner
+   */
+  public InputCompass(double degree, boolean showSpinner, GridBagConstraints compassConstr, GridBagConstraints spinnerConstr) {
+    super(degree);
+
+    if ( showSpinner ) {
+      if ( compassConstr == null )
+        compassConstr = new GridBagConstraints(
+          0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 30, 30);
+      if ( spinnerConstr == null )
+        spinnerConstr = new GridBagConstraints(
+          0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 30, 0);
+
+      this.remove( getCompassPane() );
+      this.setLayout( new GridBagLayout() );
+      this.add( getCompassPane(), compassConstr );
+
+      this.spinner = new JSpinner(new RotationSpinnerNumberModel(degree, 0, 360, 0.1, true));
+      this.spinner.setEditor( new JSpinner.NumberEditor(this.spinner,SwingUtil.getNumberFormatPattern(3)) );
+      SwingUtil.setPreferredWidth( spinner, 100 );
+      this.add( spinner, spinnerConstr );
+
+      // ActionListener auf Spinner, um Kompass (THIS) zu aktualisieren
+      this.spinner.addChangeListener( new ChangeListener() {
+        public void stateChanged(ChangeEvent e) {
+          double newValue = (Double)spinner.getValue();
+          if ( newValue != InputCompass.this.getValue() )
+            InputCompass.this.setValue(newValue);
+        }
+      });
+      // ActionListener auf Kompass (THIS), um Spinner zu aktualisieren
+      InputCompass.this.addChangeListener( new ChangeListener() {
+        public void stateChanged(ChangeEvent e) {
+          double newValue = InputCompass.this.getValue();
+          if ( newValue != (Double)spinner.getValue() )
+            spinner.setValue(newValue);
+        }
+      });
+    }
+
+    // Mouse-Listener fuer Kompass-Einstellung
+    MouseAdapter listener = new MouseAdapter() {
+      private boolean button1pressed = false;
+      public void mouseDragged(MouseEvent e) {
+        if ( button1pressed )
+          setDegreeForMousePosition(e.getPoint());
+      }
+      public void mousePressed(MouseEvent e) {
+        if ( e.getButton() == MouseEvent.BUTTON1 ) {
+          button1pressed = true;
+          setDegreeForMousePosition(e.getPoint());
+        }
+      }
+      public void mouseReleased(MouseEvent e) {
+        button1pressed = false;
+      }
+    };
+    getCompassPane().addMouseMotionListener( listener );
+    getCompassPane().addMouseListener( listener );
+  }
+
+  /**
+   * Setzt die Grad-Einstellung der Kompass-Nadel entsprechend einer
+   * Maus-Position
+   * @param p Maus-Position relativ zur Kompass-Komponente ((0/0) ist oben-links)
+   */
+  private void setDegreeForMousePosition(Point p) {
+    if ( !isEnabled() )
+      return;
+
+    int centerX = getCompassPane().getWidth()/2;
+    int centerY = getCompassPane().getHeight()/2;
+
+    // (0/0) von Komponente ist oben-links
+    int dx = p.x - centerX;
+    int dy = centerY - p.y;
+    // Distanz zwischen Klickpunkt und Mittelpunkt
+    double radius = Math.hypot( dx,dy );
+    double degree = Math.toDegrees( Math.asin( dy/radius ) );
+    if ( dx < 0 )
+      degree = 180 - degree;
+    // 0° = Norden; 90° = West
+    setValue( degree-90 );
+  }
+}

Added: trunk/src/schmitzm/swing/InputOption.java
===================================================================
--- trunk/src/schmitzm/swing/InputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/InputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,321 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Component;
+import java.awt.Color;
+import java.awt.event.FocusListener;
+import java.awt.event.FocusEvent;
+import java.util.Vector;
+import javax.swing.JLabel;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import schmitzm.swing.SpringUtilities;
+import schmitzm.swing.event.InputOptionListener;
+
+// nur fuer Doku
+import schmitzm.swing.MultipleOptionPane;
+
+/**
+ * Diese Klasse stellt die allgemeine Oberklasse fuer eine Eingabe-Option
+ * des {@link MultipleOptionPane} dar. Jede Option besteht aus einem
+ * {@linkplain #descLabel Label} und einem {@linkplain #inpComp Eingabefeld}.
+ * Die Art des Eingabefelds wird durch die Unterklassen spezifiziert.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class InputOption<E> extends JPanel {
+  /** Speichert eine Referenz auf die Eingabe-Option (<code>this</code>), damit
+   *  in inneren Klassen darauf referenziert werden kann. */
+  protected final Component OPTION_COMPONENT = this;
+  /** Speichert die Beschreibung der Option */
+  protected JLabel    descLabel = null;
+  /** Speichert die Eingabe-Komponente der Option. */
+  protected JComponent inpComp   = null;
+  /** Kann eine Fehlermeldung enthalten, wenn die Eingabe nicht
+   *  valide ist. Kann direkt von {@link #performIsInputValid()} befuellt
+   *  werden. Wird automatisch befuellt, wenn {@link #performIsInputValid()}
+   *  eine Exception wirft. */
+  protected String invalidInputMess = "";
+
+  private   boolean   inpNeeded = false;
+  private   Vector    listeners = new Vector();
+  private   E         oldValue  = null;
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label       Beschreibung (wird im Label angezeigt)
+   * @param inputNeeded gibt an, ob eine Eingabe in der Option erforderlich ist
+   */
+  public InputOption(String label, boolean inputNeeded) {
+    this(label,inputNeeded,false);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label       Beschreibung (wird im Label angezeigt)
+   * @param inputNeeded gibt an, ob eine Eingabe in der Option erforderlich ist
+   * @param centerInputComp bestimmt, ob die Eingabe-Komponente (siehe {@link #createInputComponent()})
+   *                        ins Zentrum des {@link BorderLayout} gelegt wird)
+   */
+  public InputOption(String label, boolean inputNeeded, boolean centerInputComp) {
+    this.inpNeeded = inputNeeded;
+
+    // Darstellung des Containers erzeugen
+    setLayout(new BorderLayout());
+    descLabel = new JLabel(label);
+    inpComp   = createInputComponent();
+    inpComp.addFocusListener( new FocusListener() {
+      public void focusGained(FocusEvent e) {
+        oldValue = getValue();
+        fireFocusGained();
+      }
+      public void focusLost(FocusEvent e) {
+        fireFocusLost();
+        if ( oldValue == null && getValue() != null ||
+             oldValue != null && !oldValue.equals( getValue() ) )
+          fireOptionChanged(oldValue,getValue());
+      }
+    });
+
+    // Elemente dem Container hinzufuegen
+    if ( label != null ) {
+      add( descLabel, BorderLayout.NORTH );
+      add( inpComp, centerInputComp ? BorderLayout.CENTER : BorderLayout.SOUTH );
+    } else {
+      add( inpComp, centerInputComp ? BorderLayout.CENTER : BorderLayout.NORTH );
+    }
+    doLayout();
+  }
+
+  /**
+   * Deaktiviert die Option, in dem die Eingabe-Komponente deaktiviert wird.
+   */
+  public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    inpComp.setEnabled(enabled);
+  }
+
+  /**
+   * Setzt den Hilfetext fuer Eingabe-Komponente und Beschreibungslabel.
+   * @param text Hilfe-Text
+   */
+  public void setToolTipText(String text) {
+    super.setToolTipText(text);
+    if ( inpComp != null )
+      inpComp.setToolTipText(text);
+    if ( descLabel != null )
+      descLabel.setToolTipText(text);
+  }
+
+  /**
+   * Setzt den Fokus auf das Eingabe-Feld der {@code InputOption}.
+   */
+  public void grabFocus() {
+    inpComp.grabFocus();
+  }
+
+  /**
+   * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+   * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+   * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+   *         (siehe {@link #isInputValid()})
+   */
+  public E getValue() {
+    if ( !isInputValid() )
+      return null;
+    return performGetValue();
+  }
+
+  /**
+   * Setzt einen neuen Wert, mit der die Eingabe-Option belegt wird.
+   * Ruft {@link #performSetValue(Object)} auf und bewirkt ein
+   * {@link InputOptionListener#optionChanged(Object,Object)} fuer alle
+   * angeschlossenen {@link InputOptionListener}, falls sich der Wert der
+   * Option geaendert hat.
+   * @param newValue neuer Wert
+   * @return <code>false</code> gdw. der Objekt-Typ fuer die Option nicht
+   *         zulaessig ist
+   */
+  public boolean setValue(E newValue) {
+    // alten Wert speichern
+    oldValue = getValue();
+    if ( !performSetValue(newValue) )
+      return false;
+    newValue = getValue();
+    if ( oldValue==null && newValue!=null || oldValue!=null && newValue==null || oldValue!=null && !oldValue.equals( newValue ) )
+      fireOptionChanged(oldValue,getValue());
+    return true;
+  }
+
+  /**
+   * Liefert die Beschreibung der Eingabe-Option.
+   */
+  public String getLabel() {
+    return descLabel.getText();
+  }
+
+  /**
+   * Prueft, ob fuer das Feld zwingend eine Eingabe erforderlich ist.
+   */
+  public boolean inputNeeded() {
+    return inpNeeded;
+  }
+
+  /**
+   * Bestimmt, ob fuer das Feld zwingend eine Eingabe erforderlich ist.
+   */
+  public void setInputNeeded(boolean inpNeeded) {
+    this.inpNeeded = inpNeeded;
+  }
+
+  /**
+   * Prueft, ob die Eingabe in dem Feld zulaessig ist. Die Implementierung
+   * prueft zuerst auf Leereingabe und ruft dann {@link #performIsInputValid()}
+   * auf.
+   * @return <code>false</code> falls die Option leer ist, aber eine Eingabe
+   *         erforderlich ist
+   */
+  public boolean isInputValid() {
+    // Leereingabe
+    if (  inputEmpty() )
+      return !inputNeeded();
+    try {
+      this.invalidInputMess = "";
+      return performIsInputValid();
+    } catch (Exception err) {
+      this.invalidInputMess = err.getMessage();
+      return false;
+    }
+  }
+
+  /**
+   * Liefert die letzte von {@link #isInputValid()} erzeugte
+   * Fehlermeldung.
+   */
+  public String getInvalidInputMessage() {
+    return invalidInputMess;
+  }
+
+  /**
+   * Prueft, ob das Feld eine Leereingabe beinhaltet.
+   * @see #performIsInputEmpty()
+   */
+  public boolean inputEmpty() {
+    return performIsInputEmpty();
+  }
+
+  /**
+   * Liefert eine Referenz auf die Eingabe-Komponente.<br>
+   * <b>Bemerke:</b><br>
+   * Uber diese Referenz sollte hoechsten ihr Layout beeinflusst werden, aber
+   * nicht ihr Verhalten, da ansonsten die Funktionalitaet der <code>InputOption</code>
+   * negativ beeinflusst werden kann!!
+   */
+  public JComponent getInputComponent() {
+    return inpComp;
+  }
+
+  /**
+   * Liefert eine Referenz auf das Beschreibungs-Label.<br>
+   * <b>Bemerke:</b><br>
+   * Uber diese Referenz sollte hoechsten das Layout beeinflusst werden, aber
+   * nicht das Verhalten, da ansonsten die Funktionalitaet der <code>InputOption</code>
+   * negativ beeinflusst werden kann!!
+   */
+  public JLabel getDescriptionLabel() {
+    return descLabel;
+  }
+
+  /**
+   * Erzeugt eine neue Instanz der Eingabe-Komponente. z.B. ein Text-Eingabefeld
+   * oder eine Combo-Box.
+   */
+  protected abstract JComponent createInputComponent();
+
+  /**
+   * Liefert den aktuellen Wert der Eingabe-Option.
+   */
+  protected abstract E performGetValue();
+
+  /**
+   * Setzt den aktuellen Wert der Eingabe-Option. Ist der Objekt-Typ fuer die
+   * Option nicht zulaessig, sollte nichts gemacht werden und <code>false</code>
+   * zurueckgegeben werden.
+   * @param newValue neuer Wert
+   * @return <code>false</code> gdw. der Objekt-Typ fuer die Eingabe-Option
+   *         nicht zulaessig ist
+   */
+  protected abstract boolean performSetValue(E newValue);
+
+  /**
+   * Prueft, ob die aktuelle Eingabe leer ist.
+   */
+  protected abstract boolean performIsInputEmpty();
+
+  /**
+   * Prueft, ob die aktuelle Eingabe leer ist.
+   */
+  protected abstract boolean performIsInputValid();
+
+  /**
+   * Fuegt der Eingabeoption einen Listener hinzu.
+   * @param l neuer Listener
+   */
+  public void addInputOptionListener(InputOptionListener l) {
+    listeners.add(l);
+  }
+
+  /**
+   * Entfernt einen Listener von der Eingabeoption.
+   * @param l zu entfernender Listener
+   */
+  public void removeInputOptionListener(InputOptionListener l) {
+    listeners.remove(l);
+  }
+
+  /**
+   * Informiert alle {@link InputOptionListener}, dass die Eingabe-Option
+   * den Fokus erhalten hat.
+   */
+  protected void fireFocusGained() {
+    for (int i=0; i<listeners.size(); i++)
+      if ( listeners.elementAt(i) instanceof InputOptionListener )
+        ((InputOptionListener)listeners.elementAt(i)).optionGainedFocus(this);
+  }
+
+  /**
+   * Informiert alle {@link InputOptionListener}, dass die Eingabe-Option
+   * den Fokus verloren hat.
+   */
+  protected void fireFocusLost() {
+    for (int i=0; i<listeners.size(); i++)
+      if ( listeners.elementAt(i) instanceof InputOptionListener )
+        ((InputOptionListener)listeners.elementAt(i)).optionLostFocus(this);
+  }
+
+  /**
+   * Informiert alle {@link InputOptionListener}, dass sich der Wert der
+   * Eingabeoption geaendert hat.
+   * @param oldValue alter Optionswert
+   * @param newValue neuer Optionswert
+   */
+  protected void fireOptionChanged(Object oldValue, Object newValue) {
+    for (int i=0; i<listeners.size(); i++)
+      if ( listeners.elementAt(i) instanceof InputOptionListener )
+        ((InputOptionListener)listeners.elementAt(i)).optionChanged(this,oldValue,newValue);
+  }
+
+}

Added: trunk/src/schmitzm/swing/JPanel.java
===================================================================
--- trunk/src/schmitzm/swing/JPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/JPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,80 @@
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.LayoutManager;
+// fuer Doku
+import java.awt.FlowLayout;
+import java.awt.Color;
+
+/**
+ * Diese Klasse erweitert das {@link javax.swing.JPanel} aus Standard-Java um
+ * einige (nuetzliche) Funktionen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class JPanel extends javax.swing.JPanel {
+  /**
+   * Erzeugt ein neues Panel.
+   * @param layout {@link LayoutManager} fuer das Panel
+   * @param isDoubleBuffered Flag, ob zusaetzlicher Speicher verwendet werden soll um
+   *        flicker-freie Updates zu gewaehrleisten
+   */
+  public JPanel(LayoutManager layout, boolean isDoubleBuffered) {
+    super(layout, isDoubleBuffered);
+  }
+
+  /**
+   * Erzeugt ein neues Panel.
+   * @param layout {@link LayoutManager} fuer das Panel
+   */
+  public JPanel(LayoutManager layout) {
+    super(layout);
+  }
+
+  /**
+   * Erzeugt ein neues Panel mit einem {@link FlowLayout}.
+   * @param isDoubleBuffered Flag, ob zusaetzlicher Speicher verwendet werden soll um
+   *        flicker-freie Updates zu gewaehrleisten
+   */
+  public JPanel(boolean isDoubleBuffered) {
+    super(isDoubleBuffered);
+  }
+
+  /**
+   * Erzeugt ein neues Panel mit einem {@link FlowLayout}.
+   */
+  public JPanel() {
+    super();
+  }
+
+  /**
+   * Aktiviert und deaktiviert alle Komponenten des Panels.
+   */
+  @Override
+  public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    for (Component c : getComponents())
+      c.setEnabled(enabled);
+  }
+
+  /**
+   * Setzt die Hintergrund-Farbe der Komponente und optional die der
+   * untergeordneten Komponenten
+   * @param color neue Hintergrund-Farbe
+   * @param components bestimmt, ob auch die Hintergrundfarbe der Unterkomponenten
+   *        gesetzt werden soll
+   */
+  public void setBackground(Color color, boolean components) {
+    // Hintergrund von Panel selbst setzen
+    super.setBackground(color);
+    // wenn rekursive Verarbeitung, dann auch den Hintergrund
+    // der Componenten aendern
+    if ( components )
+      for ( Component c : getComponents() )
+        if ( c instanceof JPanel )
+          ((JPanel)c).setBackground(color,components);
+        else
+          c.setBackground(color);
+  }
+
+}

Added: trunk/src/schmitzm/swing/ManualInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/ManualInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ManualInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,302 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.JTextField;
+import schmitzm.swing.InputOption;
+
+/**
+ * Diese Klasse stellt eine manuelle Eingabe-Option fuer das {@link MultipleOptionPane}
+ * dar. Die Eingabe erfolgt ueber ein {@link JTextField}. Ausser der
+ * "Input-Needed"-Restriktion, werden keine weiteren Anforderungen an die
+ * Eingabe gestellt. Die Klasse kann nicht direkt instanziiert werden.
+ * Statt dessen sind die eingebetteten Klassen
+ * <ul>
+ * <li>{@link ManualInputOption.Text    ManualInputOption.Text}</li>
+ * <li>{@link ManualInputOption.Integer ManualInputOption.Integer}</li>
+ * <li>{@link ManualInputOption.Double  ManualInputOption.Double}</li>
+ * </ul>
+ * zu verwenden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ManualInputOption extends InputOption {
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label       Beschreibung
+   * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+   * @param defValue    Wert der im Textfeld vorgeblendet wird
+   */
+  protected ManualInputOption(String label, boolean inputNeeded, Object defValue) {
+    super(label,inputNeeded);
+    ((JTextField)inpComp).setText( defValue != null ? defValue.toString() : "");
+  }
+
+  /**
+   * Erzeugt eine neue Instanz von {@link JTextField}.
+   */
+  protected JTextField createInputComponent() {
+    return new JTextField();
+  }
+
+  /**
+   * Liefert immer <code>true</code>, da keine speziellen Anforderungen
+   * an die Text-Eingabe gestellt werden.
+   */
+  protected boolean performIsInputValid() {
+    return true;
+  }
+
+  /**
+   * Prueft, ob im Eingabefeld ein Leerstring eingegeben wurde.
+   */
+  protected boolean performIsInputEmpty() {
+    return ((JTextField)inpComp).getText().trim().equals("");
+  }
+
+  /**
+   * Liefert die aktuelle Eingabe im {@link JTextField}.
+   */
+  protected Object performGetValue() {
+    return ((JTextField)inpComp).getText();
+  }
+
+  /**
+   * Setzt die aktuelle Eingabe im {@link JTextField}.
+   * @param newValue neuer Wert (muss vom Typ {@link String} sein!)
+   * @return <code>false</code> wenn kein {@link String} uebergeben wird
+   */
+  protected boolean performSetValue(Object newValue) {
+    if ( newValue == null )
+      newValue = "";
+    if ( !(newValue instanceof String) )
+      return false;
+    ((JTextField)inpComp).setText((String)newValue);
+    return true;
+  }
+
+  /**
+   * Diese Klasse stellt eine Eingabe-Option dar, in der ein beliebigen Text
+   * eingegeben werden kann.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class Text extends ManualInputOption {
+    /**
+     * Erzeugt eine neue Eingabe-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param defValue    Wert der im Textfeld vorgeblendet wird
+     */
+    public Text(String label, boolean inputNeeded, String defValue) {
+      super(label,inputNeeded,defValue);
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     */
+    public Text(String label, boolean inputNeeded) {
+      this(label,inputNeeded,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist.
+     * @param label Beschreibung
+     */
+    public Text(String label) {
+      this(label,true,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist.
+     * @param label    Beschreibung
+     * @param defValue Wert der im Textfeld vorgeblendet wird
+     */
+    public Text(String label, String defValue) {
+      this(label,true,defValue);
+    }
+
+    /**
+     * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+     * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+     * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+     *         (siehe {@link #isInputValid()})
+     */
+    public String getValue() {
+      return (String)super.getValue();
+    }
+  }
+
+  /**
+   * Diese Klasse stellt eine Eingabe-Option dar, in der ein Integer-Zahlen
+   * eingegeben werden kann.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class Integer extends ManualInputOption {
+    /**
+     * Erzeugt eine neue Eingabe-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param defValue    Wert der im Textfeld vorgeblendet wird
+     */
+    public Integer(String label, boolean inputNeeded, int defValue) {
+      super(label,inputNeeded,String.valueOf(defValue));
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option. Auch wenn die leere Eingabe u.U.
+     * nicht zulaessig ist, wird ein Leerstring vorgeblendet.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     */
+    public Integer(String label, boolean inputNeeded) {
+      super(label,inputNeeded,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist. Auch wenn die leere Eingabe u.U. nicht zulaessig ist, wird ein
+     * Leerstring vorgeblendet.
+     * @param label Beschreibung
+     */
+    public Integer(String label) {
+      super(label,true,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist.
+     * @param label    Beschreibung
+     * @param defValue Wert der im Textfeld vorgeblendet wird
+     */
+    public Integer(String label, int defValue) {
+      this(label,true,defValue);
+    }
+
+    /**
+     * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+     * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+     * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+     *         (siehe {@link #isInputValid()})
+     */
+    public java.lang.Integer getValue() {
+      return (java.lang.Integer)super.getValue();
+    }
+
+    /**
+     * Transformiert die Text-Eingabe in einen Integer.
+     * @return <code>null</code> falls das Eingabefeld leer ist.
+     */
+    protected java.lang.Integer performGetValue() {
+      if ( inputEmpty() )
+        return null;
+      return java.lang.Integer.parseInt(((JTextField)inpComp).getText());
+    }
+
+    /**
+     * Prueft, ob ein gueltiger Integer-Wert im Feld eingegeben wurde.
+     */
+    protected boolean performIsInputValid() {
+      try {
+        java.lang.Integer.parseInt(((JTextField)inpComp).getText());
+      } catch (NumberFormatException err) {
+        return false;
+      }
+      return super.performIsInputValid();
+    }
+  }
+
+  /**
+   * Diese Klasse stellt eine Eingabe-Option dar, in der ein Double-Zahlen
+   * eingegeben werden kann.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class Double extends ManualInputOption {
+    /**
+     * Erzeugt eine neue Eingabe-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param defValue    Wert der im Textfeld vorgeblendet wird
+     */
+    public Double(String label, boolean inputNeeded, double defValue) {
+      super(label,inputNeeded,String.valueOf(defValue));
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option. Auch wenn die leere Eingabe u.U.
+     * nicht zulaessig ist, wird ein Leerstring vorgeblendet.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     */
+    public Double(String label, boolean inputNeeded) {
+      super(label,inputNeeded,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist. Auch wenn die leere Eingabe u.U. nicht zulaessig ist, wird ein
+     * Leerstring vorgeblendet.
+     * @param label Beschreibung
+     */
+    public Double(String label) {
+      super(label,true,"");
+    }
+
+    /**
+     * Erzeugt eine neue Eingabe-Option, in der eine Eingabe zwingend erforderlich
+     * ist.
+     * @param label    Beschreibung
+     * @param defValue Wert der im Textfeld vorgeblendet wird
+     */
+    public Double(String label, int defValue) {
+      this(label,true,defValue);
+    }
+
+    /**
+     * Liefert den Wert, der in der Option eingegeben wurde. Prueft zuerst
+     * auf Gueltigkeit und ruft dann {@link #performGetValue()} auf.
+     * @return <code>null</code> wenn die aktuelle Eingabe nicht zulaessig ist
+     *         (siehe {@link #isInputValid()})
+     */
+    public java.lang.Double getValue() {
+      return (java.lang.Double)super.getValue();
+    }
+
+    /**
+     * Transformiert die Text-Eingabe in einen Double.
+     * @return <code>null</code> falls das Eingabefeld leer ist.
+     */
+    protected java.lang.Double performGetValue() {
+      if ( inputEmpty() )
+        return null;
+      return java.lang.Double.parseDouble(((JTextField)inpComp).getText());
+    }
+
+    /**
+     * Prueft, ob ein gueltiger Double-Wert im Feld eingegeben wurde.
+     */
+    protected boolean performIsInputValid() {
+      try {
+        java.lang.Double.parseDouble(((JTextField)inpComp).getText());
+      } catch (NumberFormatException err) {
+        return false;
+      }
+      return super.performIsInputValid();
+    }
+  }
+}

Added: trunk/src/schmitzm/swing/MultipleOptionPane.java
===================================================================
--- trunk/src/schmitzm/swing/MultipleOptionPane.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/MultipleOptionPane.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,179 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+
+import schmitzm.swing.InputOption;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.ManualInputOption;
+
+/**
+ * Diese Klasse erweitert {@link JOptionPane} um einen Ok/Abbrechen-Dialog, der
+ * mehrfache Werte gleichzeitig abfragt. Die Art der Eingaben wird ueber
+ * Instanzen der  Klasse {@link InputOption} spezifiziert.<br>
+ * Ueber die statische Funktion
+ * {@link #showMultipleInputDialog(Component,String,InputOption[])} kann ein
+ * solcher Dialog angezeigt werden.<br>
+ * Darueber hinaus bietet die statische Funktion
+ * {@link #showClassAndDescInputDialog(Component,String,Class[],String)} einen
+ * speziellen Dialog, der eine Auswahl an Klassennamen anzeigt und zu der
+ * ausgewaehlten Klasse eine Beschreibung abfragt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MultipleOptionPane extends JOptionPane {
+  /** Speichert die Eingabe-Optionen des MultipleOptionPane. */
+  protected InputOption[] multipleOption = null;
+
+  /**
+   * Erzeugt einen neuen Ok/Abbrechen-Dialog mit mehrfachen Eingabefeldern.
+   * @param multipleOption Eingabe-Optionen
+   */
+  public MultipleOptionPane(InputOption[] multipleOption) {
+    super();
+    this.multipleOption = multipleOption.clone();
+    this.setMessageType(JOptionPane.INFORMATION_MESSAGE);
+    this.setOptionType(JOptionPane.OK_CANCEL_OPTION);
+    this.setMessage( this.multipleOption );
+  }
+
+  /**
+   * Liefert die Eingabe-Optionen des <code>MultipleOptionPane</code>.
+   */
+  public InputOption[] getMultipleOptions() {
+    return multipleOption.clone();
+  }
+
+  /**
+   * Prueft, ob die Eingaben in allen Felden zulaessig sind.
+   */
+  public boolean multipleOptionsValid() {
+    return getInvalidOption() == null;
+  }
+
+  /**
+   * Prueft, ob alle Eingaben zulaessig sind und gibt die erste fehlerhafte
+   * Option zurueck.
+   * @return <code>null</code> wenn alle Eingaben korrekt sind
+   */
+  private InputOption getInvalidOption() {
+    for (int i=0; i<multipleOption.length; i++)
+      if ( !multipleOption[i].isInputValid() )
+        return multipleOption[i];
+    return null;
+  }
+
+  /**
+   * Liefert die Eingaben, die in dem <code>MultipleOptionPane</code>
+   * gemacht wurden.
+   * @return <code>null</code> falls der Dialog noch aktiv ist oder
+   *         nicht ueber die Ok-Option verlassen wurde
+   */
+  public Object[] getMultipleOptionValues() {
+    if ( getValue() == null || (Integer)getValue() != JOptionPane.OK_OPTION)
+      return null;
+    Object[] value = new Object[ multipleOption.length ];
+    for (int i=0; i<value.length; i++)
+      value[i] = multipleOption[i].getValue();
+    return value;
+  }
+
+  /**
+   * Zeigt einen modalen Ok/Abbrechen-Dialog mit mehrfachen Eingabefeldern an.
+   * @param parent Uebergeordnetes Fenster, in dem der Dialog angezeigt wird
+   *               (kann <code>null</code> sein)
+   * @param title  Titel fuer den Dialog
+   * @param option Eingabefelder
+   * @return die Werte, die in den einzelnen Feldern gemacht wurden, oder
+   *         <code>null</code>, falls der Dialog nicht ueber die Ok-Option
+   *         verlassen wurde
+   */
+  public static Object[] showMultipleInputDialog(Component parent, String title, InputOption... option) {
+    MultipleOptionPane pane   = new MultipleOptionPane(option);
+    JDialog            dialog = pane.createDialog(parent,title);
+    InputOption        invalidOption = null;
+    do {
+      dialog.setVisible(true);
+      boolean cancel = pane.getValue() == null || ((Integer)pane.getValue() != JOptionPane.OK_OPTION);
+      invalidOption = !cancel ? pane.getInvalidOption() : null;
+
+      if ( invalidOption != null ) {
+        String invalidOptionName = invalidOption.getLabel();
+        String errorDesc         = invalidOption.getInvalidInputMessage();
+        String mess = SwingUtil.RESOURCE.getString("InvalidInputMess");
+        if ( invalidOptionName != null && !invalidOptionName.trim().equals("") )
+          mess = mess.concat(": '").concat(invalidOptionName).concat("'");
+        if ( errorDesc != null && !errorDesc.trim().equals("") )
+          mess = mess.concat("\n").concat(errorDesc);
+        showMessageDialog(pane,mess,SwingUtil.RESOURCE.getString("Error"),JOptionPane.ERROR_MESSAGE);
+      }
+
+    } while ( invalidOption != null );
+    return pane.getMultipleOptionValues();
+  }
+
+  /**
+   * Zeigt einen modalen Ok/Abbrechen-Dialog mit einem Klassen-Auswahlfeld und
+   * einem Text-Eingabefeld fuer eine Beschreibung an. Eine Eingabe in beiden
+   * Feldern ist zwingend erforderlich.
+   * @param parent      Uebergeordnetes Fenster, in dem der Dialog angezeigt wird
+   *                    (kann <code>null</code> sein)
+   * @param title       Titel fuer den Dialog
+   * @param classOption Klassen, die im Dialog zur Auswahl gestellt werden
+   * @param defaultText Default-Wert fuer das Text-Eingabefeld
+   * @return Array mit 2 Werten (0 = ausgewaehlte Klasse [<code>Class</code>];
+   *         1 = eingegebene Beschreibung [<code>String</code>]), oder
+   *         <code>null</code>, falls der Dialog nicht ueber die Ok-Option
+   *         verlassen wurde
+   */
+  public static Object[] showClassAndDescInputDialog(Component parent, String title, Class[] classOption, String defaultText) {
+    String[] classDesc = new String[classOption.length];
+    for (int i=0; i<classDesc.length; i++)
+      classDesc[i] = classOption[i].getName();
+
+    return showMultipleInputDialog(
+      parent,
+      title,
+        new InputOption[] { new SelectionInputOption.Combo(SwingUtil.RESOURCE.getString("Class")+":",true,classOption,classDesc),
+                            new ManualInputOption.Text(SwingUtil.RESOURCE.getString("Description")+":",true,defaultText) }
+    );
+  }
+
+
+  /**
+   * Zeigt einen Dialog mit mehreren Options-Buttons dar.
+   * @param parent       Parent-Komponente, ueber der der Dialog angezeigt wird
+   * @param type         Art der Meldung ({@link JOptionPane#ERROR_MESSAGE},
+   *                     {@link JOptionPane#INFORMATION_MESSAGE} oder {@link JOptionPane#QUESTION_MESSAGE})
+   * @param title        Titel des Dialog-Fensters
+   * @param message      Meldung die angezeigt wird
+   * @param options      Bezeichnungen der Button, die zur Auswahl stehen
+   * @param initialValue Bezeichnung des standardmaessig ausgewaehlten Button
+   * @return Die Bezeichnung ({@code String}) des Buttons, der ausgewaehlt wurde,
+   *         oder {@code null}, wenn der Dialog ueber das System-Menu abgebrochen
+   *         wurde
+   */
+  public static Object showMultipleOptionDialog(Component parent, int type, String title, Object message, String[] options, String initialValue) {
+    JOptionPane optionPane = new JOptionPane();
+    optionPane.setMessageType(type);
+    optionPane.setOptionType(JOptionPane.OK_CANCEL_OPTION);
+    optionPane.setMessage( message );
+    optionPane.setOptions( options );
+    optionPane.setInitialValue(initialValue);
+    JDialog dialog = optionPane.createDialog(parent,title);
+    dialog.setVisible(true);
+    return optionPane.getValue();
+  }
+}

Added: trunk/src/schmitzm/swing/ObjectDisplayContainer.java
===================================================================
--- trunk/src/schmitzm/swing/ObjectDisplayContainer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/ObjectDisplayContainer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,76 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Container;
+import java.awt.Component;
+
+/**
+ * Diese Klasse repraesentiert eine abstrakte Oberklasse fuer alle GUI-Komponenten,
+ * die ein (allgemeines) Datenobjekt darstellen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class ObjectDisplayContainer extends Container {
+  /** Speichert eine Referent auf den Container (<code>this</code>). */
+  protected final Component GUI_COMPONENT  = this;
+  /** Speichert das dargestellte Objekt. */
+  protected Object data = null;
+
+  /**
+   * Erzeugt eine neue Darstellungskomponente.
+   */
+  public ObjectDisplayContainer() {
+    this(null);
+  }
+
+  /**
+   * Erzeugt eine neue Darstellungskomponente und zeigt sofort ein
+   * Objekt an.
+   * @param data anzuzeigendes Objekt
+   */
+  public ObjectDisplayContainer(Object data) {
+    setObject(data);
+  }
+
+  /**
+   * Setzt das darzustellende Objekt. Wird <code>null</code> uebergeben,
+   * wird das bisher dargestellte Objekt entfernt.
+   * @exception UnsupportedOperationException falls das angegebene Objekt
+   *            nicht dargestellt werden kann.
+   * @see #canDisplay(Object)
+   */
+  public void setObject(Object data) {
+    if ( data!=null && !canDisplay(data) )
+      throw new UnsupportedOperationException( getClass().getSimpleName().concat(" can not display an instance of ").concat(data.getClass().getName()) );
+    this.data = data;
+    refresh();
+  }
+
+  /**
+   * Liefert das aktuell dargestellte Objekt.
+   * @return <code>null</code> falls aktuell kein Objekt dargestellt wird
+   */
+  public Object getObject() {
+    return data;
+  }
+
+  /**
+   * Prueft, ob ein Objekt darstellbar ist.
+   */
+  public abstract boolean canDisplay(Object data);
+
+  /**
+   * Aktualisiert die Darstellung des aktuell angezeigten Objekts.
+   */
+  public abstract void refresh();
+}

Added: trunk/src/schmitzm/swing/OperationTreePanel.java
===================================================================
--- trunk/src/schmitzm/swing/OperationTreePanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/OperationTreePanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,375 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Vector;
+import java.util.HashMap;
+
+import javax.swing.JButton;
+import javax.swing.JTextField;
+import javax.swing.JComboBox;
+
+import schmitzm.swing.CaptionsChangeablePanel;
+import schmitzm.swing.ManualInputOption;
+import schmitzm.swing.SelectionInputOption;
+import schmitzm.swing.SwingWorker;
+import schmitzm.lang.tree.OperationTreeParser;
+
+// nur fuer Doku
+import schmitzm.lang.tree.OperationTree;
+import java.util.Map;
+
+/**
+ * Diese Klasse stellt eine Panel zur Vefuegung, mit der eine einfache arithmetische
+ * (und boolsche) Berechnungsformel eingegeben werden kann.
+ * @see OperationTree
+ * @see OperationTreeParser
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class OperationTreePanel extends CaptionsChangeablePanel {
+  /** Parser, der aus dem Formel-String einen Operator-Baum erstellt. */
+  private static final OperationTreeParser parser = new OperationTreeParser();
+
+  /** Speichert eine Referenz auf das Fenster-Objekt, um aus verschachtelten
+   *  Klassen einfach darauf zugreifen zu koennen. */
+  protected final OperationTreePanel THIS = this;
+
+  /** Konstante fuer den Start-Button.
+  *  @see #layoutConstraints
+  *  @see #resetCaptions(Map) */
+  public static final String START_BUTTON = OperationTreePanel.class.getName()+".StartButton";
+  /** Konstante fuer das Regel-Label.
+  *  @see #resetCaptions(Map) */
+  public static final String RULE_LABEL = OperationTreePanel.class.getName()+".RuleLabel";
+  /** Konstante fuer das Formel-Eingabefeld.
+  *  @see #layoutConstraints */
+  public static final String RULE_TEXTFIELD = OperationTreePanel.class.getName()+".RuleField";
+  /** Konstante fuer den Tooltip des Regel-Felds.
+  *  @see #resetCaptions(Map) */
+  public static final String RULE_TOOLTIP = OperationTreePanel.class.getName()+".RuleTooltip";
+  /** Konstante fuer das Operatoren-Label.
+  *  @see #resetCaptions(Map) */
+  public static final String OPERATOR_LABEL = OperationTreePanel.class.getName()+".OperatorLabel";
+  /** Konstante fuer das Operatoren-Auswahlfeld.
+  *  @see #layoutConstraints */
+  public static final String OPERATOR_COMBOBOX = OperationTreePanel.class.getName()+".OperatorBox";
+  /** Werte fuer die grafische Anordnung der Layout-Komponenten.
+   *  Ueber die Konstanten {@link #START_BUTTON}, {@link #RULE_TEXTFIELD}
+   *  und {@link #OPERATOR_COMBOBOX} koennen die Constraints (am Anfang von {@link #initGUI()})
+   *  veraendert oder erweitert werden. */
+  protected HashMap<String,GridBagConstraints> layoutConstraints = new HashMap<String,GridBagConstraints>();
+
+  /** Eingabefeld fuer die Formel. */
+  protected ManualInputOption.Text rule = null;
+  /** Butten zum Starten der Berechnung. */
+  protected JButton startButton = null;
+  /** Auswahl-Liste der zur Verfuegung stehenden Operatoren und Konstanzen. */
+  protected SelectionInputOption.Combo<String> operators = null;
+  /** Enthaelt die in der Operator-Auswahl zur Verfuegung gestellten Operatoren
+   *  Konstanten */
+  protected Vector<String>         avOperators     = new Vector<String>();
+  /** Enthaelt die in der Operator-Auswahl angezeigen Operatoren Konstanten */
+  protected HashMap<String,String> avOperatorsDesc = new HashMap<String,String>();
+
+  private JTextField ruleTextField = null;
+  private JComboBox  operatorsComboBox = null;
+
+  /**
+   * Erzeugt eine neue Eingabe-GUI fuer den {@link OperationTree}.
+   */
+  public OperationTreePanel() {
+    this(true);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-GUI fuer den {@link OperationTree}.
+   * @param initGUI Flag, ob {@link #initGUI()} am Ende des Konstruktor
+   *        aufgerufen werden soll (wenn {@code false} muss die explizit
+   *        durch die Unterklasse erfolgen!)
+   */
+  protected OperationTreePanel(boolean initGUI) {
+    super();
+    this.setPreferredSize( new Dimension(500,400) );
+    this.setLayout( new GridBagLayout() );
+
+    // Standard-Werte fuer die Layout-Anordnung hinterlegen
+    layoutConstraints.put(RULE_TEXTFIELD,    new GridBagConstraints(0,0,1,1,1.0,0.0,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,10,5,10),0,0));
+    layoutConstraints.put(OPERATOR_COMBOBOX, new GridBagConstraints(1,0,1,1,0.0,0.0,GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(0,10,5,10),0,0));
+    layoutConstraints.put(START_BUTTON,      new GridBagConstraints(0,1,1,1,0.0,0.0,GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,10,5,10),0,0));
+
+    // Zur Verfuegung stehende Operatoren
+    avOperators.add("&");            avOperatorsDesc.put("&",       "AND");
+    avOperators.add("|");            avOperatorsDesc.put("|",       "OR");
+    avOperators.add("!");            avOperatorsDesc.put("!",       "NOT");
+    avOperators.add("abs");          avOperatorsDesc.put("abs",     "abs(.)");
+    avOperators.add("sqrt");         avOperatorsDesc.put("sqrt",    "sqrt(.)");
+    avOperators.add("round");        avOperatorsDesc.put("round",   "round(.)");
+    avOperators.add("trunc");        avOperatorsDesc.put("trunc",   "trunc(.)");
+    avOperators.add("isNaN");        avOperatorsDesc.put("isNaN",   "isNaN(.)");
+    avOperators.add("NaN");
+    avOperators.add("random");       avOperatorsDesc.put("random",  "random value");
+    avOperators.add("sin");          avOperatorsDesc.put("sin",     "sin(.)");
+    avOperators.add("cos");          avOperatorsDesc.put("cos",     "cos(.)");
+    avOperators.add("tan");          avOperatorsDesc.put("tan",     "tan(.)");
+    avOperators.add("asin");         avOperatorsDesc.put("asin",    "arcsin(.)");
+    avOperators.add("acos");         avOperatorsDesc.put("acos",    "arccos(.)");
+    avOperators.add("atan");         avOperatorsDesc.put("atan",    "arctan(.)");
+    avOperators.add("exp");          avOperatorsDesc.put("exp",     "exp(.)");
+    avOperators.add("ln");           avOperatorsDesc.put("ln",      "ln(.)");
+    avOperators.add("log");          avOperatorsDesc.put("log",     "log(.)");
+    avOperators.add("str");          avOperatorsDesc.put("str",     "str(.)");
+    avOperators.add("val");          avOperatorsDesc.put("val",     "val(.)");
+    avOperators.add("len");          avOperatorsDesc.put("len",     "len(.)");
+    avOperators.add("toupper");      avOperatorsDesc.put("toupper", "toupper(.)");
+    avOperators.add("tolower");      avOperatorsDesc.put("tolower", "tolower(.)");
+    avOperators.add("ITE");          avOperatorsDesc.put("ITE",     "IF .. THEN .. ELSE");
+    avOperators.add("REGEX");        avOperatorsDesc.put("REGEX",   "REGEX(.,.)");
+    avOperators.add("SUBSTR");       avOperatorsDesc.put("SUBSTR",  "SUBSTRING(.,.,.)");
+    // Die "einfachen Operatoren" and Ende setzen, da diese eigentlich
+    // menuell eingegeben werden und nicht so oft gebraucht werden
+    avOperators.add("+");
+    avOperators.add("-");
+    avOperators.add("*");
+    avOperators.add("/");
+    avOperators.add("^");
+    avOperators.add("=");
+    avOperators.add("<>");
+    avOperators.add("<");
+    avOperators.add("<=");
+    avOperators.add(">");
+    avOperators.add(">=");
+
+    if (initGUI)
+      initGUI();
+  }
+
+  /**
+   * Initalisiert die GUI des Fensters.
+   */
+  protected void initGUI() {
+    // Formel
+    rule = new ManualInputOption.Text(SwingUtil.RESOURCE.getString("Rule")+":",true,"");
+    rule.setToolTipText(SwingUtil.RESOURCE.getString("RuleToolTip"));
+    ruleTextField = (JTextField)rule.getInputComponent();
+    // neue Caret-Implementierung, damit Selektion nicht verloren geht,
+    // wenn Textfeld den Focus verliert
+    ruleTextField.setCaret( new SelectionPreservingCaret(true) );
+
+    // Button zum Starten
+    startButton = new JButton(SwingUtil.RESOURCE.getString("Calculate"));
+    startButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        startCalculation();
+      }
+    });
+
+    // Combo-Box fuer Operatoren
+    String[] opArr  = (String[])avOperators.toArray(new String[0]);
+    String[] opDesc = new String[opArr.length];
+    for (int i=0; i<opArr.length; i++) {
+      String desc = avOperatorsDesc.get(opArr[i]);
+      opDesc[i] = (desc == null) ? opArr[i] : desc;
+    }
+    operators = new SelectionInputOption.Combo<String>(
+      SwingUtil.RESOURCE.getString("Operators")+":",  // Label
+      true,  // keine Null-Auswahl
+      opArr, // Operatoren
+      null,  // Default-Wert
+      opDesc // Angezeigte Operatoren
+    );
+    operatorsComboBox = ((JComboBox)operators.getInputComponent());
+    operatorsComboBox.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        performOperatorInsert(
+            (String)operators.getValue(),
+            ruleTextField
+        );
+        // Operator-Auswahl auf "nichts" zuruecksetzen
+        operators.setValue( null );
+        // Fokus zurueck auf Formel-Feld
+        rule.grabFocus();
+      }
+    } );
+
+    // Komponenten in Fenster anordnen
+    add( rule,        layoutConstraints.get(RULE_TEXTFIELD) );
+    add( startButton, layoutConstraints.get(START_BUTTON) );
+    add( operators,   layoutConstraints.get(OPERATOR_COMBOBOX) );
+  }
+
+  /**
+   * Setzt die Labels des Panels neu. Die Beschriftungen werden in einer
+   * Map hinterlegt und ueber die Konstanten {@link #RULE_LABEL}, {@link #RULE_TOOLTIP},
+   * {@link #OPERATOR_LABEL} und {@link #START_BUTTON} angesprochen.
+   * @param captionMap Map
+   */
+  public void resetCaptions(Map<String,Object> captionMap) {
+    super.resetCaptions(captionMap);
+    SwingUtil.resetCaption( rule.getDescriptionLabel(), captionMap.get( RULE_LABEL) );
+    SwingUtil.resetCaption( operators.getDescriptionLabel(), captionMap.get( OPERATOR_LABEL ) );
+    SwingUtil.resetCaption( startButton, captionMap.get( START_BUTTON ) );
+    Object caption = captionMap.get( RULE_TOOLTIP );
+    if ( caption != null )
+      rule.setToolTipText( caption.toString() );
+  }
+
+  /**
+   * Prueft die in der Maske spezifizierten Angaben auf Korrektheit. Ist eine
+   * Angabe nicht korrekt, wird ein entsprechender Fehler-Dialog angezeigt.
+   */
+  private boolean checkInputs() {
+    try {
+      checkInputsAndError();
+    } catch (Exception err) {
+      ExceptionDialog.show(
+          SwingUtil.getParentWindowComponent(this),
+          err,
+          (err!=null) ? err.getClass().getSimpleName() : SwingUtil.RESOURCE.getString("Error"),
+          null
+      );
+      return false;
+    }
+    return true;
+  }
+
+
+  /**
+   * Prueft die eingegebene Formel auf syntaktische Korrektheit. Unterklassen
+   * koennen diese Methode ueberschreiben, wenn die Mask
+   * @exception Exception falls ein Fehler gefunden wird
+   */
+  protected void checkInputsAndError() throws Exception {
+    parser.parse(rule.getValue());
+  }
+
+  /**
+   * Erzeugt einen {@link OperationTree} aus der Formel und wertet diese
+   * aus.
+   * @return das Ergebnis der Formel, als {@code double}-Wert
+   * @see OperationTreeParser
+   */
+  protected Object performCalculation() throws Exception {
+    return parser.parse( rule.getValue() ).evaluate();
+  }
+
+  /**
+   * Startet die Berechnung, wenn der Start-Button betaetigt wurde.
+   */
+  private void startCalculation() {
+    // Eingaben pruefen
+    if ( !checkInputs() )
+      return;
+
+    // SwingWorker erzeugen, der die Generierung durchfuehrt
+    SwingWorker worker = new SwingWorker(
+      new SwingWorker.Work() {
+        public Object execute() throws Exception {
+          performCalculation();
+          return null;
+        }
+        public void performError(Exception err) {
+          // Fehler bei der Code-Generierung verarbeiten
+          ExceptionDialog.show(
+              SwingUtil.getParentWindowComponent(THIS),
+              err,
+              (err!=null) ? err.getClass().getSimpleName() : SwingUtil.RESOURCE.getString("Error"),
+              null
+          );
+        }
+      },
+      SwingUtil.getParentFrame(this),
+      SwingUtil.RESOURCE.getString("WaitMess")
+    );
+
+    // Berechnung starten
+    worker.start();
+  }
+
+  /**
+   * Fuegt einen Operator in die aktuelle Formel ein. Ueber
+   * {@link JTextField#getSelectionStart()} und {@link JTextField#getSelectionEnd()}
+   * kann ermittelt werden, an welcher Stelle der aktuelle Cursor steht.
+   * @param op   einzufuegender Operator
+   * @param textField Text-Feld in das der Operator eingefuegt werden soll
+   * @see JTextField#getSelectedText()
+   */
+  protected void performOperatorInsert(String op, JTextField textField) {
+    int selStart     = textField.getSelectionStart();
+    int selEnd       = textField.getSelectionEnd();
+    String selection = textField.getSelectedText();
+
+    // Anzahl an geklammerten Parametern fuer den Operator ermitteln
+    int parameterCount = getParameterCount(op);
+
+    // Bei mehr-stelligen Funktionen, erhaelt die Funktion den selektierten
+    // Bereich als ersten Operand
+    if ( parameterCount >= 1 ) {
+      // wenn nichts selektiert war, wird "_" fuer den Operanden
+      // vorgeblendet
+      if ( selection == null )
+        selection = "_";
+      // Operator um die Parameter erweitern
+      op = op + "( " + selection;
+      for (int i=1; i<parameterCount; i++)
+        op = op + ", _";
+      op = op + " )";
+    }
+
+    // Selektierten Bereich ersetzen (wenn nichts selektiert war, wird autom.
+    // nur eingefuegt)
+    textField.replaceSelection( op );
+
+    // Wenn vorher ein Bereich ausgewaehlt war, wird nun der
+    // neue Operator ausgewaehlt
+    if ( selStart < selEnd ) {
+      textField.setSelectionStart( selStart );
+      textField.setSelectionEnd( selStart + op.length() );
+    }
+  }
+
+  /**
+   * Liefert die Anzahl an geklammerten Parametern, die ein Operator hat.
+   * @param op Operator
+   */
+  protected int getParameterCount(String op) {
+    // Funktionen mit einem geklammerten Parameter
+    if (op.equalsIgnoreCase("abs") || op.equalsIgnoreCase("sqrt") ||
+        op.equalsIgnoreCase("round") || op.equalsIgnoreCase("trunc") ||
+        op.equalsIgnoreCase("isNaN") || op.equalsIgnoreCase("sin") ||
+        op.equalsIgnoreCase("cos") || op.equalsIgnoreCase("tan") ||
+        op.equalsIgnoreCase("asin") || op.equalsIgnoreCase("acos") ||
+        op.equalsIgnoreCase("atan") || op.equalsIgnoreCase("exp") ||
+        op.equalsIgnoreCase("ln") || op.equalsIgnoreCase("log") ||
+        op.equalsIgnoreCase("val") || op.equalsIgnoreCase("str") ||
+        op.equalsIgnoreCase("toupper") || op.equalsIgnoreCase("tolower") ||
+        op.equalsIgnoreCase("!") )
+      return 1;
+
+    // Funktionen mit zwei geklammerten Parameter
+    if (op.equalsIgnoreCase("REGEX") )
+      return 2;
+
+    // Funktionen mit drei geklammerten Parameter
+    if (op.equalsIgnoreCase("ITE") )
+      return 3;
+
+    // sonst: Konstante/Alias angenommen
+    return 0;
+  }
+}

Added: trunk/src/schmitzm/swing/RotationSpinnerNumberModel.java
===================================================================
--- trunk/src/schmitzm/swing/RotationSpinnerNumberModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/RotationSpinnerNumberModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,118 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.*;
+
+/**
+ * Diese Klasse erweitert das {@link RotationSpinnerNumberModel} um eine
+ * Rotation-Funktionalitaet. Ist diese eingeschaltet, wechselt der Spinner
+ * automatisch auf den kleinsten Wert, wenn der Maximum-Wert ueberschritten
+ * wird (und umgekehrt).
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class RotationSpinnerNumberModel extends SpinnerNumberModel {
+  /** Flag fuer die Rotation-Funktionalitaet. */
+  protected boolean rotation = false;
+
+  /**
+   * Erzeugt ein neues Model.
+   * @param value    initialer Wert
+   * @param minimum  Minimal-Wert
+   * @param maximum  Maximal-Wert
+   * @param stepSize Schrittweite
+   * @param rotation falls {@code true} wechselt der Spinner auf den kleinsten Wert
+   *                 wenn der Maximalwert ueberschritten wird (und umgekehrt)
+   */
+  public RotationSpinnerNumberModel(Number value, Comparable minimum, Comparable maximum, Number stepSize, boolean rotation) {
+    super(value, minimum, maximum, stepSize);
+    setRotation(rotation);
+  }
+
+  /**
+   * Erzeugt ein neues Model.
+   * @param value    initialer Wert
+   * @param minimum  Minimal-Wert
+   * @param maximum  Maximal-Wert
+   * @param stepSize Schrittweite
+   * @param rotation falls {@code true} wechselt der Spinner auf den kleinsten Wert
+   *                 wenn der Maximalwert ueberschritten wird (und umgekehrt)
+   */
+  public RotationSpinnerNumberModel(int value, int minimum, int maximum, int stepSize, boolean rotation) {
+    super(value, minimum, maximum, stepSize);
+    setRotation(rotation);
+  }
+
+  /**
+   * Erzeugt ein neues Model.
+   * @param value    initialer Wert
+   * @param minimum  Minimal-Wert
+   * @param maximum  Maximal-Wert
+   * @param stepSize Schrittweite
+   * @param rotation falls {@code true} wechselt der Spinner auf den kleinsten Wert
+   *                 wenn der Maximalwert ueberschritten wird (und umgekehrt)
+   */
+  public RotationSpinnerNumberModel(double value, double minimum, double maximum, double stepSize, boolean rotation) {
+    super(value, minimum, maximum, stepSize);
+    setRotation(rotation);
+  }
+
+  /**
+   * Erzeugt ein neues Model ohne Minimum- und Maximumwert.
+   */
+  public RotationSpinnerNumberModel() {
+    super();
+    setRotation( false );
+  }
+
+  /**
+   * Liefert den naechst-groesseren Wert.
+   * @return {@link #getMinimum()} wenn der Maximalwert erreicht ist und
+   *         die Rotation-Funktion eingestellt ist
+   * @see #setRotation(boolean)
+   */
+  public Object getNextValue() {
+    Object next = super.getNextValue();
+    if ( next == null && rotation )
+      next = getMinimum();
+    return next;
+  }
+
+  /**
+   * Liefert den naechst-kleineren Wert.
+   * @return {@link #getMaximum()} wenn der Minimalwert erreicht ist und
+   *         die Rotation-Funktion eingestellt ist
+   * @see #setRotation(boolean)
+   */
+  public Object getPreviousValue() {
+    Object next = super.getPreviousValue();
+    if ( next == null && rotation )
+      next = getMaximum();
+    return next;
+  }
+
+  /**
+   * (De)aktiviert die Rotation-Funktion.
+   * @param rotation wenn {@code true} wird, die Rotation-Funktion aktiviert
+   */
+  public void setRotation(boolean rotation) {
+    this.rotation = rotation;
+  }
+
+  /**
+   * Prueft, ob die Rotation-Funktion aktiviert ist.
+   */
+  public boolean getRotation() {
+    return this.rotation;
+  }
+}

Added: trunk/src/schmitzm/swing/SelectionInputOption.java
===================================================================
--- trunk/src/schmitzm/swing/SelectionInputOption.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SelectionInputOption.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,516 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JRadioButton;
+
+import schmitzm.swing.InputOption;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.event.PopupMenuEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.ItemEvent;
+import java.util.Vector;
+
+/**
+ * Diese Klasse stellt eine Auswahl-Eingabe-Option fuer das {@link MultipleOptionPane}
+ * dar. Die Klasse kann nicht direkt instanziiert werden.
+ * Statt dessen sind die eingebetteten Klassen
+ * <ul>
+ * <li>{@link SelectionInputOption.Combo SelectionInputOption.Combo}</li>
+ * </ul>
+ * zu verwenden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class SelectionInputOption<E> extends InputOption<E> {
+  /** Speichert die Objekte, die zur Auswahl stehen. */
+  protected E[] selectionObject = null;
+  /** Speichert die Objekte, die fuer die Auswahl angezeigt werden. */
+  protected Object[] displayObject   = null;
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label       Beschreibung
+   * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+   * @param value       die zur Auswahl stehenden Objekte
+   * @param defIdx      Index der vorgeblendeten Auswahl
+   * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+   *                    (kann <code>null</code> sein)
+   * @exception IllegalArgumentException falls sich die Array-Groessen von
+   *            <code>value</code> und <code>display</code> unterscheiden
+   */
+  protected SelectionInputOption(String label, boolean inputNeeded, E[] value, int defIdx, Object[] display) {
+    super(label,inputNeeded);
+    // Referenz auf Objekte merken und Komponente befuellen
+    setSelectionObjects(value,display);
+    setSelectedIndex(defIdx);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option.
+   * @param label       Beschreibung
+   * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+   * @param value       die zur Auswahl stehenden Objekte
+   * @param defValue    vorgeblendetes Auswahlobjekt
+   * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+   *                    (kann <code>null</code> sein)
+   * @exception IllegalArgumentException falls sich die Array-Groessen von
+   *            <code>value</code> und <code>display</code> unterscheiden
+   */
+  protected SelectionInputOption(String label, boolean inputNeeded, E[] value, E defValue, Object[] display) {
+    super(label,inputNeeded);
+    // Referenz auf Objekte merken und Komponente befuellen
+    setSelectionObjects(value,display);
+    setSelectedItem(defValue);
+  }
+
+  /**
+   * Erzeugt eine neue Eingabe-Option. Es wird (sofern vorhanden) das erste
+   * Auswahl-Objekt vorgeblendet.
+   * @param label       Beschreibung
+   * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+   * @param value       die zur Auswahl stehenden Objekte
+   * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+   *                    (kann <code>null</code> sein)
+   * @exception IllegalArgumentException falls sich die Array-Groessen von
+   *            <code>value</code> und <code>display</code> unterscheiden
+   */
+  protected SelectionInputOption(String label, boolean inputNeeded, E[] value, Object[] display) {
+    this(label,inputNeeded,value,value.length > 0 ? 0 : -1, display);
+  }
+
+  /**
+   * Setzt die im Auswahlfeld zur Verfuegung stehenden Eintraege
+   * @param value       die zur Auswahl stehenden Objekte
+   * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+   *                    (kann <code>null</code> sein)
+   * @exception IllegalArgumentException falls sich die Array-Groessen von
+   *            <code>value</code> und <code>display</code> unterscheiden
+   *
+   */
+  public void setSelectionObjects(E[] value, Object[] display) {
+    // werden keine Wert angegeben, wird dies wie eine leere
+    // Menge behandelt
+    if (value == null)
+      value = (E[])new Object[0];
+    // Wurden keine speziellen Anzeigewerte angegeben, werden die
+    // Auswahl-Objekte verwendet
+    if (display == null)
+      display = value;
+    // Haben der Auswahl- und der Anzeige-Array unterschiedliche
+    // Laenge, wird dies nicht akzeptiert
+    if ( ( (Object[]) display).length != ( (E[]) value).length)
+      throw new IllegalArgumentException("display-Array and value-Array must have the same size!");
+
+    Object lastSelection = getValue();
+    this.selectionObject = value;
+    this.displayObject   = display;
+    performSelectionUpdate();
+    setSelectedItem(lastSelection);
+  }
+
+  /**
+   * Aktualisiert die Objekte in der der Auswahlkomponente. Wird aufgerufen,
+   * wenn dich die zur Auswahl stehenden Objekte aendern. Die Variablen
+   * {@link #selectionObject} und {@link #displayObject} sind dann bereits
+   * aktualisiert,
+   */
+  protected abstract void performSelectionUpdate();
+
+  /**
+   * Liefert immer <code>true</code>, da keine speziellen Anforderungen
+   * an die Auswahl-Eingabe gestellt werden.
+   */
+  protected boolean performIsInputValid() {
+    return true;
+  }
+
+  /**
+   * Prueft, ob im Auswahlfeld ein Eintrag ausgewaehlt wurde.
+   */
+  protected boolean performIsInputEmpty() {
+    return getSelectedIndex() < 0;
+  }
+
+  /**
+   * Liefert das Objekt, das zum aktuell ausgewaehlten Eintrag der
+   * Auswahl-Liste gehoert.
+   * @return <code>null</code> falls kein Objekt ausgewaehlt ist
+   */
+  protected E performGetValue() {
+    int idx = getSelectedIndex();
+    return idx < 0 ? null : selectionObject[getSelectedIndex()];
+  }
+
+  /**
+   * Setzt das Objekt, das in der Auswahlliste angewaehlt wird. Ruft
+   * {@link #setSelectedItem(Object)} auf.
+   * @return immer <code>true</code>
+   */
+  protected boolean performSetValue(E newValue) {
+    setSelectedItem(newValue);
+    return true;
+  }
+
+  /**
+   * Setzt einen neuen Wert, mit der die Eingabe-Option belegt wird.
+   * Ruft lediglich {@link #performSetValue(Object)} auf. Ueberschreibt die
+   * Methode von {@link InputOption}, da die Ueberpruefung auf Aenderung
+   * (und Event-Ausloesung) in {@link #setSelectedIndex(int)} erfolgt.
+   * @param newValue neuer Wert
+   * @return <code>false</code> gdw. der Objekt-Typ fuer die Option nicht
+   *         zulaessig ist
+   */
+  public boolean setValue(E newValue) {
+    if ( !performSetValue(newValue) )
+      return false;
+    return true;
+  }
+
+  /**
+   * Liefert den Index, der in der Objekt-Liste ausgewaehlt wurde.
+   * @return <code>-1</code> falls kein Objekt ausgewaehlt wurde
+   */
+  public abstract int getSelectedIndex();
+
+  /**
+   * Setzt den Index, der in der Objekt-Liste ausgewaehlt wurde. <b>Da dies die
+   * Basis-Methode fuer alle Aenderungsoperationen darstellt, muss sie
+   * die Ueberpruefung auf Aenderung implementieren und ggf.
+   * {@link #fireOptionChanged(Object,Object)} ausloesen.</b>
+   * @param idx Listen-Index (-1 um eine Leer-Auswahl zu erzeugen)
+   */
+  public abstract void setSelectedIndex(int idx);
+
+  /**
+   * Setzt die Auswahl auf ein bestimmtes Objekt. Ist dieses nicht vorhanden,
+   * wird die Auswahlliste auf eine leere Auswahl eingestellt.
+   */
+  public void setSelectedItem(Object object) {
+    // altes Object in der neuen Objektliste suchen
+    int newIdx = -1;
+    for (int i=0; i<selectionObject.length && newIdx < 0; i++)
+//      if ( selectionObject[i] == object )
+      if ( selectionObject[i]==null && object==null ||
+           selectionObject[i]!=null && selectionObject[i].equals(object) )
+        newIdx = i;
+    setSelectedIndex(newIdx);
+  }
+
+  /**
+   * Liefert das Anzeige-Objekt der akuellen Auswahl.
+   */
+  public Object getSelectedDisplayItem() {
+    // altes Object in der neuen Objektliste suchen
+    if ( getSelectedIndex() >= 0 )
+      return displayObject[ getSelectedIndex() ];
+   return null;
+  }
+
+  /**
+   * Setzt die Auswahl auf ein bestimmtes Anzeige-Objekt. Ist dieses nicht vorhanden,
+   * wird die Auswahlliste auf eine leere Auswahl eingestellt.
+   */
+  public void setSelectedDisplayItem(Object object) {
+    // altes Object in der neuen Objektliste suchen
+    int newIdx = -1;
+    for (int i=0; i<displayObject.length && newIdx < 0; i++)
+//      if ( selectionObject[i] == object )
+      if ( displayObject[i]==null && object==null ||
+           displayObject[i]!=null && displayObject[i].equals(object) )
+        newIdx = i;
+    setSelectedIndex(newIdx);
+  }
+
+  /**
+   * Liefert die Anzahl der zur Auswahl stehenden Eintraege.
+   */
+  public int getSelectedItemCount() {
+    return selectionObject != null ? selectionObject.length : 0;
+  }
+
+  /**
+   * Diese Klasse stellt eine Auswahl-Option dar, die durch eine
+   * {@link JComboBox} dargestellt wird.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class Combo<E> extends SelectionInputOption {
+    /**
+     * Erzeugt eine neue Auswahl-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param defIdx      Index der vorgeblendeten Auswahl
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Combo(String label, boolean inputNeeded, E[] value, int defIdx, Object[] display) {
+      super(label,inputNeeded,value,defIdx, display);
+    }
+
+    /**
+     * Erzeugt eine neue Auswahl-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param defValue    vorgeblendetes Auswahlobjekt
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Combo(String label, boolean inputNeeded, E[] value, E defValue, Object[] display) {
+      super(label,inputNeeded,value,defValue,display);
+    }
+
+    /**
+     * Erzeugt eine neue Auswahl-Option. Es wird das erste Auswahl-Objekt
+     * vorgeblendet.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Combo(String label, boolean inputNeeded, E[] value, Object[] display) {
+      super(label,inputNeeded,value,display);
+    }
+
+    /**
+     * Erzeugt eine leere Auswahl-Option. Diese muss nachtraeglich ueber
+     * {@link #setSelectionObjects(Object[],Object[])} befuellt werden.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     */
+    public Combo(String label, boolean inputNeeded) {
+      super(label,inputNeeded,new Object[0],new Object[0]);
+    }
+
+    /**
+     * Erzeugt eine neue Instanz von {@link JComboBox}.
+     */
+    protected JComboBox createInputComponent() {
+      JComboBox comboBox = new JComboBox();
+      comboBox.addItemListener( new ItemListener() {
+        Object oldValue = null;
+        public void itemStateChanged(ItemEvent e) {
+          if ( e.getStateChange() == e.SELECTED ) {
+            Object newValue = getValue();
+            if ( oldValue != newValue )
+              fireOptionChanged(oldValue,newValue);
+            oldValue = newValue;
+          }
+        }
+      });
+      return comboBox;
+    }
+
+    /**
+     * Befuellt die ComboBox-Liste neu.
+     */
+    protected void performSelectionUpdate() {
+      // wenn eine Leereingabe erlaubt ist, wird am Anfang ein
+      // null-Eintrag eingefuegt
+      if ( !inputNeeded() ) {
+        Object[] newSelObj = new Object[this.selectionObject.length+1];
+        Object[] newDisObj = new Object[this.displayObject.length+1];
+
+        newSelObj[0] = null;
+        newDisObj[0] = null;
+        for (int i=0; i<this.selectionObject.length; i++) {
+          newSelObj[i+1] = this.selectionObject[i];
+          newDisObj[i+1] = this.displayObject[i];
+        }
+        this.selectionObject = newSelObj;
+        this.displayObject   = newDisObj;
+      }
+      // Combo-Box aktualisieren
+      ((JComboBox)inpComp).removeAllItems();
+      for (int i=0; i<displayObject.length; i++)
+        ((JComboBox)inpComp).addItem( displayObject[i] );
+    }
+
+    /**
+     * Liefert den Index, der in der ComboBox-Liste ausgewaehlt wurde.
+     * @return <code>-1</code> falls kein Objekt ausgewaehlt wurde
+     */
+    public int getSelectedIndex() {
+      return ((JComboBox)inpComp).getSelectedIndex();
+    }
+
+    /**
+     * Setzt den Index, der in der ComboBox-Liste ausgewaehlt wurde.
+     * @param idx Listen-Index (-1 um eine Leer-Auswahl zu erzeugen)
+     */
+    public void setSelectedIndex(int idx) {
+      Object oldValue = getValue();
+      ((JComboBox)inpComp).setSelectedIndex(idx);
+      Object newValue = getValue();
+      if ( oldValue != newValue || oldValue!=null && !oldValue.equals( getValue() ) )
+        fireOptionChanged(oldValue,newValue);
+    }
+  }
+
+  /**
+   * Diese Klasse stellt eine Auswahl-Option dar, die durch ein {@link JPanel}
+   * mit vertikal angeordneten {@link JRadioButton JRadioButtons} dargestellt wird.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class Radio<E> extends SelectionInputOption {
+    /** Gruppe, in der die RadioButton agieren. */
+    protected ButtonGroup buttonGroup;
+    /** Speichert die letzte Auswahl. */
+    protected Object lastSelection = null;
+    /** Liste der Buttons. */
+    protected Vector<JRadioButton> buttonList;
+
+    /** ActionListener, der auf die Button-Klicks reagiert und ggf. Events
+     *  feuert. */
+    private ActionListener actionListener = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        Object newValue = getValue();
+        if ( lastSelection != newValue )
+          fireOptionChanged(lastSelection,newValue);
+        lastSelection = newValue;
+      }
+    };
+
+    /**
+     * Erzeugt eine neue Auswahl-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param defIdx      Index der vorgeblendeten Auswahl
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Radio(String label, boolean inputNeeded, E[] value, int defIdx, Object[] display) {
+      super(label,inputNeeded,value,defIdx, display);
+    }
+
+    /**
+     * Erzeugt eine neue Auswahl-Option.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param defValue    vorgeblendetes Auswahlobjekt
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Radio(String label, boolean inputNeeded, E[] value, E defValue, Object[] display) {
+      super(label,inputNeeded,value,defValue,display);
+    }
+
+    /**
+     * Erzeugt eine neue Auswahl-Option. Es wird das erste Auswahl-Objekt
+     * vorgeblendet.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     * @param value       die zur Auswahl stehenden Objekte
+     * @param display     die anstelle der Auswahl-Objekte angezeigten Objekte
+     *                    (kann <code>null</code> sein)
+     * @exception IllegalArgumentException falls sich die Array-Groessen von
+     *            <code>value</code> und <code>display</code> unterscheiden
+     */
+    public Radio(String label, boolean inputNeeded, E[] value, Object[] display) {
+      super(label,inputNeeded,value,display);
+    }
+
+    /**
+     * Erzeugt eine leere Auswahl-Option. Diese muss nachtraeglich ueber
+     * {@link #setSelectionObjects(Object[],Object[])} befuellt werden.
+     * @param label       Beschreibung
+     * @param inputNeeded gibt an, ob eine Eingabe erforderlich ist
+     */
+    public Radio(String label, boolean inputNeeded) {
+      super(label,inputNeeded,new Object[0],new Object[0]);
+    }
+
+    /**
+     * Erzeugt eine neues {@link JPanel}, in dem untereinander
+     * {@link JRadioButton JRadioButtons} fuer jede Auswahl-Option
+     * angeordnet werden.
+     */
+    protected JPanel createInputComponent() {
+      this.buttonGroup = new ButtonGroup();
+      this.buttonList  = new Vector<JRadioButton>();
+      JPanel panel = new JPanel();
+      panel.setLayout( new BoxLayout(panel,BoxLayout.Y_AXIS) );
+      return panel;
+    }
+
+    /**
+     * Befuellt das Panel neu mit RadioButtons.
+     */
+    protected void performSelectionUpdate() {
+      JPanel buttonPanel = ((JPanel)inpComp);
+      // Panel aktualisieren
+      buttonGroup = new ButtonGroup();
+      buttonPanel.removeAll();
+      if ( buttonList != null )
+        buttonList.clear();
+      for (Object disObj : displayObject) {
+        JRadioButton button = null;
+        if ( disObj instanceof Icon )
+          button = new JRadioButton((Icon)disObj);
+        else
+          button = new JRadioButton(disObj.toString());
+        button.addActionListener(actionListener);
+        buttonPanel.add(button);
+        buttonGroup.add(button);
+        buttonList.add(button);
+      }
+    }
+
+    /**
+     * Liefert den Index, der in der Button-Liste ausgewaehlt wurde.
+     * @return <code>-1</code> falls kein Objekt ausgewaehlt wurde
+     */
+    public int getSelectedIndex() {
+      if ( buttonGroup.getSelection() == null )
+        return -1;
+      return buttonList.indexOf( buttonGroup.getSelectedButton() );
+    }
+
+    /**
+     * Setzt den Index, der in der Button-Liste ausgewaehlt wurde.
+     * @param idx Listen-Index (-1 um eine Leer-Auswahl zu erzeugen)
+     */
+    public void setSelectedIndex(int idx) {
+// Event wird - glaube ich - bereits durch den ActionListener
+// des RadioButtons realisiert!
+//      Object oldValue = getValue();
+      if ( idx == -1 || idx >= buttonList.size() )
+        buttonGroup.setUnselected();
+      else
+        buttonList.elementAt(idx).setSelected(true);
+//      Object newValue = getValue();
+//      if ( oldValue != newValue || oldValue!=null && !oldValue.equals( getValue() ) )
+//        fireOptionChanged(oldValue,newValue);
+    }
+  }
+}

Added: trunk/src/schmitzm/swing/SelectionPreservingCaret.java
===================================================================
--- trunk/src/schmitzm/swing/SelectionPreservingCaret.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SelectionPreservingCaret.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,108 @@
+package schmitzm.swing;
+
+import javax.swing.UIManager;
+import javax.swing.text.DefaultCaret;
+import java.awt.event.FocusEvent;
+
+/**
+ * Caret implementation that doesn't blow away the selection when
+ * we lose focus.
+ * <br><br>
+ * <b>Code taken from <a href="http://javatechniques.com/blog/fixing-disappearing-text-selections-when-a-menu-is-opened/">
+ * javatechniques.com</a> and extended with the preserve-caret functionality.</b>
+ */
+public class SelectionPreservingCaret extends DefaultCaret {
+    /**
+     * Flag, whether the caret is preserved, too.
+     */
+    private boolean preserveCaret = false;
+
+    /**
+     * The last SelectionPreservingCaret that lost focus
+     */
+    private static SelectionPreservingCaret last = null;
+
+    /**
+     * The last event that indicated loss of focus
+     */
+    private static FocusEvent lastFocusEvent = null;
+
+    /**
+     * Constructs a new caret which does preserve the selection.
+     * @param preserveCaret indicates whether the caret is also preserved
+     */
+    public SelectionPreservingCaret(boolean preserveCaret) {
+        this.preserveCaret = preserveCaret;
+
+        // The blink rate is set by BasicTextUI when the text component
+        // is created, and is not (re-) set when a new Caret is installed.
+        // This implementation attempts to pull a value from the UIManager,
+        // and defaults to a 500ms blink rate. This assumes that the
+        // look and feel uses the same blink rate for all text components
+        // (and hence we just pull the value for TextArea). If you are
+        // using a look and feel for which this is not the case, you may
+        // need to set the blink rate after creating the Caret.
+        int blinkRate = 500;
+        Object o = UIManager.get("TextArea.caretBlinkRate");
+        if ((o != null) && (o instanceof Integer)) {
+            Integer rate = (Integer) o;
+            blinkRate = rate.intValue();
+        }
+        setBlinkRate(blinkRate);
+    }
+
+    /**
+     * Constructs a new caret which does preserve the selection, but not the
+     * caret.
+     */
+    public SelectionPreservingCaret() {
+        this(false);
+    }
+
+    /**
+     * Called when the component containing the caret gains focus.
+     * DefaultCaret does most of the work, while the subclass checks
+     * to see if another instance of SelectionPreservingCaret previously
+     * had focus.
+     *
+     * @param evt the focus event
+     * @see java.awt.event.FocusListener#focusGained
+     */
+    public void focusGained(FocusEvent evt) {
+        super.focusGained(evt);
+
+        // If another instance of SelectionPreservingCaret had focus and
+        // we defered a focusLost event, deliver that event now.
+        if ((last != null) && (last != this)) {
+            last.hide();
+        }
+    }
+
+    /**
+     * Called when the component containing the caret loses focus. Instead
+     * of hiding both the caret and the selection, the subclass only
+     * hides the caret and saves a (static) reference to the event and this
+     * specific caret instance so that the event can be delivered later
+     * if appropriate.
+     *
+     * @param evt the focus event
+     * @see java.awt.event.FocusListener#focusLost
+     */
+    public void focusLost(FocusEvent evt) {
+        if ( !preserveCaret )
+          setVisible(false);
+        last = this;
+        lastFocusEvent = evt;
+    }
+
+    /**
+     * Delivers a defered focusLost event to this caret.
+     */
+    protected void hide() {
+        if (last == this) {
+            super.focusLost(lastFocusEvent);
+            last = null;
+            lastFocusEvent = null;
+        }
+    }
+}

Added: trunk/src/schmitzm/swing/SliderSpinnerPanel.java
===================================================================
--- trunk/src/schmitzm/swing/SliderSpinnerPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SliderSpinnerPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,227 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Font;
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import javax.swing.JSlider;
+import javax.swing.JSpinner;
+import javax.swing.JLabel;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.SpinnerNumberModel;
+
+import schmitzm.swing.JPanel;
+
+/**
+ * Dieses Panel enthaelt einen Slider, der mit einem Spinner verknuepft ist.
+ * Zusaetzlich wird eine Ueberschrift ausgewiesen.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SliderSpinnerPanel extends JPanel {
+  /** Konstante fuer die <b>horizontale</b> Orientierung des Sliders.
+   *  @see JSlider#HORIZONTAL */
+  public static final int HORIZONTAL = JSlider.HORIZONTAL;
+  /** Konstante fuer die <b>vertikale</b> Orientierung des Sliders.
+   *  @see JSlider#VERTICAL */
+  public static final int VERTICAL = JSlider.VERTICAL;
+
+  /** Slider des Panels. */
+  protected JSlider slider = null;
+  /** Ueberschrift-Label des Panels. */
+  protected JLabel headerLabel = null;
+  /** Spinner des Panels. */
+  protected JSpinner spinner = null;
+
+  /**
+   * Erzeugt ein neues Panel.
+   * @param orientation Orientierung des Sliders
+   * @param min Minimal einstellbarer Wert
+   * @param max Maximal einstellbarer Wert
+   * @param step Schrittweite fuer Spinner
+   * @param value Initialer Wert
+   * @param headerText Text fuer das Ueberschrift-Label (wenn {@code null} wird keine Ueberschrift ausgegeben!)
+   */
+  public SliderSpinnerPanel(int orientation, double min, double max, double step, double value, String headerText) {
+    super();
+    this.setLayout( new GridBagLayout() );
+    // Ueberschrift
+    if ( headerText != null ) {
+      this.headerLabel = new JLabel( headerText );
+      this.headerLabel.setFont( headerLabel.getFont().deriveFont(Font.BOLD) );
+      this.add( headerLabel, new GridBagConstraints(
+        0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0) );
+    }
+    // Slider
+    this.slider = new JSlider(orientation);
+    this.add( slider, new GridBagConstraints(
+      0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0) );
+    // Spinner
+    this.spinner = new JSpinner();
+    SwingUtil.setPreferredWidth(this.spinner, 60);
+    this.add( spinner, new GridBagConstraints(
+      1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0) );
+
+    // ActionListener auf Spinner, um Slider zu aktualisieren
+    this.spinner.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+        double newValue = ((Number)spinner.getValue()).doubleValue();
+        if ( Math.round(newValue) != slider.getValue())
+          slider.setValue( (int)Math.round(newValue) );
+      }
+    });
+
+    // ActionListener auf Slider, um Spinner zu aktualisieren
+    this.slider.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+        int newValue = slider.getValue();
+        if (newValue != Math.round( ((Number)spinner.getValue()).doubleValue() ))
+          spinner.setValue(newValue);
+      }
+    });
+
+    // Minimum, Maximum, StepSize und initalalier Wert einstellen
+    setRestrictions(value,min,max,step);
+  }
+
+  /**
+   * Erzeugt ein neues Panel. Minimum und Maximum werden automatisch auf 0 und
+   * 100 eingestellt.
+   * @param orientation Orientierung des Sliders
+   * @param headerText Text fuer das Ueberschrift-Label (wenn {@code null} wird keine Ueberschrift ausgegeben!)
+   */
+  public SliderSpinnerPanel(int orientation, String headerText) {
+    this(orientation, 0, 100, 1, 0, headerText);
+  }
+
+  /**
+   * Liefert den eingestellten Spinner-Wert.
+   */
+  public double getValue() {
+    return ((Number)spinner.getValue()).doubleValue();
+  }
+
+  /**
+   * Setzt den eingestellten Spinner-Wert.
+   * @param value Spinner-Wert
+   */
+  public void setValue(double value) {
+    spinner.setValue( Math.max( Math.min(value,getMaxValue()), getMinValue() ) );
+  }
+
+  /**
+   * Liefert den kleinsten einstellbaren Spinner-Wert.
+   */
+  public double getMinValue() {
+    return ((Number)((SpinnerNumberModel)spinner.getModel()).getMinimum()).doubleValue();
+  }
+
+  /**
+   * Setzt den kleinsten einstellbaren Spinner-Wert.
+   * @param min kleinster einstellbarer Spinner-Wert
+   */
+  public void setMinValue(double min) {
+    setRestrictions(
+      getValue(),
+      min,
+      getMaxValue(),
+      getStepSize()
+    );
+  }
+
+  /**
+   * Liefert den groessten einstellbaren Spinner-Wert.
+   */
+  public double getMaxValue() {
+    return ((Number)((SpinnerNumberModel)spinner.getModel()).getMaximum()).doubleValue();
+  }
+
+  /**
+   * Setzt den groessten einstellbaren Spinner-Wert.
+   * @param max groesster einstellbarer Spinner-Wert
+   */
+  public void setMaxValue(double max) {
+    setRestrictions(
+      getValue(),
+      getMinValue(),
+      max,
+      getStepSize()
+    );
+  }
+
+  /**
+   * Liefert die Schrittgroesse des Spinners.
+   */
+  public double getStepSize() {
+    return ((SpinnerNumberModel)spinner.getModel()).getStepSize().doubleValue();
+  }
+
+  /**
+   * Setzt die Schrittweiter des Spinners.
+   * @param stepSize groesster einstellbarer Spinner-Wert
+   */
+  public void setStepSize(double stepSize) {
+    setRestrictions(
+      getValue(),
+      getMinValue(),
+      getMaxValue(),
+      stepSize
+    );
+  }
+
+  /**
+   * Setzt alle Restriktionen des Sliders und Spinners neu.
+   * @param value    angezeigter Wert
+   * @param min      minimaler einstellbarer Wert
+   * @param max      maximaler einstellbarer Wert
+   * @param stepSize Schrittweite des Spinners
+   */
+  public void setRestrictions(double value, double min, double max, double stepSize) {
+    // Spinner einstellen
+    spinner.setModel( new SpinnerNumberModel(
+      value,
+      min,
+      max,
+      stepSize
+    ) );
+    spinner.setEditor( new JSpinner.NumberEditor(this.spinner,SwingUtil.getNumberFormatPattern(stepSize)) );
+    // Slider einstellen
+    slider.setMinimum( (int)Math.floor(min) );
+    slider.setMaximum( (int)Math.ceil(max) );
+    setValue( value );
+  }
+
+  /**
+   * Liefert den Slider des Panels.
+   */
+  public JSlider getSlider() {
+    return this.slider;
+  }
+
+  /**
+   * Liefert das Ueberschrift-Label des Panels.
+   */
+  public JLabel getHeaderLabel() {
+    return this.headerLabel;
+  }
+
+  /**
+   * Liefert den Spinner des Panels.
+   */
+  public JSpinner getSpinner() {
+    return this.spinner;
+  }
+}

Added: trunk/src/schmitzm/swing/SpringUtilities.java
===================================================================
--- trunk/src/schmitzm/swing/SpringUtilities.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SpringUtilities.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,227 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.*;
+import javax.swing.SpringLayout;
+import java.awt.*;
+
+/**
+ * Diese Klasse stellt Methoden zur Verfuegung, um {@linkplain SpringLayout SpringLayouts}
+ * in Rasterform anzuordnen.<br>
+ * Sie wurde aus den Beispielen
+ * <a href="http://java.sun.com/docs/books/tutorial/uiswing/layout/spring.html"
+ *  target="_blank">"How to Use SpringLayout"</a> der Sun-JavaDoc 1.5
+ *  uebernommen (der Source-Code ist
+ * <a href="http://java.sun.com/docs/books/tutorial/uiswing/layout/example-1dot4/SpringUtilities.java"
+ *  target="_blank">hier</a> zu finden).<br><br>
+ * <b>Nachtraegliche Anpassungen:</b>
+ * <ul>
+ * <li><u>Code SCHMITZ-01</u><br>
+ *     Der Container muss nicht mehr so viele Komponenten besitzen, dass das
+ *     Raster komplett gefuellt werden kann. Dies fuehrte urspruenglich zu
+ *     einer <code>ArrayIndexOutOfBoundsException</code>.
+ * </ul>
+ */
+public class SpringUtilities {
+    /**
+     * A debugging utility that prints to stdout the component's
+     * minimum, preferred, and maximum sizes.
+     */
+    public static void printSizes(Component c) {
+        System.out.println("minimumSize = " + c.getMinimumSize());
+        System.out.println("preferredSize = " + c.getPreferredSize());
+        System.out.println("maximumSize = " + c.getMaximumSize());
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component is as big as the maximum
+     * preferred width and height of the components.
+     * The parent is made just big enough to fit them all.
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeGrid(Container parent,
+                                int rows, int cols,
+                                int initialX, int initialY,
+                                int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeGrid must use SpringLayout.");
+            return;
+        }
+
+        Spring xPadSpring = Spring.constant(xPad);
+        Spring yPadSpring = Spring.constant(yPad);
+        Spring initialXSpring = Spring.constant(initialX);
+        Spring initialYSpring = Spring.constant(initialY);
+//### SCHMITZM-01.sc
+//      int max = rows * cols;
+        int max = Math.min(rows*cols,parent.getComponentCount());
+//### SCHMITZM-01.ec
+        //Calculate Springs that are the max of the width/height so that all
+        //cells have the same size.
+        Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getWidth();
+        Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getWidth();
+        for (int i = 1; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth());
+            maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight());
+        }
+
+        //Apply the new width/height Spring. This forces all the
+        //components to have the same size.
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            cons.setWidth(maxWidthSpring);
+            cons.setHeight(maxHeightSpring);
+        }
+
+        //Then adjust the x/y constraints of all the cells so that they
+        //are aligned in a grid.
+        SpringLayout.Constraints lastCons = null;
+        SpringLayout.Constraints lastRowCons = null;
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                                 parent.getComponent(i));
+            if (i % cols == 0) { //start of new row
+                lastRowCons = lastCons;
+                cons.setX(initialXSpring);
+            } else { //x position depends on previous component
+                cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST),
+                                     xPadSpring));
+            }
+
+            if (i / cols == 0) { //first row
+                cons.setY(initialYSpring);
+            } else { //y position depends on previous row
+                cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH),
+                                     yPadSpring));
+            }
+            lastCons = cons;
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH,
+                            Spring.sum(
+                                Spring.constant(yPad),
+                                lastCons.getConstraint(SpringLayout.SOUTH)));
+        pCons.setConstraint(SpringLayout.EAST,
+                            Spring.sum(
+                                Spring.constant(xPad),
+                                lastCons.getConstraint(SpringLayout.EAST)));
+    }
+
+    /* Used by makeCompactGrid. */
+    private static SpringLayout.Constraints getConstraintsForCell(
+                                                int row, int col,
+                                                Container parent,
+                                                int cols) {
+        SpringLayout layout = (SpringLayout) parent.getLayout();
+        Component c = parent.getComponent(row * cols + col);
+        return layout.getConstraints(c);
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component in a column is as wide as the maximum
+     * preferred width of the components in that column;
+     * height is similarly determined for each row.
+     * The parent is made just big enough to fit them all.
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeCompactGrid(Container parent,
+                                       int rows, int cols,
+                                       int initialX, int initialY,
+                                       int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeCompactGrid must use SpringLayout.");
+            return;
+        }
+        //Align all cells in each column and make them the same width.
+        Spring x = Spring.constant(initialX);
+        for (int c = 0; c < cols; c++) {
+            Spring width = Spring.constant(0);
+//### SCHMITZM-01.sc
+//          for (int r = 0; r < rows; r++) {
+            for (int r = 0; r < rows && r*cols+c < parent.getComponentCount(); r++) {
+//### SCHMITZM-01.ec
+                width = Spring.max(width,
+                                   getConstraintsForCell(r, c, parent, cols).
+                                       getWidth());
+            }
+//### SCHMITZM-01.sc
+//          for (int r = 0; r < rows; r++) {
+            for (int r = 0; r < rows && r*cols+c < parent.getComponentCount(); r++) {
+//### SCHMITZM-01.ec
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setX(x);
+                constraints.setWidth(width);
+            }
+            x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
+        }
+
+        //Align all cells in each row and make them the same height.
+        Spring y = Spring.constant(initialY);
+        for (int r = 0; r < rows; r++) {
+            Spring height = Spring.constant(0);
+//### SCHMITZM-01.sc
+//          for (int c = 0; c < cols; r++) {
+            for (int c = 0; c < cols && r*cols+c < parent.getComponentCount(); c++) {
+//### SCHMITZM-01.ec
+                height = Spring.max(height,
+                                    getConstraintsForCell(r, c, parent, cols).
+                                        getHeight());
+            }
+//### SCHMITZM-01.sc
+//          for (int c = 0; c < cols; r++) {
+            for (int c = 0; c < cols && r*cols+c < parent.getComponentCount(); c++) {
+//### SCHMITZM-01.ec
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setY(y);
+                constraints.setHeight(height);
+            }
+            y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH, y);
+        pCons.setConstraint(SpringLayout.EAST, x);
+    }
+}

Added: trunk/src/schmitzm/swing/StatusDialog.java
===================================================================
--- trunk/src/schmitzm/swing/StatusDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/StatusDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,197 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Frame;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Container;
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JProgressBar;
+import javax.swing.JLabel;
+import javax.swing.JButton;
+import javax.swing.Icon;
+import javax.swing.BoxLayout;
+import java.util.ResourceBundle;
+import java.util.Locale;
+import java.util.Vector;
+
+
+import schmitzm.swing.SwingUtil;
+
+/**
+ * Diese Klasse stellt einen modalen Status-Dialog dar. Diese besteht neben
+ * einer Meldung aus einem {@linkplain JProgressBar Status-Balken} der
+ * standardmaessig auf 'indeterminate' eingestellt ist
+ * (siehe {@link JProgressBar#setIndeterminate(boolean)}). Darueberhinaus
+ * kann der Dialog ueber einen Abbrechen-Button beendet werden. Wie auf den
+ * Abbruch reagiert wird, ist durch die aufrufende Klasse zu behandeln.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StatusDialog extends JDialog {
+  /** Dialog-Option "Abbrechen". */
+  public static final int CANCEL_OPTION = JOptionPane.CANCEL_OPTION;
+  /** Dialog-Option "Ok". */
+  public static final int OK_OPTION = JOptionPane.OK_OPTION;
+
+  /** Label in dem die Meldung angezeigt wird. */
+  protected JLabel messageLabel = null;
+  /** Status-Balken, der den Fortschritt anzeigt. */
+  protected JProgressBar progressBar  = null;
+  /** Button um den Dialog zu beenden. */
+  protected JButton button = null;
+  /** Flag signalisiert, ob der Dialog ueber den Button abgebrochen wurde. */
+  protected boolean canceled = false;
+  /**
+   * Typ des Dialogs (Stanard: {@link #CANCEL_OPTION}).
+   * @see #CANCEL_OPTION
+   * @see #OK_OPTION
+   * @see #setDialogOption(int)
+   */
+  protected int dialogOption = CANCEL_OPTION;
+
+  /**
+   * Erzeugt einen neuen Status-Dialog. Der Dialog wird relativ zum Parent-Fenster
+   * zentriert.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param title    Titel fuer das Fenster
+   * @param message  Meldung, die zu dem Status-Balken angezeigt wird
+   */
+  public StatusDialog(Component parent, String title, String message) {
+    this(parent,title,message,0.5,0.5);
+  }
+
+  /**
+   * Erzeugt einen neuen Status-Dialog.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param title    Titel fuer das Fenster
+   * @param message  Meldung, die zu dem Status-Balken angezeigt wird
+   * @param relX     relative horizontale Position zum Parent-Fenster
+   * @param relY     relative vertikale Position zum Parent-Fenster
+   */
+  public StatusDialog(Component parent, String title, String message, double relX, double relY) {
+    this(parent,title,message,null,relX,relY);
+  }
+  /**
+   * Erzeugt einen neuen Status-Dialog.
+   * @param parent   uebergeordnetes Fenster (kann <code>null</code> sein!)
+   * @param title    Titel fuer das Fenster
+   * @param message  Meldung, die zu dem Status-Balken angezeigt wird
+   * @param icon     Icon fuer das Status-Fenster
+   * @param relX     relative horizontale Position zum Parent-Fenster
+   * @param relY     relative vertikale Position zum Parent-Fenster
+   */
+  public StatusDialog(Component parent, String title, String message, Icon icon, double relX, double relY) {
+    super((Frame)parent,true);
+    // wenn kein uebergeordnetes Fenster angegeben ist, wird es immer
+    // im Vordergrund angezeigt
+    if ( parent==null )
+      this.setAlwaysOnTop(true);
+
+    // Vorlagen-Dialog erzeugen
+    this.messageLabel = new JLabel(message);
+    this.progressBar  = new JProgressBar(JProgressBar.HORIZONTAL,0,100);
+    this.progressBar.setIndeterminate(true);
+    this.progressBar.getPreferredSize().height = 50;
+    this.button       = new JButton();
+    setDialogOption( CANCEL_OPTION );
+
+    Vector infoComponents = new Vector<Component>();
+    insertInformationComponents(infoComponents);
+    JOptionPane  pane = new JOptionPane(
+      infoComponents.toArray(),
+      JOptionPane.INFORMATION_MESSAGE,
+      JOptionPane.DEFAULT_OPTION,
+      icon,
+      new Object[] {button}
+    );
+    JDialog dialog = pane.createDialog(parent,title);
+
+    // Dialog nach Vorlage initialisieren
+    this.setTitle( dialog.getTitle() );
+    this.getContentPane().setLayout(new BorderLayout());
+    this.getContentPane().add(dialog.getContentPane());
+    this.setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
+    this.button.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // Wenn die Option des Dialogs auf "OK" gesetzt wurde, handelt es
+        // sich beim Button-Klick nicht um einen Abbruch!
+        canceled = (dialogOption == CANCEL_OPTION);
+        setVisible(false);
+      }
+    } );
+    pack();
+    SwingUtil.setRelativeFramePosition(this,SwingUtil.getParentWindow(parent),relX,relY);
+  }
+
+  /**
+   * Fuegt die Informations-Komponenten des Dialogs (Label und Statusbalken)
+   * in eine Liste ein. Diese werden dann (untereinander) im Dialog angezeigt.
+   * Sub-Klassen koennen diese Methode ueberschreiben und weitere Komponenten
+   * in die Liste einfuegen.<br>
+   * <b>Bemerke:</b> Der Abbruch-Button ist eine Steuerungskomponente und
+   * gehoert <b>nicht</b> zu den Elementen der Liste.
+   * @param list Liste von Komponenten, die im Dialog angezeigt werden
+   */
+  protected void insertInformationComponents(Vector<Component> list) {
+    list.add( messageLabel );
+    list.add( progressBar );
+  }
+
+  /**
+   * Liefert eine Referenz auf den Status-Balken, der im Dialog angezeigt wird.
+   * Hierueber kann dieser formatiert werden.
+   */
+  public JProgressBar getProgressBar() {
+    return progressBar;
+  }
+
+  /**
+   * Setzt die Eigenschaft des Dialog-Buttons.
+   * @param option {@link #CANCEL_OPTION} oder {@link #OK_OPTION}
+   * @exception IllegalArgumentException falls keine der Optionen
+   *            {@link #CANCEL_OPTION} oder {@link #OK_OPTION} angegeben wurde
+   */
+  public void setDialogOption(int option) {
+    switch( option ) {
+      case CANCEL_OPTION: this.button.setText(SwingUtil.RESOURCE.getString("Cancel"));
+                          break;
+      case OK_OPTION: this.button.setText(SwingUtil.RESOURCE.getString("Ok"));
+                      break;
+      default: throw new IllegalArgumentException("Unknown dialog option! Only CANCEL_OPTION or OK_OPTION allowed!");
+    }
+    this.dialogOption = option;
+  }
+
+  /**
+   * Zeigt oder verbirgt den Dialog. Beim Angezeigen wird das {@link #canceled}-Flag
+   * mit <code>false</code> initialisiert.
+   */
+  public void setVisible(boolean visible) {
+    if ( !isVisible() && visible )
+      canceled = false;
+    super.setVisible(visible);
+  }
+
+  /**
+   * Prueft, ob das Fenster durch den Button abgebrochen wurde.
+   */
+  public boolean isCanceled() {
+    return canceled;
+  }
+
+}

Added: trunk/src/schmitzm/swing/StoplightContainer.java
===================================================================
--- trunk/src/schmitzm/swing/StoplightContainer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/StoplightContainer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,129 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+
+import schmitzm.swing.CircleIcon;
+
+/**
+ * Stellt eine horizontale (links rot/rechts gruen) oder vertikale
+ * (oben rot/unten gruen) Rot/Gruen-Ampel dar.
+ * Die Ampel wird aus zwei nebeneinander oder untereinander angeordneten
+ * {@link JButton} erstellt, die deaktiviert sind, also nicht auf
+ * Clicks reagieren.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StoplightContainer extends JPanel {
+  /** beide Ampel-Lichter sind aus */
+  public static final int EMPTY = 0;
+  /** das untere (rechte) Ampel-Licht ist Gruen */
+  public static final int GREEN = 1;
+  /** das obere (linke) Ampel-Licht ist Rot */
+  public static final int RED = 2;
+
+  private CircleIcon emptyIcon;
+  private CircleIcon greenIcon;
+  private CircleIcon redIcon;
+
+  private JButton upperLight;
+  private JButton lowerLight;
+  private int mode;
+
+  /**
+   * Erzeugt eine horizontale oder vertikale Ampel.
+   * @param mode initialer Modus (EMPTY/GREEN/RED)
+   * @param width Breite der Ampel in Pixeln
+   * @param height Hoehe der Ampel in Pixeln
+   * @param vertical <code>true</code> = vertikale Anordnung; <code>false<code> = horizontale Anordnung
+   */
+  public StoplightContainer(int mode, int width, int height, boolean vertical) {
+    super();
+    setSize( new Dimension(width, height) );
+    setLayout(null);
+
+    // Durchmesser des Kreises ist 80% der Ampelbreite
+    int cirWidth = (int) (vertical ? (width * 0.8) : (height * 0.8));
+    emptyIcon = new CircleIcon(cirWidth, cirWidth, Color.lightGray);
+    greenIcon = new CircleIcon(cirWidth, cirWidth, Color.green);
+    redIcon = new CircleIcon(cirWidth, cirWidth, Color.red);
+    if (vertical) {
+      upperLight = createButton(width, height / 2, 0, 0);
+      lowerLight = createButton(width, height / 2, 0, height / 2);
+    }
+    else {
+      upperLight = createButton(width / 2, height, 0, 0);
+      lowerLight = createButton(width / 2, height, width / 2, 0);
+    }
+
+    upperLight.setEnabled(false);
+    lowerLight.setEnabled(false);
+    add(upperLight);
+    add(lowerLight);
+
+    setMode(mode);
+  }
+
+  /**
+   * Erzeugt eine vertikale Ampel.
+   * @param mode initialer Modus (EMPTY/GREEN/RED)
+   * @param width Breite der Ampel in Pixeln
+   * @param height Hoehe der Ampel in Pixeln
+   */
+  public StoplightContainer(int mode, int width, int height) {
+    this(mode, width, height, true);
+  }
+
+  private JButton createButton(int width, int height, int posX, int posY) {
+    JButton b = new JButton();
+    b.setSize(width, height);
+    b.setLocation(posX, posY);
+    return b;
+  }
+
+  /**
+   * Setzt den Modus der Ampel
+   * @param mode EMPTY, GREEN oder RED (andere Werte werden ignoriert)
+   */
+  public void setMode(int mode) {
+    this.mode = mode;
+    switch (mode) {
+      case EMPTY: {
+        upperLight.setIcon(emptyIcon);
+        lowerLight.setIcon(emptyIcon);
+        break;
+      }
+      case GREEN: {
+        upperLight.setIcon(emptyIcon);
+        lowerLight.setIcon(greenIcon);
+        break;
+      }
+      case RED: {
+        upperLight.setIcon(redIcon);
+        lowerLight.setIcon(emptyIcon);
+        break;
+      }
+    }
+  }
+
+  /**
+   * Liefert den aktuellen Modus der Ampel.
+   * @return EMPTY, GREEN oder RED.
+   */
+  public int getMode() {
+    return mode;
+  }
+}

Added: trunk/src/schmitzm/swing/SwingResource.20080424
===================================================================
--- trunk/src/schmitzm/swing/SwingResource.20080424	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SwingResource.20080424	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,104 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import schmitzm.lang.HashtableResourceBundle;
+import java.util.ResourceBundle;
+
+// fuer Doku
+import java.util.Locale;
+
+/**
+ * Dieses Standard-ResourceBundle stellt folgende Objekte fuer Swing-Komponenten
+ * zur Verfuegung.
+ * <table align=center border=2 cellpadding=5><code>
+ * <tr><th>Key</th><th>Ressource</th></tr>
+ * <tr><td><code>Ok</code></td><td>"Ok"</td></tr>
+ * <tr><td><code>Cancel</code></td><td>"Cancel"</td></tr>
+ * <tr><td><code>Apply</code></td><td>"Apply"</td></tr>
+ * <tr><td><code>Ready</code></td><td>"Ready"</td></tr>
+ * <tr><td><code>Open</code></td><td>"Open"</td></tr>
+ * <tr><td><code>Close</code></td><td>"Close"</td></tr>
+ * <tr><td><code>Save</code></td><td>"Save"</td></tr>
+ * <tr><td><code>WaitMess</code></td><td>"Please wait..."</td></tr>
+ * <tr><td><code>FileExistsMess</code></td><td>"File already exists"</td></tr>
+ * <tr><td><code>Warning</code></td><td>"Warning"</td></tr>
+ * <tr><td><code>Error</code></td><td>"Error"</td></tr>
+ * <tr><td><code>Details</code></td><td>"Details..."</td></tr>
+ * <tr><td><code>Information</code></td><td>"Information"</td></tr>
+ * <tr><td><code>Class</code></td><td>"Class"</td></tr>
+ * <tr><td><code>Description</code></td><td>"Description"</td></tr>
+ * <tr><td><code>InvalidInputMess</code></td><td>"Invalid input"</td></tr>
+ * <tr><td><code>Refresh</code></td><td>"Refresh"</td></tr>
+ * <tr><td><code>Reload</code></td><td>"Reload"</td></tr>
+ * <tr><td><code>Clear</code></td><td>"Clear"</td></tr>
+ * <tr><td><code>Skip</code></td><td>"Skip"</td></tr>
+ * <tr><td><code>Overwrite</code></td><td>"Overwrite"</td></tr>
+ * <tr><td><code>OverwriteAll</code></td><td>"Overwrite all"</td></tr>
+ * <tr><td><code>Replace</code></td><td>"Replace"</td></tr>
+ * <tr><td><code>CreateDuplicate</code></td><td>"Create duplicate"</td></tr>
+ * <tr><td><code>RememberChoice</code></td><td>"Remember this choice"</td></tr>
+ * <tr><td><code>Rule</code></td><td>"Rule"</td></tr>
+ * <tr><td><code>RuleToolTip</code></td><td>"Insert your arithmetical rule here..."</td></tr>
+ * <tr><td><code>Operators</code></td><td>"Operators"</td></tr>
+ * <tr><td><code>Start</code></td><td>"Start"</td></tr>
+ * <tr><td><code>Calculate</code></td><td>"Calculate"</td></tr>
+ * </table>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SwingResource extends HashtableResourceBundle {
+  /**
+   * Enthaelt eine Referenz auf die Ressourcen in den Default-Locale
+   * @see Locale#setDefault(Locale)
+   */
+  public static final ResourceBundle DEFAULT = ResourceBundle.getBundle(SwingResource.class.getName());
+
+  /**
+   * Liefert die (Key/Wert)-Paerchen. Die Keys (erste Dimension) muessen aus Strings
+   * bestehen.
+   */
+  public Object[][] getContents() {
+    return new Object[][] {
+        {"Ok","Ok"},
+        {"Cancel","Cancel"},
+        {"Apply","Apply"},
+        {"Ready","Ready"},
+        {"Open","Open"},
+        {"Close","Close"},
+        {"Save","Save"},
+        {"WaitMess","Please wait..."},
+        {"FileExists","File already exists"},
+        {"Details","Details..."},
+        {"Warning","Warning"},
+        {"Error","Error"},
+        {"Information","Information"},
+        {"Class","Class"},
+        {"Description","Description"},
+        {"InvalidInputMess","Invalid input"},
+        {"Refresh","Refresh"},
+        {"Reload","Reload"},
+        {"Clear","Clear"},
+        {"Skip","Skip"},
+        {"Overwrite","Overwrite"},
+        {"OverwriteAll","Overwrite all"},
+        {"Replace","Replace"},
+        {"CreateDuplicate","Create duplicate"},
+        {"RememberChoice","Remember this choice"},
+        {"Rule","Rule"},
+        {"RuleToolTip","Insert your arithmetical rule here..."},
+        {"Operators","Operators"},
+        {"Start","Start"},
+        {"Calculate","Calculate"}
+    };
+  }
+}

Added: trunk/src/schmitzm/swing/SwingResource_de.20080424
===================================================================
--- trunk/src/schmitzm/swing/SwingResource_de.20080424	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SwingResource_de.20080424	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,89 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+// nur fuer Doku
+import java.util.Locale;
+
+/**
+ * Diese Klasse stellt deutsche Ressourcen fuer Swing-Komponenten zur Verfuegung.
+ * <table align=center border=2 cellpadding=5><code>
+ * <tr><th>Key</th><th>Ressource</th></tr>
+ * <tr><td><code>Cancel</code></td><td>"Abbrechen"</td></tr>
+ * <tr><td><code>Apply</code></td><td>"Ãœbernehmen"</td></tr>
+ * <tr><td><code>Ready</code></td><td>"Fertig"</td></tr>
+ * <tr><td><code>Open</code></td><td>"Öffnen"</td></tr>
+ * <tr><td><code>Close</code></td><td>"Schliessen"</td></tr>
+ * <tr><td><code>Save</code></td><td>"Speichern"</td></tr>
+ * <tr><td><code>WaitMess</code></td><td>"Bitte warten..."</td></tr>
+ * <tr><td><code>FileExistsMess</code></td><td>"Datei existiert bereits"</td></tr>
+ * <tr><td><code>Warning</code></td><td>"Warnung"</td></tr>
+ * <tr><td><code>Error</code></td><td>"Fehler"</td></tr>
+ * <tr><td><code>Class</code></td><td>"Klasse"</td></tr>
+ * <tr><td><code>Description</code></td><td>"Beschreibung"</td></tr>
+ * <tr><td><code>InvalidInputMess</code></td><td>"Unzulässige Eingabe"</td></tr>
+ * <tr><td><code>Refresh</code></td><td>"Aktualisieren"</td></tr>
+ * <tr><td><code>Reload</code></td><td>"Neu laden"</td></tr>
+ * <tr><td><code>Clear</code></td><td>"Löschen"</td></tr>
+ * <tr><td><code>Skip</code></td><td>"Ãœbergehen"</td></tr>
+ * <tr><td><code>Overwrite</code></td><td>"Ãœberschreiben"</td></tr>
+ * <tr><td><code>OverwriteAll</code></td><td>"Alle überschreiben"</td></tr>
+ * <tr><td><code>Replace</code></td><td>"Ersetzen"</td></tr>
+ * <tr><td><code>CreateDuplicate</code></td><td>"Duplikat erzeugen"</td></tr>
+ * <tr><td><code>RememberChoice</code></td><td>"Immer diese Auswahl treffen"</td></tr>
+ * <tr><td><code>Rule</code></td><td>"Formel"</td></tr>
+ * <tr><td><code>RuleToolTip</code></td><td>"Hier eine arithmetische Formel eingeben..."</td></tr>
+ * <tr><td><code>Operators</code></td><td>"Operatoren"</td></tr>
+ * <tr><td><code>Start</code></td><td>"Start"</td></tr>
+ * <tr><td><code>Calculate</code></td><td>"Rechnen"</td></tr>
+ * </table>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SwingResource_de extends SwingResource {
+  /**
+   * Liefert die (Key/Wert)-Paerchen. Die Keys (erste Dimension) muessen aus Strings
+   * bestehen.
+   */
+  public Object[][] getContents() {
+    return new Object[][] {
+        {"Cancel","Abbrechen"},
+        {"Apply","Ãœbernehmen"},
+        {"Ready","Fertig"},
+        {"Open","Öffnen"},
+        {"Close","Schliessen"},
+        {"Save","Speichern"},
+        {"WaitMess","Bitte warten..."},
+        {"FileExists","Datei existiert bereits"},
+        {"Warning","Warnung"},
+        {"Error","Fehler"},
+        {"Class","Klasse"},
+        {"Description","Beschreibung"},
+        {"InvalidInputMess","Unzulässige Eingabe"},
+        {"Refresh","Aktualisieren"},
+        {"Reload","Neu laden"},
+        {"Clear","Löschen"},
+        {"Skip","Ãœbergehen"},
+        {"Overwrite","Ãœberschreiben"},
+        {"OverwriteAll","Alle überschreiben"},
+        {"Replace","Ersetzen"},
+        {"CreateDuplicate","Duplikat erzeugen"},
+        {"RememberChoice","Immer diese Auswahl treffen"},
+        {"Rule","Formel"},
+        {"RuleToolTip","Hier eine arithmetische Formel eingeben..."},
+        {"Operators","Operatoren"},
+        {"Start","Start"},
+        {"Calculate","Rechnen"}
+    };
+  }
+
+}

Added: trunk/src/schmitzm/swing/SwingResource_en.20080424
===================================================================
--- trunk/src/schmitzm/swing/SwingResource_en.20080424	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SwingResource_en.20080424	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,22 @@
+/** XULU - This file is part of the eXtendable Unified Land Use Modelling Platform (XULU)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+/**
+ * Diese Klasse stellt englische Ressourcen fuer Swing-Komponenten zur Verfuegung.
+ * Sie ist direkt vom Standard {@link SwingResource} abgeleitet und erweitert
+ * diesen z.Zt. noch nicht!
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SwingResource_en extends SwingResource {
+}

Added: trunk/src/schmitzm/swing/SwingUtil.java
===================================================================
--- trunk/src/schmitzm/swing/SwingUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SwingUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,713 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.Container;
+import java.awt.Toolkit;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Cursor;
+import java.awt.Image;
+import java.awt.Window;
+import java.awt.Color;
+import java.awt.GridBagConstraints;
+import java.awt.LayoutManager;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Locale;
+import javax.swing.JScrollPane;
+import javax.swing.JLabel;
+import javax.swing.JFrame;
+import javax.swing.JDialog;
+import javax.swing.AbstractButton;
+import javax.swing.ImageIcon;
+import javax.swing.JTree;
+import javax.swing.JInternalFrame;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.imageio.ImageIO;
+
+import schmitzm.lang.ResourceProvider;
+import schmitzm.lang.LangUtil;
+
+import org.apache.log4j.Logger;
+
+// fuer Doku
+import java.text.NumberFormat;
+
+/**
+ * Diese Klasse beinhaltet statische Hilfsfunktionen fuer das Arbeiten
+ * mit Swing-GUIs.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ * @version 1.1
+ */
+public class SwingUtil {
+  private static final Logger LOGGER = Logger.getLogger( SwingUtil.class.getClass().getName() );
+
+  /** {@link ResourceProvider}, der die Lokalisation fuer GUI-Komponenten
+   *  des Package {@code schmitzm.swing} zur Verfuegung stellt. Diese sind
+   *  in properties-Datein unter {@code schmitzm.swing.resource.locales}
+   *  hinterlegt. */
+  public static ResourceProvider RESOURCE = new ResourceProvider( LangUtil.extendPackagePath(SwingUtil.class,"resource.locales.SwingResourceBundle"), Locale.ENGLISH );
+
+  //****************************************************************************
+  // Diese Icons sind auf Basis der Icons von Gimp erstellt
+  // Eine Sammlung aller Gimp-Icons liegt im svn: gimp-tool-cursors.xcf
+  //****************************************************************************
+  /** Cursor in Form einer Lupe mit Plus Symbol */
+  public static final Cursor ZOOMIN_CURSOR = 	createCursorFromResourcePath("resource/cursor/zoom_in.gif",8,8,null);
+  /** Cursor in Form einer Lupe mit Minus Symbol */
+  public static final Cursor ZOOMOUT_CURSOR = 	createCursorFromResourcePath("resource/cursor/zoom_out.gif",8,8,null);
+  /** Cursor in Form einer Lupe ohne Symbol*/
+  public static final Cursor ZOOM_CURSOR = 		createCursorFromResourcePath("resource/cursor/zoom.gif",8,8,null);
+  /** Cursor in Form einer offenen Hand **/
+  public static final Cursor PAN_CURSOR = 		createCursorFromResourcePath("resource/cursor/hand_pan.png",9,11,null);
+  /** Cursor in Form einer geschlossenen Hand **/
+  public static final Cursor PANNING_CURSOR = 	createCursorFromResourcePath("resource/cursor/hand_closed.png",9,11,null);
+  /** Cursor in Form einer geschlossenen Hand **/
+  public static final Cursor CROSSHAIR_CURSOR = createCursorFromResourcePath("resource/cursor/crosshair.gif",10,10,null);
+
+//  public static final ImageIcon ICON_RASTER = createImageIconFromResourcePath("resource/icon/small/raster.png","");
+//  public static final ImageIcon ICON_VECTOR = createImageIconFromResourcePath("resource/icon/small/vector.png","");
+
+
+  /** Modus "Innen".
+   *  @see #setRelativeFramePosition(Window,Window,int,int) */
+  public static final int BOUNDS_INNER = 10;
+  /** Modus "Aussen".
+   *  @see #setRelativeFramePosition(Window,Window,int,int) */
+  public static final int BOUNDS_OUTER = 20;
+  /** Ausrichtung oben-mitte. */
+  public static final int NORTH = GridBagConstraints.NORTH;
+  /** Ausrichtung oben-links. */
+  public static final int NORTHWEST = GridBagConstraints.NORTHWEST;
+  /** Ausrichtung oben-rechts. */
+  public static final int NORTHEAST = GridBagConstraints.NORTHEAST;
+  /** Ausrichtung unten-mitte. */
+  public static final int SOUTH = GridBagConstraints.SOUTH;
+  /** Ausrichtung unten-links. */
+  public static final int SOUTHWEST = GridBagConstraints.SOUTHWEST;
+  /** Ausrichtung unten-rechts. */
+  public static final int SOUTHEAST = GridBagConstraints.SOUTHEAST;
+  /** Ausrichtung zentriert. */
+  public static final int CENTER = GridBagConstraints.CENTER;
+  /** Ausrichtung mitte-links. */
+  public static final int WEST = GridBagConstraints.WEST;
+  /** Ausrichtung mitte-rechts. */
+  public static final int EAST = GridBagConstraints.EAST;
+
+  /**
+   * Erzeugt ein Icon auf Basis einer relativen Pfad-Angabe. Als Basis-Verzeichnis
+   * wird der Classpath von {@code SwingUtil} verwendet.
+   * @param imgPath relativer Pfad des Icons
+   * @param imgDesc Beschreibung fuer Icon
+   * @return {@code null}, wenn das Icon nicht gefunden wird
+   */
+  public static ImageIcon createImageIconFromResourcePath(String imgPath, String imgDesc) {
+    return createImageIconFromResourcePath(null,imgPath,imgDesc);
+  }
+
+  /**
+   * Erzeugt ein Icon  auf Basis einer relativen Pfad-Angabe.
+   * @param resourceBase Klasse, deren Classpath als Basis-Verzeichnis verwendet wird
+   * @param imgPath relativer Pfad des Icons
+   * @param imgDesc Beschreibung fuer Icon
+   * @return {@code null}, wenn das Icon nicht gefunden wird
+   */
+  public static ImageIcon createImageIconFromResourcePath(Class resourceBase, String imgPath, String imgDesc) {
+    if ( resourceBase == null )
+      resourceBase = SwingUtil.class;
+    java.net.URL imgURL = resourceBase.getResource(imgPath);
+    if (imgURL == null) {
+      LOGGER.error("Couldn't find image file: " + imgPath);
+      return null;
+    }
+    return new ImageIcon(imgURL, imgDesc);
+  }
+
+  /**
+   * Erzeugt einen {@link Cursor} auf Basis einer relativen Pfad-Angabe.
+   * Als Basis-Verzeichnis wird der Classpath von {@code SwingUtil} verwendet.
+   * @param imgPath relativer Pfad des Icons
+   * @param x       X-Position im Image, die den Hotspot des Cursors darstellen soll
+   * @param y       Y-Position im Image, die den Hotspot des Cursors darstellen soll
+   * @param name    Bezeichnung fuer den Cursor
+   * @return {@code null}, wenn das Icon nicht gefunden wird
+   */
+  public static Cursor createCursorFromResourcePath(String imgPath, int x, int y, String name) {
+    java.net.URL imgURL = SwingUtil.class.getResource(imgPath);
+    try {
+      return Toolkit.getDefaultToolkit().createCustomCursor(
+          ImageIO.read(imgURL),
+          new Point(x, y),
+          name
+      );
+    } catch( Exception err ) {
+      return null;
+    }
+  }
+
+  /**
+   * Erzeugt ein neues Fenster mit {@link BorderLayout} und zeigt darin
+   * eine {@link Component} an.
+   * @param comp anzuzeigende Komponente (kann {@code null} sein)
+   * @param title Titel des Fensters (kann {@code null} sein)
+   * @param icon Icon-Image (kann {@code null} sein)
+   */
+  public static JFrame createFrame(Component comp, String title, Image icon) {
+    JFrame frame = new JFrame( title );
+    frame.getContentPane().setLayout( new BorderLayout() );
+    if ( comp != null )
+      frame.getContentPane().add( comp, BorderLayout.CENTER );
+    if ( title != null )
+      frame.setTitle( title );
+    if ( icon != null )
+      frame.setIconImage( icon );
+    frame.pack();
+    return frame;
+  }
+
+  /**
+   * Zeigt ein neues Fenster einer {@link Component} an.
+   * @param comp anzuzeigende Komponente (kann {@code null} sein)
+   * @param title Titel des Fensters (kann {@code null} sein)
+   * @param icon Icon-Image (kann {@code null} sein)
+   * @see #createFrame(Component, String, Image)
+   */
+  public static JFrame showFrame(Component comp, String title, Image icon) {
+    JFrame frame = createFrame(comp,title,icon);
+    frame.setVisible( true );
+    return frame;
+  }
+
+  /**
+   * Liefert das Fenster, das eine Kompoenente beinhaltet.
+   * @param comp eine GUI-Komponente
+   * @return <code>null</code> falls die Komponente in keinem Fenster
+   *         enthalten ist
+   */
+  public static Window getParentWindow(Component comp) {
+    // durch die Parents laufen, bis Window gefunden
+    while ( comp!=null && !(comp instanceof Window) )
+      comp = comp.getParent();
+    // wenn kein Window gefunden -> null zurueckgeben
+    if ( comp== null )
+      return null;
+    return (Window)comp;
+  }
+
+  /**
+   * Liefert den {@link Frame}, das eine Kompoenente beinhaltet.
+   * @param comp eine GUI-Komponente
+   * @return <code>null</code> falls die Komponente in keinem {@link Frame}
+   *         enthalten ist
+   */
+  public static Frame getParentFrame(Component comp) {
+    // durch die Parents laufen, bis Frame gefunden
+    while ( comp!=null && !(comp instanceof Frame) )
+      comp = comp.getParent();
+    // wenn kein Frame gefunden -> null zurueckgeben
+    if ( comp== null )
+      return null;
+    return (Frame)comp;
+  }
+
+  /**
+   * Liefert das Fenster, das eine Kompoenente beinhaltet. Dabei kann es sich
+   * auch um einen {@link JInternalFrame} handelt.
+   * @param comp eine GUI-Komponente
+   * @return <code>null</code> falls die Komponente in keinem Fenster
+   *         enthalten ist
+   */
+  public static Component getParentWindowComponent(Component comp) {
+    // durch die Parents laufen, bis Window gefunden
+    while ( comp!=null && !(comp instanceof Window) && !(comp instanceof JInternalFrame))
+      comp = comp.getParent();
+    // wenn kein Window gefunden -> null zurueckgeben
+    if ( comp== null )
+      return null;
+    return comp;
+  }
+
+  /**
+   * Packt das Fenster, in dem eine Kompoenente plaziert ist.
+   * @param comp eine GUI-Komponente
+   * @return <code>false</code> falls die Komponente in keinem Fenster
+   *         enthalten ist
+   * @see Window#pack()
+   */
+  public static boolean packParentWindow(Component comp) {
+    Window w = getParentWindow(comp);
+    if ( w == null )
+      return false;
+    w.pack();
+    return true;
+  }
+
+  /**
+   * Prueft, ob eine Komponente eine Kind-Komponente einer anderen Komponente ist.
+   * @param child Component
+   * @param parent Component
+   * @return {@code false} wenn {@code child == null}, {@code true} wenn {@code child == parent},
+   *         {@code isChildComponent(child.getParent(),parent)} sonst
+   */
+  public static boolean isChildComponent(Component child, Component parent) {
+    if ( child == null )
+      return false;
+    if ( child == parent )
+      return true;
+    return isChildComponent(child.getParent(),parent);
+  }
+
+  /**
+   * Zentriert ein Fenster auf dem Monitor.
+   * @param window das zu zentrierende Fenster
+   */
+  public static void centerFrameOnScreen(Window window) {
+      setRelativeFramePosition(window,0.5,0.5);
+  }
+
+  /**
+   * Zentriert ein Fenster auf dem Monitor, aber verrückt das Window per Zufall um 10 Prozenz
+   *
+   * @param window das zu zentrierende Fenster
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public static void centerFrameOnScreenRandom(Window window) {
+	  Random r = new Random( (new Date()).getTime() );
+      setRelativeFramePosition(window,0.5 + (r.nextDouble()*0.2-0.1) , 0.5 + (r.nextDouble()*0.2-0.1));
+  }
+
+
+  /**
+   * Positioniert ein Fenster auf dem Monitor, relativ zu dessen Groesse. (0.5/0.5)
+   * positioniert das Fenster z.B. genau in der Mitte des Monitors; (0.25/0.5)
+   * setzt das Fenster vertikal in die Mitte und horizontal auf ein Viertel der
+   * Monitorbreite.
+   * @param window das zu positionierende Fenster
+   * @param relX  Relationsfaktor fuer die horizontale Position (0 < <code>relX</code> < 1)
+   * @param relY  Relationsfaktor fuer die vertikale Position (0 < <code>relX</code> < 1)
+   */
+  public static void setRelativeFramePosition(Window window, double relX, double relY) {
+      setRelativeFramePosition(window,null,relX,relY);
+  }
+
+  /**
+   * Positioniert ein Fenster auf dem Monitor relativ zur Position eines
+   * anderen Fensters. (0.5/0.5) positioniert das Fenster z.B. genau in der Mitte
+   * anderen Fensters. (0.25/0.5) setzt das Fenster vertikal in die Mitte und
+   * horizontal auf ein Viertel des Referenz-Fensters.
+   * @param window das zu positionierende Fenster
+   * @param relFrame Fenster zu dem das Fenster in Relation gesetzt wird (wenn
+   *                 <code>null</code> wird der gesamte Monitor verwendet)
+   * @param relX  Relationsfaktor fuer die horizontale Position (0 < <code>relX</code> < 1)
+   * @param relY  Relationsfaktor fuer die vertikale Position (0 < <code>relY</code> < 1)
+   */
+  public static void setRelativeFramePosition(Window window, Window relFrame, double relX, double relY) {
+      Dimension screenSize  = Toolkit.getDefaultToolkit().getScreenSize();
+      Dimension relSize     = (relFrame==null) ? screenSize : relFrame.getSize();
+      Point     relLocation = (relFrame==null) ? new Point(0,0) : relFrame.getLocation();
+
+      // Fenster auf Monitor-Groesse anpassen
+      Dimension frameSize = window.getSize();
+      if (frameSize.height > screenSize.height) {
+        frameSize.height = screenSize.height;
+      }
+      if (frameSize.width > screenSize.width) {
+        frameSize.width = screenSize.width;
+      }
+      // Fenster positionieren
+      window.setLocation( relLocation.x + Math.round( (relSize.width-frameSize.width)*(float)relX ),
+                         relLocation.y + Math.round( (relSize.height-frameSize.height)*(float)relY )
+      );
+  }
+
+  /**
+   * Positioniert ein Fenster auf dem Monitor relativ zur Position eines
+   * anderen Fensters. Das Fenster wird jedoch immer innerhalb des Bildschirms
+   * positioniert.
+   * @param window das zu positionierende Fenster
+   * @param relWindow Fenster zu dem das Fenster in Relation gesetzt wird (wenn
+   *                 <code>null</code> wird der gesamte Monitor verwendet)
+   * @param type bestimmt, ob das Fenster innerhalb oder ausserhalb des relativen
+   *                       Fensters positioniert wird ({@link #BOUNDS_INNER} oder {@link #BOUNDS_OUTER}).
+   * @param position Positionierung des Fensters ({@link #NORTH}, {@link #NORTHEAST}, ...)
+   */
+  public static void setRelativeFramePosition(Window window, Window relWindow, int type, int position) {
+    if ( type == BOUNDS_INNER ) {
+      // An den inneren Grenzen des Frames ausrichten
+      double relX = 0;
+      double relY = 0;
+      switch ( position ) {
+        case NORTH:     relX = 0.5; relY = 0.0; break;
+        case NORTHWEST: relX = 0.0; relY = 0.0; break;
+        case NORTHEAST: relX = 1.0; relY = 0.0; break;
+        case SOUTH:     relX = 0.5; relY = 1.0; break;
+        case SOUTHWEST: relX = 0.0; relY = 1.0; break;
+        case SOUTHEAST: relX = 1.0; relY = 1.0; break;
+        case CENTER:    relX = 0.5; relY = 0.5; break;
+        case WEST:      relX = 0.0; relY = 0.5; break;
+        case EAST:      relX = 1.0; relY = 0.5; break;
+        default: throw new IllegalArgumentException("Unsupported mode for 'position'.");
+      }
+      setRelativeFramePosition(window,relWindow,relX,relY);
+      return;
+    }
+    if ( type == BOUNDS_OUTER ) {
+      Dimension screenSize  = Toolkit.getDefaultToolkit().getScreenSize();
+      Dimension relSize     = (relWindow==null) ? screenSize : relWindow.getSize();
+      Point     relLocation = (relWindow==null) ? new Point(0,0) : relWindow.getLocation();
+      // An den Aussen-Grenzen des Frames ausrichten
+      Point    loc = new Point(0,0);
+      switch ( position ) {
+        case NORTH:
+        case NORTHWEST:
+        case NORTHEAST: loc.y = relLocation.y - window.getHeight(); break;
+        case SOUTH:
+        case SOUTHWEST:
+        case SOUTHEAST: loc.y = relLocation.y + relSize.height; break;
+        case CENTER:
+        case WEST:
+        case EAST:      loc.y = relLocation.y + relSize.height/2 - window.getHeight()/2; break;
+        default: throw new IllegalArgumentException("Unsupported mode for 'position'.");
+      }
+      switch ( position ) {
+        case WEST:
+        case NORTHWEST:
+        case SOUTHWEST: loc.x = relLocation.x - window.getWidth(); break;
+        case EAST:
+        case NORTHEAST:
+        case SOUTHEAST: loc.x = relLocation.x + relSize.width; break;
+        case CENTER:
+        case NORTH:
+        case SOUTH:     loc.x = relLocation.x + relSize.width/2 - window.getWidth()/2; break;
+        default: throw new IllegalArgumentException("Unsupported mode for 'position'. GridBagConstraints location expected!");
+      }
+
+      loc.x = Math.max(0,loc.x);
+      loc.y = Math.max(0,loc.y);
+      if ( loc.x + window.getWidth() > screenSize.width )
+        loc.x = screenSize.width - window.getWidth();
+      if ( loc.y + window.getHeight() > screenSize.height )
+        loc.y = screenSize.height - window.getHeight();
+
+      // Fenster positionieren
+      window.setLocation( loc );
+      return;
+    }
+    throw new IllegalArgumentException("Unsupported mode for 'type'. BOUNDS_INNER or BOUNDS_OUTER expected!");
+  }
+
+  /**
+   * Aendert die Breite einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param w    Breite
+   */
+  public static void setWidth(Component comp, int w) {
+    Dimension d = comp.getPreferredSize();
+    d.width = w;
+    comp.setSize(d);
+  }
+
+  /**
+   * Aendert die Hoehe einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param h    Hoehe
+   */
+  public static void setHeight(Component comp, int h) {
+    Dimension d = comp.getPreferredSize();
+    d.height = h;
+    comp.setSize(d);
+  }
+
+  /**
+   * Aendert die bevorzugte Breite einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param w    Breite
+   */
+  public static void setPreferredWidth(Component comp, int w) {
+    Dimension d = comp.getPreferredSize();
+    d.width = w;
+    comp.setPreferredSize(d);
+  }
+
+  /**
+   * Aendert die bevorzugte Hoehe einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param h    Hoehe
+   */
+  public static void setPreferredHeight(Component comp, int h) {
+    Dimension d = comp.getPreferredSize();
+    d.height = h;
+    comp.setPreferredSize(d);
+  }
+
+  /**
+   * Aendert die minimal erlaubte Breite einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param w    Breite
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public static void setMinimumWidth(Component comp, int w) {
+	  Dimension d = comp.getMinimumSize();
+	  d.width = w;
+	  comp.setMinimumSize(d);
+  }
+
+  /**
+   * Aendert die minimal erlaubte Hoehe einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param h    Hoehe
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public static void setMinimumHeight(Component comp, int h) {
+	  Dimension d = comp.getMinimumSize();
+	  d.height = h;
+	  comp.setMinimumSize(d);
+  }
+
+  /**
+   * Aendert die maximal erlaubte Breite einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param w    Breite
+   */
+  public static void setMaximumWidth(Component comp, int w) {
+    Dimension d = comp.getMaximumSize();
+    d.width = w;
+    comp.setMaximumSize(d);
+  }
+
+  /**
+   * Aendert die maximal erlaubte Hoehe einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param h    Hoehe
+   */
+  public static void setMaximumHeight(Component comp, int h) {
+    Dimension d = comp.getMaximumSize();
+    d.height = h;
+    comp.setMaximumSize(d);
+  }
+
+  /**
+   * Fixiert die Groesse einer GUI-Komponente.
+   * @param comp GUI-Komponente
+   * @param d Ausmasse (Hoehe und Breite)
+   */
+  public static void fixComponentSize(Component comp, Dimension d) {
+    comp.setPreferredSize(d);
+    comp.setMinimumSize(d);
+    comp.setMaximumSize(d);
+  }
+
+  /**
+   * Fixiert die Groesse einer GUI-Komponente mit der aktuell
+   * bevorzugten Groesse.
+   * @param comp GUI-Komponente
+   */
+  public static void fixComponentSize(Component comp) {
+    fixComponentSize(comp, comp.getPreferredSize());
+  }
+
+  /***
+   * Setzt die Hintergrundfarbe einer Komponente und aller darin enthaltener
+   * Komponenten.
+   * @param comp Komponente
+   * @param color neue Hintergrund-Farbe
+   */
+  public static void setAllBackground(Component comp, Color color) {
+    comp.setBackground( color );
+    if ( comp instanceof JFrame )
+      setAllBackground( ((JFrame)comp).getContentPane(), color );
+    if ( comp instanceof JScrollPane )
+      setAllBackground( ((JScrollPane)comp).getViewport(), color );
+    else if ( comp instanceof Container )
+      for ( Component innerComp : ((Container)comp).getComponents() )
+        setAllBackground( innerComp, color );
+  }
+
+  /**
+   * Versucht, aus einem String eine Farbe zu erstellen. Drei Moeglichekeiten
+   * gibt es fuer das Format des Strings:
+   * <ol>
+   *   <li><code>"RGB(<i>red</i>,<i>green</i>,<i>blue</i>)"</code><br>
+   *       wobei <i>red</i>,<i>green</i> und <i>blue</i> dezimale Werte
+   *       zwischen 0 und 255 sind.</li>
+   *   <li>Der String stellt einen Integer-Wert im dezimalen, oktalen oder
+   *       hexadezimalen Format dar, aus dem die 3 RGB-Werte extrahiert werden
+   *       (siehe {@link Color#decode(String) Color.decode(..)}).</li>
+   *   <li>Der String spezifiziert ein statisches Feld der Klasse {@link Color}.<br>
+   *       z.B. steht <code>"RED"</code> fuer {@link Color#RED Color.RED},
+   *            <code>"darkGray"</code> fuer {@link Color#darkGray Color.darkGray} oder
+   *            <code>"LIGHT_GRAY"</code> fuer {@link Color#LIGHT_GRAY Color.LIGHT_GRAY}</li>
+   * </ol>
+   *
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   *
+   * @throws IllegalArgumentException wenn der uebergebene String nicht interpretiert werden kann.
+   *
+   */
+  // Schmeisst nur noch IllegalArgumentException, vorher Exception. (SK) 21.08.2007
+  public static Color parseColor(String colorStr) throws IllegalArgumentException {
+    // Wenn String mit "RGB(" startet, Format RGB(<r>,<b>,<g>) parsen
+    if ( colorStr.toUpperCase().startsWith("RGB(") )
+      try {
+        StringTokenizer tok = new StringTokenizer(colorStr.substring(4));
+        int r = Integer.parseInt(tok.nextToken(",()\n"));
+        int g = Integer.parseInt(tok.nextToken(",()\n"));
+        int b = Integer.parseInt(tok.nextToken(",()\n"));
+        return new Color(r,g,b);
+      } catch (Exception err) {
+        throw new IllegalArgumentException("Invalid RGB-specification. RGB(<r>,<g>,<b>) expected.",err);
+      }
+
+    // Versucht, die Farbe als dezimalen, oktalen oder hexadezimalen Integer
+    // einzulesen
+    Long testLong = null;
+    try {
+      testLong = Long.decode(colorStr);
+      return Color.decode(colorStr);
+    } catch (NumberFormatException err) {
+      // wenn 'testLong' ein gueltiger Long ist, handelt es sich zwar um
+      // eine gueltige Zahl, aber kein passendes Integer-Format nicht!
+      if ( testLong != null )
+        throw new IllegalArgumentException(colorStr + " is not a valid RGB-Integer in decimal, octal or hexadecimal format.");
+    }
+
+    // Wenn auch kein Integer angegeben wurde, wird versucht, die Farbe
+    // als Feld von java.awt.Color zu interpretieren
+    try {
+      return (Color)Color.class.getDeclaredField(colorStr).get(null);
+    } catch ( Exception err1 ) {
+      try {
+        // als letztes noch versuchen, die Farbe in Gross-Buchstaben zu finden
+        return (Color) Color.class.getDeclaredField(colorStr.toUpperCase()).get(null);
+      } catch ( Exception err2 ) {
+        throw new IllegalArgumentException(colorStr + " is not a valid color name. Name of a static field of class java.awt.Color expected.");
+      }
+    }
+  }
+
+  /**
+   * Setzt das Label eine Componente neu. Macht nichts, falls {@code newLabel}
+   * oder {@code comp} den Wert {@code null} hat.
+   * @param comp {@link JLabel}, {@link AbstractButton}, {@link JDialog} oder
+   *             {@link Frame}
+   * @param newLabel neue Beschriftung
+   * @exception UnsupportedOperationException falls {@code comp} nicht
+   *            unterstuetzt wird.
+   *
+   */
+  public static void resetCaption(Component comp, Object newLabel) {
+    if ( comp == null || newLabel == null )
+      return;
+    if ( comp instanceof JLabel )
+      ((JLabel)comp).setText( newLabel.toString() );
+    else if ( comp instanceof AbstractButton )
+      ((AbstractButton)comp).setText( newLabel.toString() );
+    else if ( comp instanceof Frame )
+      ((Frame)comp).setTitle( newLabel.toString() );
+    else if ( comp instanceof JDialog )
+      ((JDialog)comp).setTitle( newLabel.toString() );
+    else
+      throw new UnsupportedOperationException(SwingUtil.class.getSimpleName()+"resetCaption(.) can not be applied to "+comp.getClass().getSimpleName());
+  }
+
+  /**
+   * Erstellt das Pattern fuer ein {@link NumberFormat}.
+   * @param sample Beispiel-Wert, der die Anzahl der dargestellten Nachkomma-Stellen
+   *               bestimmt
+   */
+  public static String getNumberFormatPattern(double sample) {
+    String format = "0";
+    for (int i=0; sample != (int)sample; i++ ) {
+      if ( i==0 )
+        format += ".";
+      sample *= 10;
+      format += "0";
+    }
+    return format;
+  }
+
+  /**
+   * Erstellt das Pattern fuer ein {@link NumberFormat}.
+   * @param digits Anzahl der dargestellten Nachkomma-Stellen
+   */
+  public static String getNumberFormatPattern(int digits) {
+    String format = "0";
+    for (int i=0; i<digits; i++ ) {
+      if ( i==0 )
+        format += ".";
+      format += "0";
+    }
+    return format;
+  }
+
+  /**
+   * Copied from http://www.exampledepot.com/egs/javax.swing.tree/ExpandAll.html
+   * e1029. Expanding or Collapsing All Nodes in a JTree Component
+   * If expand is true, expands all nodes in the tree.
+   * Otherwise, collapses all nodes in the tree.
+   * @param tree {@link JTree} to expand or collapse
+   */
+  public static void expandAll(JTree tree, boolean expand) {
+      TreeNode root = (TreeNode)tree.getModel().getRoot();
+
+      // Traverse tree from root
+      expandAll(tree, new TreePath(root), expand);
+  }
+  private static void expandAll(JTree tree, TreePath parent, boolean expand) {
+      // Traverse children
+      TreeNode node = (TreeNode)parent.getLastPathComponent();
+      if (node.getChildCount() >= 0) {
+          for (Enumeration e=node.children(); e.hasMoreElements(); ) {
+              TreeNode n = (TreeNode)e.nextElement();
+              TreePath path = parent.pathByAddingChild(n);
+              expandAll(tree, path, expand);
+          }
+      }
+
+      // Expansion or collapse must be done bottom-up
+      if (expand) {
+          tree.expandPath(parent);
+      } else {
+          tree.collapsePath(parent);
+      }
+  }
+
+  /**
+   * This method maximizes a frame; the iconified bit is not affected
+   * Taken from e564. Iconifying and Maximizing a Frame,
+   * http://www.exampledepot.com/egs/java.awt/frame_FrameIconify.html
+   *
+   * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+   */
+  public static void maximize(Frame frame) {
+      int state = frame.getExtendedState();
+
+      // Set the maximized bits
+      state |= Frame.MAXIMIZED_BOTH;
+
+      // Maximize the frame
+      frame.setExtendedState(state);
+  }
+
+}

Added: trunk/src/schmitzm/swing/SwingWorker.java
===================================================================
--- trunk/src/schmitzm/swing/SwingWorker.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/SwingWorker.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,246 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Frame;
+
+import schmitzm.swing.StatusDialog;
+import schmitzm.lang.WorkingThread;
+
+/**
+ * Diese Klasse stellt einen Thread dar, der (aufwendigere) Arbeiten innerhalb einer
+ * GUI ausfuehrt, damit diese nicht blockiert.<br>
+ * Dem SwingWorker <b>kann</b> ein {@link StatusDialog} (modal!) zugeordnet werden.
+ * Ist dies der Fall kehrt der Thread nicht aus seiner <code>start()</code>-Routine
+ * zurueck sondern sperrt mit dem Dialog die zugrunde liegende GUI.
+ * Ist die "Arbeit" beendet schliesst der Thread automatisch den Dialog, so dass
+ * die GUI wieder zugeaenglich wird. Zudem kann die Arbeit des SwingWorker jederzeit
+ * ueber den Abbruch-Button des {@link StatusDialog} beendet werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class SwingWorker extends WorkingThread {
+  /** Speichert den Arbeitsablauf fuer den Thread. */
+  protected Work         work = null;
+  /** Speichert den Dialog, der waehrend der Arbeit des SwingWorkers angezeigt wird. */
+  protected StatusDialog dialog = null;
+  /** Speichert, ob der Dialog nach Beendigung der Arbeit offen gehalten soll
+   *  (Default: <code>false</code>).
+   *  @see #setKeepOpen(boolean) */
+  protected boolean keepOpen = false;
+  /** Speichert, ob der Worker (durch einen Fehler) abgebrochen wurde ({@code true})
+   *  oder seine Arbeit erfolgreich ausgefuehrt hat ({@code false}). */
+  protected boolean canceled = false;
+  /** Resultat von {@link Work#execute()}. Kann nach Beendigung des Threads
+   *  ueber {@link #getWorkResult()} abgefragt werden.*/
+  protected Object workResult = null;
+
+  /**
+   * Erzeugt einen neuen SwingWorker. Waehrend der Arbeit wird kein Dialog
+   * angezeigt. Der Thread kehrt unmittelbar aus seiner <code>start()</code>-Routine
+   * zurueck, so dass eine untergeordnete GUI <b>nicht</b> blockiert.
+   * @param work auszufuehrende Arbeit
+   */
+  public SwingWorker(Work work) {
+    this(work,null);
+  }
+
+  /**
+   * Erzeugt einen neuen SwingWorker. Der Thread kehrt nicht aus seiner
+   * <code>start()</code>-Routine zurueck, sondern zeigt den (modalen) Dialog
+   * an, so dass eine untergeordnete GUI gesperrt wird.
+   * @param work   auszufuehrende Arbeit
+   * @param dialog anzuzeigender Dialog (modal!)
+   */
+  public SwingWorker(Work work, StatusDialog dialog) {
+    this.work   = work;
+    this.dialog = dialog;
+    // Worker in der Work-Klasse setzten, so dass waehrend der
+    // Ausfuehrung der Arbeit auf den Worker (Dialog) zugegriffen werden
+    // kann
+    work.worker = this;
+  }
+
+  /**
+   * Erzeugt einen neuen SwingWorker. Der Thread kehrt nicht aus seiner
+   * <code>start()</code>-Routine zurueck, sondern zeigt einen (modalen) Dialog
+   * an, so dass eine untergeordnete GUI gesperrt wird.
+   * @param work       auszufuehrende Arbeit
+   * @param parent     uebergeordnete GUI (kann <code>null</code> sein!)
+   * @param statusMess Meldung, die im Dialog angezeigt wird
+   */
+  public SwingWorker(Work work, Frame parent, String statusMess) {
+    this(work,parent,statusMess,0.5,0.5);
+  }
+
+  /**
+   * Erzeugt einen neuen SwingWorker. Der Thread kehrt nicht aus seiner
+   * <code>start()</code>-Routine zurueck, sondern zeigt einen (modalen) Dialog
+   * an, so dass eine untergeordnete GUI gesperrt wird.
+   * @param work       auszufuehrende Arbeit
+   * @param parent     uebergeordnete GUI (kann <code>null</code> sein!)
+   * @param statusMess Meldung, die im Dialog angezeigt wird
+   * @param relX       horizontale Positionierung des Dialogs relativ zum uebergeordneten Fenster
+   * @param relY       vertikale Positionierung des Dialogs relativ zum uebergeordneten Fenster
+   */
+  public SwingWorker(Work work, Frame parent, String statusMess, double relX, double relY) {
+    this(work,
+         new StatusDialog(
+           parent,
+           SwingUtil.RESOURCE.getString("WaitMess"),
+           statusMess,
+           relX,
+           relY
+         )
+    );
+  }
+
+  /**
+   * Bestimmt, ob der Dialog nach Beendigung der Arbeit geoeffnet bleibt.
+   * Standardmaessig wird der Dialog automatisch geschlossen.
+   * @param keepOpen wenn <code>false</code> wird der Dialog nach Beendigung
+   *                 der Arbeit automatisch geschlossen.
+   */
+  public void setKeepOpen(boolean keepOpen) {
+    this.keepOpen = keepOpen;
+  }
+
+  /**
+   * Prueft, ob der Dialog nach Beendigung der Arbeit geoeffnet bleibt.
+   * Standardmaessig wird der Dialog automatisch geschlossen.
+   * @see #setKeepOpen(boolean)
+   */
+  public boolean isKeptOpen() {
+    return this.keepOpen;
+  }
+
+  /**
+   * Startet den Thread. Wurde ein Dialog angegeben/erzeugt wird dieser
+   * nach dem Thread-Start angezeigt und blockiert den untergeordneten
+   * Ablauf. Der Thread kehrt erst nach Beendigung seiner Arbeit oder wenn
+   * der Dialog abgebrochen wurde, aus dieser Methode zurueck.
+   */
+  public void start() {
+    super.start();
+    if ( dialog != null ) {
+      dialog.setVisible(true);
+      if ( dialog.isCanceled() && isRunning() )
+        terminate();
+    }
+  }
+
+  /**
+   * Fuehrt die dem Thread zugeordnete Arbeit aus und verarbeitet auftretende
+   * Fehler.
+   * @see Work#execute()
+   * @see Work#performError(Throwable)
+   */
+  public void performWork() {
+    try {
+      this.canceled = false;
+      this.workResult = work.execute();
+    } catch (Throwable err) {
+      // Fehler die zum Abbruch der "Arbeit" fuehren
+      this.canceled = true;
+      this.workResult = null;
+      work.performError(err);
+    }
+  }
+
+  /**
+   * Initialisierung des Threads. Macht nichts!
+   */
+  public void performInit() {
+  }
+
+  /**
+   * Wird ausgefuehrt nachdem der Thread beendet wurde. Schliesst einen evt.
+   * vorhandenen Dialog, so dass eine untergeordnete GUI wieder freigegeben wird.
+   */
+  public void performDispose() {
+    if (dialog != null) {
+      if ( dialog.isCanceled() )
+        this.canceled = true;
+      if ( !keepOpen )
+        dialog.setVisible(false);
+      else
+        dialog.setDialogOption( StatusDialog.OK_OPTION );
+    }
+  }
+
+  /**
+   * Bricht die Arbeit des Threads <b>unmittelbar</b> und <b>unkontrolliert</b>
+   * ab.
+   * @see Thread#stop()
+   */
+  public void terminate() {
+    stop();
+  }
+
+  /**
+   * Liefert den Dialog, der waehrend der Arbeit des Threads angezeigt wird.
+   * @return <code>null</code> falls kein Dialog angegeben wurde.
+   */
+  public StatusDialog getDialog() {
+    return dialog;
+  }
+
+  /**
+   * Liefert {@code true}, wenn der Worker (aufgrund eines Fehlers oder
+   * manuell durch den Dialog) abgebrochen wurde.
+   */
+  public boolean isCanceled() {
+    return canceled;
+  }
+
+  /**
+   * Liefert nach der Beendigung des Threads dessen Ergebnis.
+   * @see Work#execute()
+   */
+  public Object getWorkResult() {
+    return this.workResult;
+  }
+
+  /**
+   * Diese Klasse spezifiziert die Arbeit eines {@link SwingWorker}.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static abstract class Work {
+    // speichert den SwingWorker, der die Arbeit verrichtet
+    // WIRD VON DER AUSFUEHRENDEN SWINGWORKER-KLASSE GESETZT!!
+    private SwingWorker worker = null;
+
+    /**
+     * Implementiert den Arbeitsablauf des {@link SwingWorker}.
+     * @return "irgendwas" als Ergebnis
+     */
+    public abstract Object execute() throws Exception;
+
+    /**
+     * Implementiert die Fehlerbehandlung, wenn waehrend des Arbeitsablauf
+     * ein Fehler auftritt. In dieser Basis-Implementierung wird lediglich
+     * eine Fehlermeldung angezeigt.<br>
+     * Kann gefahrlos von Unterklassen ueberschrieben werden.
+     */
+    public void performError(Throwable err) {
+      ExceptionDialog.show(worker.dialog,err);//,SwingResource.DEFAULT.getString("Error"),err.getMessage());
+    };
+
+    /**
+     * Liefert den {@link SwingWorker}, der die Arbeit verrichtet.
+     */
+    public SwingWorker getSwingWorker() {
+      return worker;
+    }
+  }
+}

Added: trunk/src/schmitzm/swing/TextAreaPrintStream.java
===================================================================
--- trunk/src/schmitzm/swing/TextAreaPrintStream.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/TextAreaPrintStream.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,39 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import javax.swing.JTextArea;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Diese Klasse stellt einen {@link PrintStream} dar, der an eine {@link JTextArea}
+ * gekoppelt ist. Jegliche Ausgabe, die auf den Stream geschrieben wird, wird
+ * in der TextArea ausgegeben
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class TextAreaPrintStream extends PrintStream {
+  /**
+   * Erzeugt einen neuen Stream.
+   * @param textArea Ausgabe-Komponente
+   */
+  public TextAreaPrintStream(final JTextArea textArea) {
+    super(new OutputStream() {
+      public void write(int b) {
+        textArea.append( String.valueOf((char)b) );
+        // Position ans Ende des Textes setzen
+        textArea.setCaretPosition( textArea.getText().lastIndexOf("\n")+1 );
+      }
+    } );
+  }
+}

Added: trunk/src/schmitzm/swing/TreeSelectionDialog.java
===================================================================
--- trunk/src/schmitzm/swing/TreeSelectionDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/TreeSelectionDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,100 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import javax.swing.JOptionPane;
+import javax.swing.JTree;
+import javax.swing.JScrollPane;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import schmitzm.swing.MultipleOptionPane;
+import schmitzm.swing.tree.EmptyNode;
+
+/**
+ * Diese Klasse stellt einen Dialog dar, der Objekte in einer Baumstruktur zur
+ * Auswahl stellt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class TreeSelectionDialog extends JOptionPane {
+  private TreeModel           model = null;
+  private JTree               tree  = null;
+  private TreeSelectionDialog THIS  = this;
+
+  /**
+   * Erzeugt einen neuen Dialog.
+   * @param model Daten-Basis fuer die darzustellenden Objekte
+   */
+  public TreeSelectionDialog(TreeModel model) {
+    super();
+    this.setPreferredSize(new Dimension(250,300));
+    this.validate();
+    this.model = model;
+    this.tree  = new JTree(model);
+    tree.setRootVisible(false);
+    this.setOptionType(JOptionPane.OK_CANCEL_OPTION);
+    this.setMessage( new JScrollPane(tree) );
+    // OK-Button soll nur aktiviert sein, wenn ein passendes Objekt
+    // ausgewaehlt ist
+    final JButton okButton = (JButton)((JPanel)THIS.getComponent(1)).getComponent(0);
+    okButton.setEnabled(false);
+    this.tree.addTreeSelectionListener( new TreeSelectionListener() {
+      public void valueChanged(TreeSelectionEvent e) {
+        okButton.setEnabled(
+            e.getNewLeadSelectionPath() != null &&
+            e.getNewLeadSelectionPath().getLastPathComponent() != null &&
+            e.getNewLeadSelectionPath().getLastPathComponent() instanceof DefaultMutableTreeNode &&
+            ((DefaultMutableTreeNode)e.getNewLeadSelectionPath().getLastPathComponent()).getUserObject() != null
+        );
+      }
+    });
+  }
+
+  /**
+   * Zeigt den Auswahldialog an.
+   * @param parent aufrufende Komponente
+   * @param title Titel fuer das Dialogfenster
+   * @param multipleSelect bestimmt, ob mehrere Baum-Elemente (Objekte) gleichzeitig
+   *        ausgewaehlt werden duerfen
+   * @return alle ausgewaehlten Objekte oder <code>null</code>, wenn der Dialog
+   *         abgebrochen wurde.
+   */
+  public Object[] show(Component parent, String title, boolean multipleSelect) {
+    if ( multipleSelect )
+      tree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
+    else
+      tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
+
+    JDialog dialog = this.createDialog(parent,title);
+    dialog.setVisible(true);
+    // Dialog abgebrochen
+    if ( getValue() == null || ((Integer)getValue() != JOptionPane.OK_OPTION) )
+      return null;
+    // Dialog mit OK beendet
+    TreePath[] selPath = tree.getSelectionPaths();
+    Object[]   selObj = new Object[selPath.length];
+    for (int i=0; i<selObj.length; i++)
+      selObj[i] = ((DefaultMutableTreeNode)selPath[i].getLastPathComponent()).getUserObject();
+    return selObj;
+  }
+}

Added: trunk/src/schmitzm/swing/event/InputOptionAdapter.java
===================================================================
--- trunk/src/schmitzm/swing/event/InputOptionAdapter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/InputOptionAdapter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,45 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.event;
+
+import schmitzm.swing.InputOption;
+
+/**
+ * Diese Klasse bildet eine Implementierung von {@link InputOptionListener},
+ * die nichts macht.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class InputOptionAdapter implements InputOptionListener {
+  /**
+   * Wird aufgerufen, wenn sich der Eingabewert des Option geaendert hat.
+   * @param inputOption Option, die sich geaendert hat
+   * @param oldValue alter Wert
+   * @param newValue neuer Wert
+   */
+  public void optionChanged(InputOption inputOption, Object oldValue, Object newValue) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn die Eingabe-Komponente der Option den Fokus erhaelt.
+   * @param inputOption Option, die den Focus erhalten hat
+   */
+  public void optionGainedFocus(InputOption inputOption) {
+  }
+
+  /**
+   * Wird aufgerufen, wenn die Eingabe-Komponente der Option den Fokus verliert.
+   * @param inputOption Option, die den Focus verloren hat
+   */
+  public void optionLostFocus(InputOption inputOption) {
+  }
+}

Added: trunk/src/schmitzm/swing/event/InputOptionListener.java
===================================================================
--- trunk/src/schmitzm/swing/event/InputOptionListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/InputOptionListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,43 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.event;
+
+import schmitzm.swing.InputOption;
+
+/**
+ * Dieser Listener verfolgt die Eingaben in einer {@link schmitzm.swing.InputOption}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface InputOptionListener {
+  /**
+   * Wird aufgerufen, wenn sich der Eingabewert des Option geaendert hat.
+   * @param inputOption Option, die sich geaendert hat
+   * @param oldValue alter Wert
+   * @param newValue neuer Wert
+   */
+  public void optionChanged(InputOption inputOption, Object oldValue, Object newValue);
+
+  /**
+   * Wird aufgerufen, wenn die Eingabe-Komponente der Option den Fokus
+   * erhaelt.
+   * @param inputOption Option, die den Focus erhalten hat
+   */
+  public void optionGainedFocus(InputOption inputOption);
+
+  /**
+   * Wird aufgerufen, wenn die Eingabe-Komponente der Option den Fokus
+   * verliert.
+   * @param inputOption Option, die den Focus verloren hat
+   */
+  public void optionLostFocus(InputOption inputOption);
+}

Added: trunk/src/schmitzm/swing/event/MouseAdapter.java
===================================================================
--- trunk/src/schmitzm/swing/event/MouseAdapter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/MouseAdapter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,30 @@
+package schmitzm.swing.event;
+
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseEvent;
+
+/**
+ * Diese Klasse erweitert den {@link java.awt.event.MouseAdapter} um die
+ * Funktionen des {@link MouseMotionListener}.<br>
+ * <b>Diese Klasse stellt einen Workaround fuer Java 1.5 dar. In Java 1.6 ist
+ * diese Funktionalitaet bereits im {@link java.awt.event.MouseAdapter}
+ * enthalten!</b>
+ * @deprecated
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MouseAdapter extends java.awt.event.MouseAdapter implements MouseMotionListener {
+  /**
+   * Macht nichts.
+   * @param e Maus-Ereignis
+   */
+  public void mouseDragged(MouseEvent e) {
+  }
+
+  /**
+   * Macht nichts.
+   * @param e Maus-Ereignis
+   */
+  public void mouseMoved(MouseEvent e) {
+  }
+}

Added: trunk/src/schmitzm/swing/event/PopupMenuListener.java
===================================================================
--- trunk/src/schmitzm/swing/event/PopupMenuListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/PopupMenuListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,66 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.event;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+import javax.swing.JPopupMenu;
+import javax.swing.event.MouseInputAdapter;
+
+/**
+ * Diese Klasse implementiert einen MouseListener, der auf einen
+ * PopupTrigger lauscht. Sofern dieser erfolgt, wird ein Popup-Menue
+ * angezeigt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class PopupMenuListener extends MouseInputAdapter {
+  private JPopupMenu menu;
+
+  /**
+   * Erzeugt einen neuen Listener
+   * @param menu Popup-Menue, welches geoeffnet werden soll
+   */
+  public PopupMenuListener(JPopupMenu menu) {
+    super();
+    this.menu = menu;
+  }
+
+  /**
+   * Checkt, ob ein PopupTrigger (i.A. Rechtsklick) auf eine mit dem
+   * PopupMenue verbundene Komponente stattgefunden hat.
+   * Ist dies der Fall, wird das PopupMenue geoeffnet.
+   */
+  protected void checkPopupSignal(MouseEvent e) {
+    if ( e.isPopupTrigger() ) {
+      menu.show( (Component)e.getSource(), e.getX(), e.getY() );
+      e.consume();
+    }
+  }
+
+  /**
+   * Prueft auf <code>MouseEvent.isPopupTrigger()</code> und zeigt gegebenfalls
+   * das Menu an.
+   */
+  public void mousePressed(MouseEvent e) {
+    checkPopupSignal(e);
+
+  }
+
+  /**
+   * Prueft auf <code>MouseEvent.isPopupTrigger()</code> und zeigt gegebenfalls
+   * das Menu an.
+   */
+  public void mouseReleased(MouseEvent e) {
+    checkPopupSignal(e);
+  }
+}

Added: trunk/src/schmitzm/swing/event/WindowEventConnector.java
===================================================================
--- trunk/src/schmitzm/swing/event/WindowEventConnector.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/WindowEventConnector.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,113 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.event;
+
+import java.awt.Window;
+import java.awt.Frame;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowAdapter;
+// nur fuer Doku
+import java.awt.Dialog;
+
+/**
+ * Dieser {@link WindowAdapter} verbindet ein (Unter-)Fenster mit den Aktionen
+ * eines anderen (Haupt-)Fensters.<br>
+ * Wird das Haupt-Fenster geschlossen oder minimiert, wird auch das verbundene
+ * Unter-Fenster verborgen. Wird das Haupt-Fenster geoeffnet, wird auch das
+ * Unter-Fenster angezeigt, sofern es beim Schliessen des Haupt-Fensters
+ * geoeffnet war.<br>
+ * Fuer jedes Unter-Fenster wird ein <code>WindowEventConnector</code> erzeugt
+ * und als {@link Window#addWindowListener WindowListener} an das Haupt-Fenster
+ * gekoppelt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class WindowEventConnector extends WindowAdapter {
+  /** Kontrolliertes Unter-Fenster. */
+  protected Window    window     = null;
+  /** Speichert den Status des Unter-Fensters, wenn das Haupt-Fenster geschlossen
+   *  oder minimiert wird.*/
+  protected boolean   wasVisible = false;
+
+  /**
+   * Erzeugt einen neuen Connector.
+   * @param window durch den Connector gesteuertes (Unter-)Fenster
+   */
+  public WindowEventConnector(Window window) {
+    super();
+    this.window = window;
+  }
+
+  /**
+   * Wird aufgerufen, wenn das Haupt-Fenster geschlossen wird. Bevor das Unter-Fenster
+   * ebenfalls geschlossen wird, wird sein Status gesichtert.
+   * @see #wasVisible
+   */
+  public void windowClosed(WindowEvent e) {
+    wasVisible = window.isVisible();
+    window.setVisible(false);
+  }
+
+  /**
+   * Wird aufgerufen, wenn das Haupt-Fenster ueber das System-Menue geschlossen
+   * wird. Bevor das Unter-Fenster ebenfalls geschlossen wird, wird sein Status
+   * gesichert.
+   * @see #wasVisible
+   */
+  public void windowClosing(WindowEvent e) {
+    windowClosed(e);
+  }
+
+  /**
+   * Wird aufgerufen, wenn das Haupt-Fenster geoeffnet wird. War das Unter-Fenster
+   * zuvor geoeffnet, wird es ebenfalls wieder angezeigt.
+   */
+  public void windowOpened(WindowEvent e) {
+    if ( !window.isVisible() )
+      window.setVisible(wasVisible);
+  }
+
+// KLAPPT NICHT WIE GEWUENSCHT
+//  /**
+//   * Wird aufgerufen, wenn das Haupt-Fenster aktiviert wird.
+//   */
+//  public void windowActivated(WindowEvent e) {
+//    windowOpened(e);
+//  }
+
+  /**
+   * Wird aufgerufen, wenn das Haupt-Fenster minimiert wird. Handelt es sich
+   * bei dem Unter-Fenster um einen {@link Frame}, wird auch dieses minimiert.
+   * Andernfalls (z.B. bei einem {@link Dialog}) wird das Unter-Fenster
+   * verborgen.
+   */
+  public void windowIconified(WindowEvent e) {
+    if ( window instanceof Frame )
+      ((Frame)window).setState(Frame.ICONIFIED);
+    else
+      windowClosed(e);
+  }
+
+  /**
+   * Wird aufgerufen, wenn das Haupt-Fenster "deminimiert" wird. Handelt es sich
+   * bei dem Unter-Fenster um einen {@link Frame}, wird auch dieses "deminimiert".
+   * Andernfalls (z.B. bei einem {@link Dialog}) wird das Unter-Fenster
+   * wieder angezeigt.
+   */
+  public void windowDeiconified(WindowEvent e) {
+    if ( window instanceof Frame )
+      ((Frame)window).setState(Frame.NORMAL);
+    else
+      windowOpened(e);
+  }
+
+}

Added: trunk/src/schmitzm/swing/event/package.html
===================================================================
--- trunk/src/schmitzm/swing/event/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/event/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code javax.swing.event}.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/swing/log4j/LoggerConfigurationTableModel.java
===================================================================
--- trunk/src/schmitzm/swing/log4j/LoggerConfigurationTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/log4j/LoggerConfigurationTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,164 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.log4j;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.TreeSet;
+import java.util.Comparator;
+import javax.swing.table.AbstractTableModel;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Level;
+
+// nur fuer Doku
+import javax.swing.table.TableModel;
+import org.apache.log4j.Appender;
+
+/**
+ * Diese Klasse stellt ein {@link TableModel} dar, in dem die aktuell
+ * im {@link LogManager} registrierten Log4j-Logger in 4 Spalten angezeigt werden.
+ * <ol start=0>
+ *   <li>{@linkplain Logger#getName() Logger-Name} (Typ {@link String})</li>
+ *   <li>{@linkplain Logger#getLevel() Logger-Level} (Typ {@link Level})</li>
+ *   <li>{@linkplain Logger#getAdditivity() Logger-Additiviaet (Rekursive Verarbeitung der Vater-Appender)} (Typ {@link Boolean})</li>
+ *   <li>Appender-Liste (Typ {@link Appender Enumeration&lt;Appender&gt;})</li>
+ * </ol>
+ * <b><u>Beachte</u>:</b><br>
+ * Nur die Spalten 1 (Level) und 2 (Additivitaet) sind als veraenderbar vorgesehen!
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LoggerConfigurationTableModel extends AbstractTableModel {
+  /** Spalten-Namen der Tabelle. */
+  public final String[] COL_NAME = new String[] {"Logger","Level","Additivity","Appender list"};
+  /** Spalten-Typen der Tabelle.
+   *  <ol start=0>
+   *    <li>{@link String}</li>
+   *    <li>{@link Level}</li>
+   *    <li>{@link Boolean}</li>
+   *    <li>{@link Enumeration}</li>
+   *  </ol> */
+  public final Class[] COL_TYPE = new Class[] {String.class,Level.class,Boolean.class,Enumeration.class};
+
+  /** {@link LogManager#getCurrentLoggers()} liefert eine {@link Enumeration}.
+   *  Fuer einen wahlfreien Zugriff, werden die Logger bei der {@link #refresh()}-Aktion
+   *  in ein {@link Vector} kopiert. */
+  protected Vector<Logger> loggers = new Vector<Logger>();
+
+  /**
+   * Erzeugt ein neues Tabellenmodell.
+   */
+  public LoggerConfigurationTableModel() {
+    super();
+    refresh();
+  }
+
+  /**
+   * Aktualisiert die Tabelle auf Basis der aktuelle registrierten
+   * Logger.
+   * @see LogManager#getCurrentLoggers()
+   */
+  public void refresh() {
+    loggers.clear();
+    loggers.add( LogManager.getRootLogger() );
+    Enumeration<Logger> newLoggers = LogManager.getCurrentLoggers();
+    // Fuer die Sortierung die Logger in ein TreeSet einfuegen
+    TreeSet<Logger> sortedLoggers = new TreeSet<Logger>( new Comparator<Logger>() {
+      public int compare(Logger logger1, Logger logger2) {
+        return logger1.getName().compareTo( logger2.getName() );
+      }
+    });
+    for (; newLoggers != null && newLoggers.hasMoreElements(); )
+      sortedLoggers.add( newLoggers.nextElement() );
+
+    // Die sortierten Logger der Reihe nach in die Logger-Liste einfuegen
+    for (; !sortedLoggers.isEmpty();) {
+      loggers.add(sortedLoggers.first());
+      sortedLoggers.remove( sortedLoggers.first() );
+    }
+    this.fireTableDataChanged();
+  }
+
+  /**
+   * Liefert die Anzahl an Zeilen (Loggern) der Tabelle.
+   */
+  public int getRowCount() {
+    return loggers.size();
+  }
+
+  /**
+   * Liefert die Spaltenanzahl der Tabelle. Immer 3.
+   */
+  public int getColumnCount() {
+    return COL_NAME.length;
+  }
+
+  /**
+   * Liefert einen Spaltennamen der Tabelle.
+   */
+  public String getColumnName(int col) {
+    return COL_NAME[col];
+  }
+
+  /**
+   * Liefert den Typ einer Tabellen-Spalte.
+   */
+  public Class getColumnClass(int col) {
+    return COL_TYPE[col];
+  }
+
+  /**
+   * Liefert einen Zellenwert der Tabelle.
+   * @param row Zeilenindex (beginnend bei 0)
+   * @param col Spaltenindex (beginnend bei 0)
+   */
+  public Object getValueAt(int row, int col) {
+    Logger logger = loggers.elementAt(row);
+    switch (col) {
+      case 0: return logger.getName();
+      case 1: return logger.getLevel();
+      case 2: return logger.getAdditivity();
+      case 3: return logger.getAllAppenders();
+    }
+    return null;
+  }
+
+  /**
+   * Prueft, ob eine Zeile editierbar ist.
+   * @param row Zeilenindex (beginnend bei 0)
+   * @param col Spaltenindex (beginnend bei 0)
+   * @return {@code true} nur fuer Spalte 1 (Logger-Level) und Spalte 3
+   * (Additivitaet)
+   */
+  public boolean isCellEditable(int row, int col) {
+    return col == 1 || col == 2;
+  }
+
+  /**
+   * Setzt einen Zellenwert der Tabelle. Nur relevant fuer die Spalten 1 (Level)
+   * und 2 (Additivitaet).
+   * @param value neue Wert
+   * @param row Zeilenindex (beginnend bei 0)
+   * @param col Spaltenindex (beginnend bei 0)
+   */
+  public void setValueAt(Object value, int row, int col) {
+    Logger logger = loggers.elementAt(row);
+    switch (col) {
+      case 1: logger.setLevel( (Level)value );
+              break;
+      case 2: logger.setAdditivity( (Boolean)value );
+              break;
+    }
+  }
+}

Added: trunk/src/schmitzm/swing/log4j/LoggerFrame.java
===================================================================
--- trunk/src/schmitzm/swing/log4j/LoggerFrame.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/log4j/LoggerFrame.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,250 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.log4j;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JFrame;
+import javax.swing.JTable;
+import javax.swing.JScrollPane;
+import javax.swing.JComboBox;
+import javax.swing.JCheckBox;
+import javax.swing.JButton;
+import javax.swing.DefaultCellEditor;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Appender;
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PropertyConfigurator;
+
+import schmitzm.swing.SwingUtil;
+import schmitzm.swing.ExceptionDialog;
+import schmitzm.swing.FileInputOption;
+import schmitzm.swing.table.ComponentRenderer;
+
+// nur fuer Doku
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggerRepository;
+
+/**
+ * Diese Klasse stellt ein Fenster dar, in dem die aktuell im {@link LogManager}
+ * registrierten Log4j-Logger als Tabelle dargestellt sind.
+ * <ol>
+ *   <li>Logger-Name</li>
+ *   <li>Level</li>
+ *   <li>Additivitaet (ob rekursiv auch die Vater-Appender ausgefuehrt werden)</li>
+ *   <li>Dateinamen der {@link FileAppender}, "CONSOLE" fuer {@link ConsoleAppender}</li>
+ * </ol>
+ * Ueber einen {@linkplain #refresh() Refresh-Button} kann die Tabelle jederzeit aktualisiert
+ * werden. Es koennen <b>keine</b> Logger hinzugefuegt oder entfernt werden.
+ * Veraendert werden koennen die Logger nur hinsichtlich ihres Levels und ihrer
+ * Addititvitaet.<br>
+ * Die Eingabe-Maske bietet jedoch die Moeglichkeit, eine Logger-Konfiguration
+ * aus einer {@linkplain PropertyConfigurator#doConfigure(String,LoggerRepository) Log4j-Konfigurationsdatei}
+ * (neu) zu laden.<br>
+ * <b><u>Beachte</u>:</b><br>
+ * Beim Neu-Laden werden lediglich die <u>explizit</u> in der Datei spezifizierten
+ * Logger-Konfigurationen aktualisiert. Vom der Applikation erzeugte Logger bleiben
+ * erhalten (und koennen auch nicht mehr geloescht werden)!
+ * Ebenso bleiben alle Logger-Properties unveraendert erhalten, die nicht
+ * <u>explizit</u> in der Konfigurationsdatei angegeben sind. Wird z.B. die
+ * Additivitaet eines Loggers in der Datei nicht gesetzt, wird sie beim ersten
+ * Einlesen per Default auf {@code true} gesetzt. Wird sie durch den Anwender
+ * abgeaendert, bleibt diese Einstellung erhalten, auch wenn die Datei neu
+ * eingelesen wird.
+ * @see LoggerConfigurationTableModel
+ * @see Logger
+ * @see LogManager
+ * @see PropertyConfigurator#configure(String)
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class LoggerFrame extends JFrame {
+  /** Eingabe-Feld fuer die Log4j-Konfigurationsdatei. */
+  protected FileInputOption configFileField =  new FileInputOption(null,false);
+  /** Butten, ueber den die Log4j-Konfigurationsdatei neu geladen wird.
+   *  @see #reload() */
+  protected JButton reloadButton = new JButton( SwingUtil.RESOURCE.getString("Reload") );
+  /** Butten, ueber den die Logger-Tabelle aktualisiert wird.
+   *  @see #refresh() */
+  protected JButton refreshButton = new JButton( SwingUtil.RESOURCE.getString("Refresh") );
+  /** Tabelle, in der die Logger angezeigt werden.
+   *  @see #loggerTableModel */
+  protected JTable loggerTable = null;
+  /** Datenbasis fuer die Logger-Tabelle. */
+  protected LoggerConfigurationTableModel loggerTableModel = null;
+
+  /**
+   * Erzeugt ein neues Fenster.
+   * @param file vorgeblendete Log4j-Konfigurationsdatei
+   */
+  public LoggerFrame(File file) {
+    super("Log4j Logger configuration");
+    initGUI();
+    configFileField.setValue(file);
+  }
+
+  /**
+   * Erzeugt ein neues Fenster.
+   */
+  public LoggerFrame() {
+    this(null);
+  }
+
+  /**
+   * Initialisiert das Fenster-Layout.
+   */
+  protected void initGUI() {
+    this.setLayout( new GridBagLayout() );
+    this.setSize(400,300);
+    // Logger-Tabelle
+    loggerTableModel = new LoggerConfigurationTableModel();
+    loggerTable = new JTable(loggerTableModel);
+    loggerTable.setCellSelectionEnabled(false);
+    loggerTable.setRowHeight( new JComboBox().getPreferredSize().height );
+    loggerTable.setDefaultRenderer( Enumeration.class, new ComponentRenderer() {
+      public Component createRendererComponent( JTable table,
+                                                Object value,
+                                                boolean isSelected,
+                                                boolean hasFocus,
+                                                int row,
+                                                int column) {
+
+        // Appender in String umwandeln
+        if ( value instanceof Enumeration )
+          value = getStringFromAppenders((Enumeration<Appender>)value);
+        return super.createRendererComponent(table,value,isSelected,hasFocus,row,column);
+      }
+    });
+    loggerTable.setDefaultRenderer( Boolean.class, new ComponentRenderer.JCheckBox());
+    loggerTable.setDefaultEditor( Boolean.class, new DefaultCellEditor( new JCheckBox() {
+      public int getHorizontalAlignment() {
+        return this.CENTER;
+      }
+      public int getVerticalAlignment() {
+        return this.CENTER;
+      }
+    }));
+
+    loggerTable.setDefaultRenderer( Level.class, new ComponentRenderer.JComboBox() );
+    loggerTable.setDefaultEditor( Level.class, new DefaultCellEditor( new JComboBox( new Object[] {
+        Level.ALL,
+        Level.DEBUG,
+        Level.ERROR,
+        Level.FATAL,
+        Level.INFO,
+        Level.OFF,
+        Level.WARN
+    })));
+
+    // Button-Aktionen
+    refreshButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        refresh();
+      }
+    });
+    reloadButton.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        reload();
+      }
+    });
+
+    // Componenten in GUI einbinden
+    this.getContentPane().add( reloadButton,                   new GridBagConstraints(0,0,1,1,0.0,0.0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(5,5,5,5),0,0) );
+    this.getContentPane().add( configFileField,                new GridBagConstraints(1,0,1,1,1.0,0.0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(5,5,5,5),0,0) );
+    this.getContentPane().add( new JScrollPane( loggerTable ), new GridBagConstraints(0,1,2,1,1.0,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(5,5,5,5),0,0) );
+    this.getContentPane().add( refreshButton,                  new GridBagConstraints(0,2,1,1,0.0,0.0,GridBagConstraints.EAST,GridBagConstraints.NONE,new Insets(5,5,5,5),0,0) );
+  }
+
+  /**
+   * Aktualisiert die Logger auf Basis der Konfigurationsdatei. Dies entspricht
+   * der Aktion des {@linkplain #reloadButton Reload-Button}.
+   */
+  public void reload() {
+    if ( loggerTableModel != null && configFileField.getValue() != null ) {
+      try {
+        String configFileName = configFileField.getValue().getAbsolutePath();
+        PropertyConfigurator.configure(configFileName);
+        refresh();
+      } catch (Exception err) {
+        ExceptionDialog.show(this,err);
+      }
+    }
+  }
+
+  /**
+   * Aktualisiert die Logger-Tabelle auf Basis der aktuelle geladenen
+   * Log4j-Logger.  Dies entspricht der Aktion des
+   * {@linkplain #refreshButton Refresh-Button}.
+   * @see LoggerConfigurationTableModel#refresh()
+   */
+  public void refresh() {
+    if ( loggerTableModel != null ) {
+      loggerTableModel.refresh();
+    }
+  }
+
+  /**
+   * Liefert einen String, der die {@link Appender} eines Loggers darstellt.
+   * <ul>
+   *   <li>{@link FileAppender} werden durch den Dateinamen dargestellt</li>
+   *   <li>{@link ConsoleAppender} werden durch das Schluesselwort "CONSOLE" dargestellt</li>
+   *   <li>Alle anderen {@link Appender} werden NICHT dargestellt</li>
+   * </ul>
+   * Sub-Klassen koennen diese Methode ueberschreiben, um auch andere
+   * Appender-Arten darzustellen.
+   * @param appenders {@link Appender}-Liste
+   */
+  public String getStringFromAppenders(Enumeration<Appender> appenders) {
+    StringBuffer buffer = new StringBuffer();
+    for(;appenders != null && appenders.hasMoreElements();) {
+      Appender appender = appenders.nextElement();
+      if ( appender instanceof ConsoleAppender )
+        buffer.append("CONSOLE");
+      if ( appender instanceof FileAppender )
+        buffer.append( ((FileAppender)appender).getFile() );
+      if ( appenders.hasMoreElements() )
+        buffer.append("; ");
+    }
+    return buffer.toString();
+  }
+
+//    /**
+//     * Ermittelt  einen String, der die {@link Appender} eines Loggers darstellt.
+//     * {@link FileAppender} werden durch den Dateinamen dargestellt und
+//     * {@link ConsoleAppender} durch das Schluesselwort "CONSOLE".
+//     * @param appenders {@link Appender}-Liste
+//     */
+//    private void setAppendersFromString(String appendersStr, Logger logger) {
+//      logger.removeAllAppenders();
+//      StringTokenizer tok = new StringTokenizer(appendersStr);
+//      for(;tok.hasMoreTokens();) {
+//        String appenderStr = tok.nextToken(" ;\t\n");
+//        Appender appender = null;
+//        if ( appenderStr.equalsIgnoreCase("CONSOLE") )
+//          appender = new ConsoleAppender();
+//        else {
+//          appender = new RollingFileAppender();
+//          ((RollingFileAppender)appender).setFile(appenderStr);
+//        }
+//        logger.addAppender( appender );
+//      }
+//    }
+}

Added: trunk/src/schmitzm/swing/menu/ActionStructure.java
===================================================================
--- trunk/src/schmitzm/swing/menu/ActionStructure.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/menu/ActionStructure.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,58 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.menu;
+
+/**
+ * Diese Klasse repraesentiert eine Kette von Aktionen. Jede Aktion wird
+ * durch einen <code>int</code>-Wert codiert. Alle Aktionen der Kette zusammen
+ * muessen ebenfalls in einem <code>int</code>-Wert codiert werden koennen.
+ * Eine Moeglichkeit ist z.B. die logische Veroderung der einzelnen Aktionen, wenn
+ * jede Einzelaktion durch eine 2er-Potenz codiert wird.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface ActionStructure {
+
+  /**
+   * Aktion, die fuer "keine Aktion" steht.
+   */
+  public static final int ACTION_NONE = 0;
+
+  /**
+   * Liefert das <code>ActionStructure</code>-Element, das in der
+   * Kette unmittelbar uebergeordnet ist.
+   * @return <code>null</code> falls die Aktion die Wurzel-Aktion darstellt
+   */
+  public ActionStructure getParentActionStructure();
+
+  /**
+   * Liefert die Aktion, fuer die dieses Element in der
+   * <code>ActionStructure</code>-Kette steht.
+   */
+  public int getActionCode();
+
+  /**
+   * Liefert einen Wert, der saemtliche Aktionen der
+   * <code>ActionStructure</code>-Kette codiert. z.B. logische Veroderung der
+   * einzelnen Aktionen der <code>ActionStructure</code>-Kette.
+   */
+  public int getCompleteActionCode();
+
+  /**
+   * Prueft, ob zwei Actionscodes gemeinsame Komponenten beinhalten.
+   * @param actionCode1 Aktionscode
+   * @param actionCode2 Aktionscode
+   * @return <code>true</code> gdw. die beiden Gesamt-Aktionen mindestens
+   *         eine gemeinsame Aktion haben.
+   */
+  public boolean compareActions(int actionCode1, int actionCode2);
+}

Added: trunk/src/schmitzm/swing/menu/ObjectMenuItem.java
===================================================================
--- trunk/src/schmitzm/swing/menu/ObjectMenuItem.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/menu/ObjectMenuItem.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,115 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.menu;
+
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+
+/**
+ * Diese Klasse stellt einen Menupunkt dar, der gleichzeitig auch ein
+ * Benutzer-Objekt beinhalten kann.<br>
+ * Jedem Menuepunkt ist ein ActionCode zugeordnet, so dass nach Auswahl des
+ * Menuepunkts eine Aktionskette entsteht, welche in einem einheitlichen
+ * <code>ActionListener</code> interpretiert werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ObjectMenuItem extends JMenuItem implements ActionStructure {
+  private   int               actionCode = 0;
+  private   ActionStructure   parent;
+  private   Object            object;
+
+  /**
+   * Erzeugt einen neuen Menuepunkt.
+   * @param name       Anzeige-Name
+   * @param object     Benutzerobjekt
+   * @param actionCode codiert die Aktion die mit diesem Menue verknuepft ist
+   * @param parent     uebergeordnete Aktion (z.B. uebergeordnetes <code>ObjectSubMenu</code>)
+   */
+  public ObjectMenuItem(String name, Object object, int actionCode, ActionStructure parent) {
+    super(name);
+    this.parent     = parent;
+    this.actionCode = actionCode;
+    this.object     = object;
+  }
+
+  /**
+   * Erzeugt einen neuen Menuepunkt. Diesem ist keine spezielle Aktion
+   * zugeordnet.
+   * @param name    Anzeige-Name
+   * @param object  Benutzerobjekt
+   * @param parent  uebergeordnete Aktion (z.B. uebergeordnetes <code>ObjectSubMenu</code>)
+   * @see schmitzm.swing.menu.ActionStructure#ACTION_NONE
+   */
+  public ObjectMenuItem(String name, Object object, ActionStructure parent) {
+    this(name,object,ACTION_NONE,parent);
+  }
+
+  /**
+   * Erzeugt einen neuen Menuepunkt. Diesem ist kein Objekt zugeordnet, sondern
+   * nur eine Aktion.
+   * @param name       Anzeige-Name
+   * @param listener   Listener, der auf den Menuepunkt horcht
+   * @param actionCode codiert die Aktion die mit diesem Menue verknuepft ist
+   * @param parent     uebergeordnete Aktion (z.B. uebergeordnetes <code>ObjectSubMenu</code>)
+   */
+  public ObjectMenuItem(String name, ActionListener listener, int actionCode, ActionStructure parent) {
+    this(name,(Object)null,actionCode,parent);
+    this.addActionListener(listener);
+  }
+
+
+  /**
+   * Liefert das Benutzer-Objekt des Menupunkts.
+   */
+  public Object getObject() {
+    return object;
+  }
+
+  /**
+   * Liefert die dem Menue uebergeordnete <code>ActionStructure</code>.
+   */
+  public ActionStructure getParentActionStructure() {
+    return parent;
+  }
+
+  /**
+   * Liefert die einzelne Aktion, die diesem Menue zugeordnet ist.
+   */
+  public int getActionCode() {
+    return actionCode;
+  }
+
+  /**
+   * Liefert die gesamte Aktionskette, die diesem Menue zugeordnet ist, in dem
+   * die einzelnen Aktionen der Kette logisch verodert werden.
+   */
+  public int getCompleteActionCode() {
+    if ( parent == null )
+      return getActionCode();
+    return getActionCode() | getParentActionStructure().getCompleteActionCode();
+  }
+
+  /**
+   * Prueft, ob zwei Actionscodes gemeinsame Komponenten beinhalten.
+   * Dies geschiet durch ein logisches UND zwischen den Aktionen, sowie
+   * einem Vergleich, ob das Ergebnis >0 ist.
+   * @param actionCode1 Aktionscode
+   * @param actionCode2 Aktionscode
+   * @return <code>true</code> gdw. die beiden Gesamt-Aktionen mindestens
+   *         eine gemeinsame Aktion haben.
+   */
+  public boolean compareActions(int actionCode1, int actionCode2) {
+    return (actionCode1 & actionCode2) > 0;
+  }
+
+}

Added: trunk/src/schmitzm/swing/menu/ObjectSubMenu.java
===================================================================
--- trunk/src/schmitzm/swing/menu/ObjectSubMenu.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/menu/ObjectSubMenu.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,122 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.menu;
+
+import javax.swing.JMenu;
+import java.awt.event.ActionListener;
+
+/**
+ * Diese Klasse stellt ein Untermenue in einer Menue-Struktur dar.<br>
+ * Jedem Menue ist ein ActionCode zugeordnet, so dass nach Auswahl eines
+ * Menuepunkts eine Aktionskette entsteht, welche in einem einheitlichen
+ * <code>ActionListener</code> interpretiert werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class ObjectSubMenu extends JMenu implements ActionStructure {
+  private    int              actionCode = 0;
+  private    ActionStructure  parent;
+  /** Speichert den (einheitlichen) <code>AktionListener</code>, der fuer
+   *  die Interpretation einer Menue-Auswahl verwendet wird. Dieser wird
+   *  einem <code>MenuItem</code> <b>nicht</b> automatisch zugeordnet!.
+   *  Die Speicherung in dieser abstrakten Oberklasse dient lediglich
+   *  der Handhabbarkeit der Unterklassen. */
+  protected  ActionListener   itemActionListener;
+
+  /**
+   * Erzeugt ein neues Untermenue.
+   * @param name       Anzeige-Name fuer das Untermenue.
+   * @param actionCode codiert die Aktion die mit diesem Menue verknuepft ist
+   * @param parent     uebergeordnete Aktion (z.B. uebergeordnetes <code>ObjectSubMenu</code>)
+   * @param itemActionListener ActionListener, der fuer die Interpretation des
+   *                           ausgewaehlten Menuepunkts herangezogen wird (wird
+   *                           den Menuepunkten <b>nicht</b> automatisch zugeordnet!
+   * @param reorganize bestimmt, ob die Struktur dieses Menues bereits im Konstruktor
+   *                   erzeugt werden soll (siehe {@link #reorganize()}). In
+   *                   vielen Faellen wird diese Funktion manuell durch eine
+   *                   uebergeordnetes Menue ausgeloest, was eine
+   *                   Struktur-Erzeugung bei der Instanziierung ueberfluessig macht.
+   */
+  public ObjectSubMenu(String name, int actionCode, ActionStructure parent, ActionListener itemActionListener, boolean reorganize) {
+    super(name);
+    this.parent     = parent;
+    this.actionCode = actionCode;
+    this.itemActionListener = itemActionListener;
+    if (reorganize)
+      reorganize();
+  }
+
+  /**
+   * Erzeugt ein neues Wurzel-Menue. Diesem ist keine Aktion zugeordnet und
+   * es hat keine Uebergeordneten Menues. In der Regel ist es deshalb fuer
+   * die Interpretation und Verarbeitung der ausgewaehlten Menueaktion
+   * zustaendig (ist also selbst der <code>ActionListener</code>!).
+   * @param name Anzeige-Name fuer das Untermenue.
+   */
+  public ObjectSubMenu(String name) {
+    this(name,ActionStructure.ACTION_NONE,null,null,false);
+  }
+
+  /**
+   * Erstellt die Struktur des Menues neu und ruft <code>reorganize()</code>
+   * fuer alle Untermenues auf.
+   */
+  public void reorganize() {
+    for (int i = 0; i < getItemCount(); i++)
+      if ( getItem(i) instanceof ObjectSubMenu )
+        ((ObjectSubMenu)getItem(i)).reorganize();
+  }
+
+  /**
+   * Liefert den ActionListener, der den Menuepunkten zugeordnet werden kann.
+   */
+  public ActionListener getItemActionListener() {
+    return this.itemActionListener;
+  }
+
+  /**
+   * Liefert die dem Menue uebergeordnete <code>ActionStructure</code>.
+   */
+  public ActionStructure getParentActionStructure() {
+    return parent;
+  }
+
+  /**
+   * Liefert die einzelne Aktion, die diesem Menue zugeordnet ist.
+   */
+  public int getActionCode() {
+    return actionCode;
+  }
+
+  /**
+   * Liefert die gesamte Aktionskette, die diesem Menue zugeordnet ist, in dem
+   * die einzelnen Aktionen der Kette logisch verodert werden.
+   */
+  public int getCompleteActionCode() {
+    if ( parent == null )
+      return getActionCode();
+    return getActionCode() | getParentActionStructure().getCompleteActionCode();
+  }
+
+  /**
+   * Prueft, ob zwei Actionscodes gemeinsame Komponenten beinhalten.
+   * Dies geschiet durch ein logisches UND zwischen den Aktionen, sowie
+   * einem Vergleich, ob das Ergebnis >0 ist.
+   * @param actionCode1 Aktionscode
+   * @param actionCode2 Aktionscode
+   * @return <code>true</code> gdw. die beiden Gesamt-Aktionen mindestens
+   *         eine gemeinsame Aktion haben.
+   */
+  public boolean compareActions(int actionCode1, int actionCode2) {
+    return (actionCode1 & actionCode2) > 0;
+  }
+}

Added: trunk/src/schmitzm/swing/menu/package.html
===================================================================
--- trunk/src/schmitzm/swing/menu/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/menu/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,7 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code javax.swing.menu}.
+	Diese erleichtern die Arbeit mit <code>JMenu</code> und <code>JMenuItem</code>.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/swing/package.html
===================================================================
--- trunk/src/schmitzm/swing/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code javax.swing}.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/swing/resource/cursor/crosshair.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/crosshair.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/gimp-tool-cursors.xcf
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/gimp-tool-cursors.xcf
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/hand_closed.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/hand_closed.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/hand_pan.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/hand_pan.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/zoom.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/zoom.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/zoom_cursor.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/zoom_cursor.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/zoom_in.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/zoom_in.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/cursor/zoom_out.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/cursor/zoom_out.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/large/info.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/large/info.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/large/select_multi.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/large/select_multi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/large/select_normal.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/large/select_normal.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/large/zoom.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/large/zoom.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/Thumbs.db
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/Thumbs.db
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/close_polygon.bmp
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/close_polygon.bmp
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/close_polygon.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/close_polygon.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/info.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/info.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/lasso.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/lasso.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/new.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/new.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/redo.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/redo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_multi.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_multi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_normal.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_normal.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_points.bmp
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_points.bmp
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_points.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_points.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_polgon.bmp
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_polgon.bmp
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/select_polgon.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/select_polgon.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/undo.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/undo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/utilities.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/utilities.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/utilities.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/utilities.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/icons/small/zoom.gif
===================================================================
(Binary files differ)


Property changes on: trunk/src/schmitzm/swing/resource/icons/small/zoom.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle.properties
===================================================================
--- trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,35 @@
+# ---------------------------------------------------------------
+# ------ Default Translations (english) for GUI components ------
+# ------ in Package schmitz.swing                          ------
+# ---------------------------------------------------------------
+
+Ok=Ok
+Cancel=Cancel
+Apply=Apply
+Ready=Ready
+Open=Open
+Close=Close
+Save=Save
+WaitMess=Please wait...
+FileExists=File already exists
+Details=Details...
+Warning=Warning
+Error=Error
+Information=Information
+Class=Class
+Description=Description
+InvalidInputMess=Invalid input
+Refresh=Refresh
+Reload=Reload
+Clear=Clear
+Skip=Skip
+Overwrite=Overwrite
+OverwriteAll=Overwrite all
+Replace=Replace
+CreateDuplicate=Create duplicate
+RememberChoice=Remember this choice
+Rule=Rule
+RuleToolTip=Insert your arithmetical rule here...
+Operators=Operators
+Start=Start
+Calculate=Calculate

Added: trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle_de.properties
===================================================================
--- trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle_de.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/resource/locales/SwingResourceBundle_de.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,32 @@
+# ----------------------------------------------------
+# ------ German Translations for GUI components ------
+# ------ in Package schmitz.swing               ------
+# ----------------------------------------------------
+
+Cancel=Abbrechen
+Apply=Übernehmen
+Ready=Fertig
+Open=Öffnen
+Close=Schliessen
+Save=Speichern
+WaitMess=Bitte warten...
+FileExists=Datei existiert bereits
+Warning=Warnung
+Error=Fehler
+Class=Klasse
+Description=Beschreibung
+InvalidInputMess=Unzulässige Eingabe
+Refresh=Aktualisieren
+Reload=Neu laden
+Clear=Löschen
+Skip=Übergehen
+Overwrite=Überschreiben
+OverwriteAll=Alle überschreiben
+Replace=Ersetzen
+CreateDuplicate=Duplikat erzeugen
+RememberChoice=Immer diese Auswahl treffen
+Rule=Formel
+RuleToolTip=Hier eine arithmetische Formel eingeben...
+Operators=Operatoren
+Start=Start
+Calculate=Rechnen

Added: trunk/src/schmitzm/swing/table/AbstractMutableTableModel.java
===================================================================
--- trunk/src/schmitzm/swing/table/AbstractMutableTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/AbstractMutableTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,35 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.swing.table;
+
+import javax.swing.JTable;
+import javax.swing.table.TableModel;
+
+import schmitzm.swing.table.AbstractTableModel;
+
+/**
+ * Diese Klasse erweitert das {@link AbstractTableModel} um die Methoden
+ * des {@link MutableTableModel}.
+ * @see MutableTable
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractMutableTableModel extends AbstractTableModel implements MutableTableModel {
+  /**
+   * Macht nichts. Wird von {@link MutableTable#setModel(TableModel)}
+   * aufgerufen. Bietet die Moeglichkeit, Tabellenmodell-spezifische
+   * Eigenschaften an der darstellenden Tabelle automatisch durch
+   * das Tabellenmodell zu setzen.
+   * @param table Tabelle, in der das {@link TableModel} dargestellt wird
+   */
+  public void initTable(JTable table) {
+  }
+}

Added: trunk/src/schmitzm/swing/table/AbstractTableModel.java
===================================================================
--- trunk/src/schmitzm/swing/table/AbstractTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/AbstractTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,58 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+package schmitzm.swing.table;
+
+/**
+ * Erweitert das Java {@link javax.swing.table.AbstractTableModel} in dem die
+ * Methoden {@code #getColumnName(int)} und {@link #getColumnCount()} bereits
+ * implementiert sind. Diese werden durch die neue abstrakte Methode
+ * {@link #createColumnNames()} bestimmt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ *
+ */
+public abstract class AbstractTableModel extends javax.swing.table.AbstractTableModel {
+
+  /** Beinhaltet die Spaltennamen.
+   *  @see #createColumnNames() */
+  protected String[] colNames = createColumnNames();
+
+  /**
+   * Liefert die Anzahl an Spalten.
+   * @param col Spaltenindex (beginnend bei 0)
+   */
+  public int getColumnCount() {
+    return colNames.length;
+  }
+
+  /**
+   * Liefert den Namen einer Spalte.
+   * @param col Spaltenindex (beginnend bei 0)
+   */
+  @Override
+  public String getColumnName(int col) {
+    return colNames[col];
+  }
+
+  /**
+   * Liefert die Spaltennamen der Tabelle. Diese Methode liefert direkten
+   * Zugriff auf den Array der Spaltennamen, so dass dessen Inhalt veraendert
+   * werden kann (z.B. fuer Internationalisierung).
+   */
+  public String[] getColumnNames()  {
+    return colNames;
+  }
+
+  /**
+   * Erzeugt die Spaltennamen der Tabelle.
+   */
+  public abstract String[] createColumnNames();
+
+}

Added: trunk/src/schmitzm/swing/table/ColorEditor.java
===================================================================
--- trunk/src/schmitzm/swing/table/ColorEditor.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/ColorEditor.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,103 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.table.TableCellEditor;
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.JTable;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Diese Klasse stellt einen Editor fuer eine Tabellen-Zelle dar, welche
+ * ein {@link Color}-Objekt beinhaltet. Dieser wird durch einen Button
+ * repraesentiert. Wird dieser aktiviert erscheint ein {@link JColorChooser}
+ * ueber den eine neue Farbe ausgewaehlt werden kann.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of  Bonn/Germany)
+ * @version 1.0
+ */
+public class ColorEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
+  private Color         currentColor;
+  private JButton       button;
+  private JColorChooser colorChooser;
+  private JDialog       dialog;
+  protected static final String EDIT = "edit";
+
+  /**
+   * Erzeugt einen neuen Editor.
+   */
+  public ColorEditor() {
+    //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);
+    button.addActionListener(this);
+    button.setBorderPainted(false);
+
+    //Set up the dialog that the button brings up.
+    colorChooser = new JColorChooser();
+    dialog = JColorChooser.createDialog(button,
+                                        "Pick a Color",
+                                        true, //modal
+                                        colorChooser,
+                                        this, //OK button handler
+                                        null); //no CANCEL button handler
+  }
+
+  /**
+   * Verarbeitet den "Klick" auf die Tabellenzelle (Button). Ruft einen
+   * {@link JColorChooser} auf, ueber den eine Farbe ausgewaehlt werden
+   * kann.
+   */
+  public void actionPerformed(ActionEvent e) {
+    if (EDIT.equals(e.getActionCommand())) {
+      //The user has clicked the cell, so
+      //bring up the dialog.
+      button.setBackground(currentColor);
+      colorChooser.setColor(currentColor);
+      dialog.setVisible(true);
+      //Make the renderer reappear.
+      fireEditingStopped();
+    } else {
+      //User pressed dialog's "OK" button.
+      currentColor = colorChooser.getColor();
+    }
+  }
+
+  /**
+   * Liefert die aktuell ausgewaehlte Farbe.
+   * @return <code>Color</code>-Instanz
+   */
+  public Object getCellEditorValue() {
+    return currentColor;
+  }
+
+  /**
+   * Liefert die Komponente, die den Editor repraesentiert.
+   * Hierbei handelt es sich um einen {@link JButton}.
+   */
+  public Component getTableCellEditorComponent(JTable table,
+                                               Object value,
+                                               boolean isSelected,
+                                               int row,
+                                               int column) {
+    currentColor = (Color) value;
+    return button;
+  }
+}

Added: trunk/src/schmitzm/swing/table/ColorRenderer.java
===================================================================
--- trunk/src/schmitzm/swing/table/ColorRenderer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/ColorRenderer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,109 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.table.TableCellRenderer;
+import java.awt.Color;
+import java.awt.Component;
+
+/**
+ * Diese Klasse stellt einen Renderer fuer Tabellenzellen dar, in denen
+ * eine Farbe (also eine <code>Color</code>-Instanz) dargestellt werden soll.<br>
+ * Die Farbe wird durch ein {@link JLabel} in entsprechender Hintergrundfarbe
+ * dargestellt.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ColorRenderer extends JLabel implements TableCellRenderer {
+    private   Border  unselectedBorder = null;
+    private   Border  selectedBorder   = null;
+    /** Speichert, ob die Zelle einen inneren Rand erhaelt */
+    protected boolean isBordered = true;
+
+  /**
+   * Erzeugt einen neuen Zellen-Renderer.
+   * @param isBordered gibt an ob die Zelle komplett ausgefuellt wird, oder
+   *                   ein innerer Rand erzeugt wird
+   */
+  public ColorRenderer(boolean isBordered) {
+    this.isBordered = isBordered;
+    setOpaque(true);
+  }
+
+  /**
+   * Liefert einen {@link JLabel}, das die in der Tabellenzelle enthaltene
+   * Farbe darstellt.
+   * @param table      Tabelle in der die Zelle liegt
+   * @param color      Farbe, die in der Zelle darzustellen ist (<b>muss eine
+   *                   Instanz von <code>Color</code> sein!!</b>)
+   * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+   * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+   * @param row        Tabellenzeile der Zelle
+   * @param column     Tabellenspalte der Zelle
+   * @return ein farbiges {@link JLabel}
+   */
+  public Component getTableCellRendererComponent(
+                               JTable table,
+                               Object color,
+                               boolean isSelected,
+                               boolean hasFocus,
+                               int row,
+                               int column) {
+    // Randarten erzeugen, falls dies noch nicht geschehen ist
+    if (selectedBorder == null) {
+      selectedBorder = BorderFactory.createMatteBorder(
+          2, 5, 2, 5,
+          table.getSelectionBackground()
+      );
+    }
+    if (unselectedBorder == null) {
+      unselectedBorder = BorderFactory.createMatteBorder(
+          2, 5, 2, 5,
+          table.getBackground()
+      );
+    }
+    // Rand setzen
+    if (isBordered) {
+      if (isSelected)
+        setBorder(selectedBorder);
+      else
+        setBorder(unselectedBorder);
+    }
+    // Label erzeugen/initialisieren
+    return createColorLabel((Color)color,this);
+  }
+
+  /**
+   * Liefert einen {@link JLabel}, das eine Farbe darstellt. Ist der uebergebene
+   * <code>label</code>-Parameter <code>null</code> wird eine neue
+   * {@link JLabel}-Instanz erzeugt.
+   * @param color Farbe, die durch das Label dargestellt wird
+   * @param label Label, das entsprechend der Farbe umformatiert (kann
+   *              <code>null</code> sein)
+   */
+  public static JLabel createColorLabel(Color color, JLabel label) {
+    if (label == null)
+      label = new JLabel();
+
+    label.setOpaque(true);
+    label.setBackground(color);
+    if ( color != null )
+      label.setToolTipText("RGB value: " + color.getRed() + ", "
+                           + color.getGreen() + ", "
+                           + color.getBlue());
+    return label;
+  }
+}

Added: trunk/src/schmitzm/swing/table/ColorsRenderer.java
===================================================================
--- trunk/src/schmitzm/swing/table/ColorsRenderer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/ColorsRenderer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,83 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import javax.swing.JTable;
+import javax.swing.BorderFactory;
+import javax.swing.border.Border;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.event.MouseEvent;
+// nur fuer Doku
+import javax.swing.JLabel;
+import javax.swing.JComponent;
+
+/**
+ * Diese Klasse stellt einen Renderer fuer Tabellenzellen dar, in denen
+ * mehrere Farben (also ein <code>Color[]</code>) dargestellt werden sollen.<br>
+ * Jede Farbe wird durch ein {@link JLabel} in entsprechender Hintergrundfarbe
+ * dargestellt. Die Label werden horizontal angeordnet.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ColorsRenderer extends ColorRenderer {
+  // Rand um Farb-Label
+  private Border labelBorder = BorderFactory.createLineBorder(Color.BLACK);
+
+  /**
+   * Erzeugt einen neuen Zellen-Renderer.
+   * @param isBordered gibt an ob die Zelle komplett ausgefuellt wird, oder
+   *                   ein Rand erzeugt wird
+   */
+  public ColorsRenderer(boolean isBordered) {
+    super(isBordered);
+    this.setLayout( new GridBagLayout() );
+  }
+
+  /**
+   * Liefert einen Container, in dem fuer jede Farbe ein {@link JLabel}
+   * enthalten ist.
+   * @param table      Tabelle in der die Zelle liegt
+   * @param object     Farben, die in der Zelle darzustellen sind (<b>muss eine
+   *                   Instanz von <code>Color[]</code> sein!!</b>)
+   * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+   * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+   * @param row        Tabellenzeile der Zelle
+   * @param column     Tabellenspalte der Zelle
+   * @return eine {@link JComponent} mit horizontal angeordneten {@link JLabel}s
+   */
+  public Component getTableCellRendererComponent(
+                               JTable table,
+                               Object object,
+                               boolean isSelected,
+                               boolean hasFocus,
+                               int row,
+                               int column) {
+    // Methode der Oberklasse (mit Dummy-Farbe) zum erzeugen/setzen
+    // des Rands
+    super.getTableCellRendererComponent(table,null,isSelected,hasFocus,row,column);
+
+    Color[] color = (Color[]) object;
+    // Fuer jede Farbe ein Label erzeugen
+    this.removeAll();
+    for (int i=0; i<color.length; i++) {
+      JLabel label = ColorRenderer.createColorLabel(color[i],null);
+      if ( isBordered )
+        label.setBorder( labelBorder );
+      add(label, new GridBagConstraints(i,0,1,1,1,1.0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0));
+    }
+    return this;
+  }
+}

Added: trunk/src/schmitzm/swing/table/ComponentRenderer.java
===================================================================
--- trunk/src/schmitzm/swing/table/ComponentRenderer.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/ComponentRenderer.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,284 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import java.awt.Component;
+import java.awt.Color;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.AbstractButton;
+import javax.swing.SwingConstants;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Diese Klasse stellt einen Tabellen-Renderer fuer jede Art von
+ * {@link Component}-Objekten dar. Um z.B. einen Button oder eine
+ * Checkbox in einer {@link JTable}-Zelle darzustellen, muss mittels der
+ * Methode {@link JTable#setDefaultRenderer(Class,TableCellRenderer)} dieser
+ * Renderer fuer die entsprechende Zellwert-Klasse zugeordnet werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class ComponentRenderer implements TableCellRenderer, SwingConstants {
+  /** Horizontale Ausrichtung der Componente innerhalb der Tabellenzelle
+   *  (Default: {@link SwingConstants#CENTER}). */
+  protected int horAlign = CENTER;
+  /** Vertikale Ausrichtung der Componente innerhalb der Tabellenzelle
+   *  (Default: {@link SwingConstants#CENTER}). */
+  protected int vertAlign = CENTER;
+
+  /**
+   * Erzeugt einen neuen Zellen-Renderer. <b>Die Ausrichtung ist nur relevant
+   * fuer Komponenten vom Typ {@link AbstractButton}!</b>
+   * @param horAlign horizontale Ausrichtung der Komponente innerhalb der
+   *                 Tabellenzelle (RIGHT, LEFT, CENTER, LEADING, TRAILING aus
+   *                 {@link SwingConstants}; Default = CENTER)
+   * @param vertAlign vertikale Ausrichtung der Komponente innerhalb der
+   *                  Tabellenzelle (CENTER, TOP, BOTTOM aus
+   *                  {@link SwingConstants}; Default = CENTER)
+   */
+  public ComponentRenderer(int horAlign, int vertAlign) {
+    super();
+    this.horAlign = horAlign;
+    this.vertAlign = vertAlign;
+  }
+
+  /**
+   * Erzeugt einen neuen Zellen-Renderer.
+   */
+  public ComponentRenderer() {
+    this(CENTER,CENTER);
+  }
+
+  /**
+   * Liefert die horizontale Ausrichtung der Komponente innerhalb der
+   * Tabellenzelle.
+   * @return RIGHT, LEFT, CENTER, LEADING oder TRAILING aus {@link SwingConstants}; Default = CENTER
+   */
+  public int getHorizontalAlignment() {
+    return horAlign;
+  }
+
+  /**
+   * Setzt die horizontale Ausrichtung der Komponente innerhalb der
+   * Tabellenzelle. Wirkt sich nur auf Komponenten vom Typ
+   * {@link AbstractButton} aus!
+   * @param horAlign (RIGHT, LEFT, CENTER, LEADING oder TRAILING aus {@link SwingConstants})
+   */
+  public void setHorizontalAlignment(int horAlign) {
+    this.horAlign = horAlign;
+  }
+
+  /**
+   * Liefert die vertikale Ausrichtung der Komponente innerhalb der
+   * Tabellenzelle.
+   * @return CENTER, TOP oder BOTTOM aus {@link SwingConstants}; Default = CENTER
+   */
+  public int getVerticalAlignment() {
+    return vertAlign;
+  }
+
+  /**
+   * Setzt die vertikale Ausrichtung der Komponente innerhalb der
+   * Tabellenzelle. Wirkt sich nur auf Komponenten vom Typ
+   * {@link AbstractButton} aus!
+   * @param vertAlign (CENTER, TOP oder BOTTOM aus {@link SwingConstants})
+   */
+  public void setVerticalAlignment(int vertAlign) {
+    this.vertAlign = vertAlign;
+  }
+
+  /**
+   * Die von der Methode {@link #createRendererComponent(JTable,Object,boolean,boolean,int,int)}
+   * gelieferte Komponente wird farblich hintelegt, wenn die Tabellenzeile
+   * selektiert ist.
+   * @param table      Tabelle in der die Zelle liegt
+   * @param value      Zu rendernder Wert
+   * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+   * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+   * @param row        Tabellenzeile der Zelle
+   * @param column     Tabellenspalte der Zelle
+   */
+  public Component getTableCellRendererComponent(
+                               JTable table,
+                               Object value,
+                               boolean isSelected,
+                               boolean hasFocus,
+                               int row,
+                               int column) {
+    Component rendComp = createRendererComponent(table,value,isSelected,hasFocus,row,column);
+    if ( rendComp instanceof AbstractButton ) {
+      ((AbstractButton)rendComp).setHorizontalAlignment(horAlign);
+      ((AbstractButton)rendComp).setVerticalAlignment(vertAlign);
+    }
+    if ( isSelected ) {
+      rendComp.setBackground(table.getSelectionBackground());
+      rendComp.setForeground(table.getSelectionForeground());
+    }
+    return rendComp;
+  }
+
+  /**
+   * Liefert einfach das zu {@link Component} gecastete Objekt {@code value} zurueck.
+   * Handelt es sich bei <code>comp</code> nicht um ein {@link Component}-Objekt, wird
+   * der {@link DefaultTableCellRenderer} herangezogen.
+   * @param table      Tabelle in der die Zelle liegt
+   * @param value      Zu rendernde Komponente (<b>muss eine
+   *                   Instanz von <code>Component</code> sein!!</b>)
+   * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+   * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+   * @param row        Tabellenzeile der Zelle
+   * @param column     Tabellenspalte der Zelle
+   */
+  protected Component createRendererComponent( JTable table,
+                                               Object value,
+                                               boolean isSelected,
+                                               boolean hasFocus,
+                                               int row,
+                                               int column) {
+
+    if ( !(value instanceof Component) )
+      return new DefaultTableCellRenderer().getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
+    return (Component)value;
+  }
+
+  /**
+   * Dieser Renderer stellt das jeweilige Objekt in einem {@link javax.swing.JTextField} dar.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class JTextField extends ComponentRenderer {
+    /**
+     * Erzeugt einen neuen Zellen-Renderer in Form einer {@link JCheckBox}.
+     */
+    public JTextField() {
+      super();
+    }
+
+    /**
+     * Liefert ein {@link javax.swing.JTextField}, das als Inhalt den Wert der
+     * Tabellenzelle hat.
+     * @param table      Tabelle in der die Zelle liegt
+     * @param value      Zu renderndes Objekt
+     * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+     * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+     * @param row        Tabellenzeile der Zelle
+     * @param column     Tabellenspalte der Zelle
+     */
+    public Component createRendererComponent( JTable table,
+                                              Object value,
+                                              boolean isSelected,
+                                              boolean hasFocus,
+                                              int row,
+                                              int column) {
+      return new javax.swing.JTextField(value != null ? value.toString() : "");
+    }
+  }
+
+  /**
+   * Dieser Renderer stellt das jeweilige Objekt in einer {@link javax.swing.JCheckBox} dar.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class JCheckBox extends ComponentRenderer  {
+    /**
+     * Erzeugt einen neuen Zellen-Renderer in Form einer {@link JCheckBox}.
+     * @param horAlign horizontale Ausrichtung der Checkbox innerhalb der
+     *                 Tabellenzelle (RIGHT, LEFT, CENTER, LEADING, TRAILING aus
+     *                 {@link SwingConstants}; Default = CENTER)
+     * @param vertAlign vertikale Ausrichtung der Checkbox innerhalb der
+     *                  Tabellenzelle (CENTER, TOP, BOTTOM aus
+     *                  {@link SwingConstants}; Default = CENTER)
+     */
+    public JCheckBox(int horAlign, int vertAlign) {
+      super(horAlign, vertAlign);
+    }
+
+    /**
+     * Erzeugt einen neuen Zellen-Renderer in Form einer {@link JCheckBox}.
+     */
+    public JCheckBox() {
+      this(CENTER, CENTER);
+    }
+
+    /**
+     * Liefert eine {@link javax.swing.JCheckBox}, die aktiviert ist, wenn
+     * es sich bei dem darzustellenden Objekt um einen {@link Boolean} handelt,
+     * der den Wert {@code true} hat.
+     * @param table      Tabelle in der die Zelle liegt
+     * @param value      Zu renderndes Objekt
+     * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+     * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+     * @param row        Tabellenzeile der Zelle
+     * @param column     Tabellenspalte der Zelle
+     */
+    public Component createRendererComponent( JTable table,
+                                              Object value,
+                                              boolean isSelected,
+                                              boolean hasFocus,
+                                              int row,
+                                              int column) {
+      return new javax.swing.JCheckBox("", value instanceof Boolean && value != null ? (Boolean)value : false);
+    }
+  }
+
+  /**
+   * Dieser Renderer stellt das jeweilige Objekt in Form einer
+   * {@link javax.swing.JComboBox} dar.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  public static class JComboBox extends ComponentRenderer {
+    /**
+     * Erzeugt einen neuen Zellen-Renderer in Form einer {@link JComboBox}.
+     * @param horAlign horizontale Ausrichtung der Combobox innerhalb der
+     *                 Tabellenzelle (RIGHT, LEFT, CENTER, LEADING, TRAILING aus
+     *                 {@link SwingConstants}; Default = CENTER)
+     * @param vertAlign vertikale Ausrichtung der Combobox innerhalb der
+     *                  Tabellenzelle (CENTER, TOP, BOTTOM aus
+     *                  {@link SwingConstants}; Default = CENTER)
+     */
+    public JComboBox(int horAlign, int vertAlign) {
+      super(horAlign, vertAlign);
+    }
+
+    /**
+     * Erzeugt einen neuen Zellen-Renderer in Form einer {@link JComboBox}.
+     */
+    public JComboBox() {
+      this(CENTER, CENTER);
+    }
+
+    /**
+     * Liefert eine {@link javax.swing.JComboBox}, die als Inhalt den Wert der
+     * Tabellenzelle hat.
+     * @param table      Tabelle in der die Zelle liegt
+     * @param value      Zu renderndes Objekt
+     * @param isSelected gibt an ob die Zelle aktuell selektiert ist
+     * @param hasFocus   gibt an ob die Zelle aktuell den Fokus besitzt
+     * @param row        Tabellenzeile der Zelle
+     * @param column     Tabellenspalte der Zelle
+     */
+    public Component createRendererComponent( JTable table,
+                                              Object value,
+                                              boolean isSelected,
+                                              boolean hasFocus,
+                                              int row,
+                                              int column) {
+      return new javax.swing.JComboBox( new Object[] {value} );
+    }
+  }
+
+}

Added: trunk/src/schmitzm/swing/table/MutableTable.java
===================================================================
--- trunk/src/schmitzm/swing/table/MutableTable.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/MutableTable.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,348 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import javax.swing.JTable;
+import javax.swing.JScrollPane;
+import javax.swing.JMenu;
+import javax.swing.JPopupMenu;
+import javax.swing.JMenuItem;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.event.MenuEvent;
+
+import schmitzm.swing.event.PopupMenuListener;
+
+// nur fuer Doku
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+
+
+/**
+ * Diese Klasse stellt eine veraenderbare Tabelle dar. Sie erhaelt ein
+ * Kontextmenue, ueber das der Tabelle Datensaetze hinzugefuegt, entfernt oder
+ * geaendert werden koennen. Zudem reagiert die Tabelle automatisch auf einen
+ * Doppelklick mit der Aenderungsaktion.
+ * @see MutableTableModel
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class MutableTable extends JTable {
+  /** Konstante fuer den Menuepunkt "Hinzufuegen". */
+  public static final int ITEM_ADD    = 2;
+  /** Konstante fuer den Menuepunkt "Loeschen". */
+  public static final int ITEM_REMOVE = 4;
+  /** Konstante fuer den Menuepunkt "Aendern". */
+  public static final int ITEM_CHANGE = 8;
+  /** Konstante, die alle Menuepunkte beinhaltet. */
+  public static final int ITEM_ALL    = ITEM_ADD | ITEM_REMOVE | ITEM_CHANGE;
+
+  /** Speichert die Datenbasis fuer die Tabelle */
+  protected MutableTableModel model = null;
+
+  /** Speichert, welche Menuepunkte fuer das Kontextmenue aktiv sind */
+  protected int mask = 0;
+
+  /**
+   * Erzeugt eine neue Tabelle.
+   * @param model Datenbasis fuer die Tabelle
+   * @param mask  Definiert die Eintraege im Kontextmenue (logische
+   *              ODER-Verknuepfung der Konstanten <code>ITEM_ADD</code>,
+   *              <code>ITEM_REMOVE</code> und <code>ITEM_CHANGE</code>)
+   * @see #ITEM_ADD
+   * @see #ITEM_REMOVE
+   * @see #ITEM_CHANGE
+   */
+  public MutableTable(MutableTableModel model, int mask) {
+    super(model);
+    this.mask  = mask;
+    setModel(model);
+
+    // MouseListener fuer PopupMenu
+    this.addMouseListener( createPopupMenuListener(mask) );
+    getTableHeader().addMouseListener( createPopupMenuListener(mask) );
+
+    // MouseListener fuer Doppelklick
+    this.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON1 &&
+            e.getClickCount() == 2)
+          performDoubleClick();
+      }
+    });
+  }
+
+  /**
+   * Erzeugt eine neue Tabelle mit komplettem Kontextmenue.
+   * @param model Datenbasis fuer die Tabelle
+   * @see #ITEM_ALL
+   */
+  public MutableTable(MutableTableModel model) {
+    this(model,ITEM_ALL);
+  }
+
+  /**
+   * Setzt das Datenmodell fuer die Tabelle und ruft {@link MutableTableModel#initTable(JTable)}
+   * auf.
+   * @exception UnsupportedOperationException falls kein {@link MutableTableModel}
+   *            uebergeben wird
+   */
+  @Override
+  public void setModel(TableModel model) {
+    if ( !(model instanceof MutableTableModel) )
+      throw new UnsupportedOperationException("MutableTableModel expected for MutableTable!");
+    super.setModel(model);
+    this.model = (MutableTableModel)model;
+    this.model.initTable(this);
+  }
+
+  /**
+   * Erzeugt einen {@link MouseListener} der bei einem Popup-Signal (z.B.
+   * Rechtsklick) das Tabellen-Menue oeffnet. In diesem sind alle Menuepunkte
+   * (Aendern, Loeschen, Hinzufuegen) enthalten.
+   * @see #createPopupMenuListener(int)
+   * @see #ITEM_ALL
+   */
+  public MouseListener createPopupMenuListener() {
+    return createPopupMenuListener(ITEM_ALL);
+  }
+
+  /**
+   * Erzeugt einen {@link MouseListener} der bei einem Popup-Signal (z.B.
+   * Rechtsklick) das Tabellen-Kontextmenue oeffnet.
+   * @param mask  Definiert die Eintraege im Kontextmenue (logische
+   *              ODER-Verknuepfung der Konstanten <code>ITEM_ADD</code>,
+   *              <code>ITEM_REMOVE</code> und <code>ITEM_CHANGE</code>)
+   * @see #ITEM_ADD
+   * @see #ITEM_REMOVE
+   * @see #ITEM_CHANGE
+   */
+  public MouseListener createPopupMenuListener(int mask) {
+    return new PopupMenuListener( createPopupMenu(mask).getPopupMenu() );
+  }
+
+  /**
+   * Erzeugt eine Instanz des {@link PopupMenu}. Unterklassen koennen diese
+   * Methode ueberschreiben, um das Menu zu erweitern.
+   * @param mask  Definiert die Eintraege im Kontextmenue (logische
+   *              ODER-Verknuepfung der Konstanten <code>ITEM_ADD</code>,
+   *              <code>ITEM_REMOVE</code> und <code>ITEM_CHANGE</code>)
+   * @see #ITEM_ADD
+   * @see #ITEM_REMOVE
+   * @see #ITEM_CHANGE
+   */
+  protected PopupMenu createPopupMenu(int mask) {
+    return new PopupMenu(this,mask);
+  }
+
+  /**
+   * Liefert die Bit-Maske, welche Punkt im Kontextmenue aktiv sind.
+   * @see #ITEM_ADD
+   * @see #ITEM_REMOVE
+   * @see #ITEM_CHANGE
+   * @see #ITEM_ALL
+   */
+  public int getMask() {
+    return mask;
+  }
+
+  /**
+   * Wird aufgerufen, wenn der Menuepunkt "Ändern" gewaehlt wird.
+   * Ruft fuer jede selektierte Zelle {@link MutableTableModel#performChangeData(int,int) MutableTableModel#performChangeData(..)}
+   * auf. Ist nur eine zeilenweise Auswahl in der Tabelle erlaubt, so
+   * wird die obige Methode nur fuer jede selektierte Zeile aufgerufen.
+   * Der Aufrufparameter fuer die Spalte ist in diesem Fall die Spalte, auf die geklickt
+   * wurde.<br>
+   * Erwirkt anschliessend ein {@link AbstractTableModel#fireTableDataChanged()}
+   * @see #setRowSelectionAllowed(boolean)
+   */
+  protected void performChange() {
+    int selCol[] = this.getSelectedColumns();
+    int selRow[] = this.getSelectedRows();
+
+    for (int r=0; r<selRow.length; r++) {
+      // werden Zellen einzeln selektiert, wird fuer jede Zelle
+      // einzeln die Aenderungsaktion ausgefuehrt, sonst nur einmal
+      // pro Zeile
+      if ( !this.rowSelectionAllowed )
+        for (int c=0; c<selCol.length; c++)
+          model.performChangeData(selRow[r],selCol[c]);
+      else
+        // Als Spalte wird diejenige angegeben, auf die geklickt wurde
+        model.performChangeData(selRow[r], this.getSelectedColumn());
+    }
+    model.fireTableDataChanged();
+  }
+
+  /**
+   * Wird aufgerufen, wenn der Menuepunkt "Löschen" gewaehlt wird.
+   * Ruft fuer jede selektierte Zeile {@link MutableTableModel#performRemoveRow(int) MutableTableModel#performRemoveRow(..)}
+   * auf. Dabei wird <b>rueckwaerts</b> vorgegangen - also die letzte selektierte
+   * Zeile zuerst -, damit der Zeilenindex ueber alle
+   * {@link MutableTableModel#performRemoveRow(int)}-Aufrufe konsistent bleibt.
+   * Erwirkt anschliessend automatisch ein {@link AbstractTableModel#fireTableDataChanged()}
+
+   */
+  protected void performRemove() {
+    int selRow[] = this.getSelectedRows();
+    for (int r=selRow.length-1; r>=0; r--)
+      model.performRemoveRow(selRow[r]);
+    model.fireTableDataChanged();
+  }
+
+  /**
+   * Wird aufgerufen, wenn der Menuepunkt "Hinzufügen" gewaehlt wird.
+   * Ruft {@link MutableTableModel#performAddRow() MutableTableModel#performAddRow()}
+   * auf.<br>
+   * Erwirkt anschliessend automatisch ein {@link AbstractTableModel#fireTableDataChanged()}
+   */
+  protected void performAdd() {
+    model.performAddRow();
+    model.fireTableDataChanged();
+  }
+
+  /**
+   * Wird aufgerufen, wenn ein Doppelklick auf die Tabelle vorgenommen wird.
+   * Standardmaessig wird {@link #performChange()} aufgerufen (sofern
+   * der Aendern-Menuepunkt aktiv ist!).
+   * Unterklassen koennen diese Methode ueberschreiben, um diesem Ereignis
+   * eins andere Aktion zuzuweisen oder das Ereignis gaenzlich zu unterdruecken.
+   */
+  public void performDoubleClick() {
+    if ( (mask & ITEM_CHANGE) > 0 )
+      performChange();
+  }
+
+  /**
+   * Erzeugt ein {@link JScrollPane} fuer die Tabelle. Dieses erhaelt automatisch
+   * den gleichen ToolTip-Text und Popupmenue-Listener wie die Tabelle.
+   * Der Anwender kann somit auch auf das ScrollPane klicken, um die Tabellen-Optionen
+   * anzuwaehlen.
+   */
+  public JScrollPane createScrollPane() {
+    JScrollPane scrollPane = new JScrollPane( this );
+    scrollPane.setToolTipText( this.getToolTipText() );
+    scrollPane.addMouseListener( this.createPopupMenuListener( this.getMask() ) );
+    return scrollPane;
+  }
+
+  /**
+   * Diese Klasse stellt das Kontextmenue fuer einen {@link MutableTable}
+   * dar.
+   * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+   * @version 1.0
+   */
+  protected class PopupMenu extends JMenu {
+    /** Menueeintrag fuer Aendern-Aktion */
+    protected JMenuItem changeItem = new JMenuItem("Ändern");
+    /** Menueeintrag fuer Entfernen-Aktion */
+    protected JMenuItem removeItem = new JMenuItem("Entfernen");
+    /** Menueeintrag fuer Hinzufuegen-Aktion */
+    protected JMenuItem addItem = new JMenuItem("Hinzufügen");
+
+    private MutableTable table = null;
+
+    /**
+     * Erzeugt ein neues Kontextmenue.
+     * @param table Tabelle auf die sich die Aktionen des Menus beziehen
+     * @param mask  Definiert die Eintraege im Kontextmenue (logische
+     *              ODER-Verknuepfung der Konstanten <code>ITEM_ADD</code>,
+     *              <code>ITEM_REMOVE</code> und <code>ITEM_CHANGE</code>)
+     * @see #ITEM_ADD
+     * @see #ITEM_REMOVE
+     * @see #ITEM_CHANGE
+     */
+    public PopupMenu(final MutableTable table, int mask) {
+      super();
+      this.table = table;
+      changeItem.addActionListener( new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          table.performChange();
+        }
+      });
+      addItem.addActionListener( new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          table.performAdd();
+        }
+      });
+      removeItem.addActionListener( new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          table.performRemove();
+        }
+      });
+
+      // Menue-Punkte hinzufuegen
+      if ( (mask & ITEM_CHANGE) > 0 )
+        this.add( changeItem );
+      if ( (mask & ITEM_ADD) > 0 )
+        this.add( addItem );
+      if ( (mask & ITEM_REMOVE) > 0 )
+        this.add( removeItem );
+
+      this.addMenuListener( new MenuListener() {
+        public void menuCanceled(MenuEvent e) {}
+        public void menuDeselected(MenuEvent e) {}
+        public void menuSelected(MenuEvent e) {
+          setEnabled(ITEM_REMOVE,table.getSelectedRowCount() > 0);
+          setEnabled(ITEM_CHANGE,table.getSelectedRowCount() > 0);
+        }
+
+      });
+    }
+
+    /**
+     * Erzeugt ein komplettes Kontextmenue mit allen moeglichen Aktionen.
+     */
+    public PopupMenu(final MutableTable table) {
+      this(table,ITEM_ALL);
+    }
+
+    /**
+     * (De)aktiviert ein oder mehrere Menueintraege.
+     * @param mask definiert die betroffenen Eintraege (logische
+     *              ODER-Verknuepfung der Konstanten <code>ITEM_ADD</code>,
+     *              <code>ITEM_REMOVE</code> und <code>ITEM_CHANGE</code>)
+     * @param enabled bestimmt ob der Eintrag aktiviert oder deaktiviert wird
+     */
+    public void setEnabled(int mask, boolean enabled) {
+      if ( (mask & ITEM_CHANGE) > 0 )
+        changeItem.setEnabled( enabled );
+      if ( (mask & ITEM_ADD) > 0 )
+        addItem.setEnabled( enabled );
+      if ( (mask & ITEM_REMOVE) > 0 )
+        removeItem.setEnabled( enabled );
+    }
+
+    /**
+     * Erzeugt ein dem Menue entsprechendes {@link JPopupMenu}.
+     */
+    public JPopupMenu getPopupMenu() {
+      JPopupMenu menu = super.getPopupMenu();
+      menu.addPopupMenuListener( new javax.swing.event.PopupMenuListener() {
+        public void popupMenuCanceled(javax.swing.event.PopupMenuEvent e) {}
+        public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent e) {}
+        public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent e) {
+          setEnabled(ITEM_REMOVE,table.getSelectedRowCount() > 0);
+          setEnabled(ITEM_CHANGE,table.getSelectedRowCount() > 0);
+        }
+      });
+      return menu;
+    }
+
+  }
+
+}

Added: trunk/src/schmitzm/swing/table/MutableTableModel.java
===================================================================
--- trunk/src/schmitzm/swing/table/MutableTableModel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/MutableTableModel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,70 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+
+/**
+ * Diese Klasse erweitert das {@link TableModel} um die 3
+ * Aktionen
+ * <ul>
+ * <li>Hinzufuegen einer Tabellenzeile</li>
+ * <li>Loeschen einer Tabellenzeile</li>
+ * <li>Aendern einer Tabellenzelle</li>
+ * </ul>
+ * Diese Aktionsaufforderungen koennen somit von ausserhalb (z.B. durch ein Menue)
+ * an das TableModel herangetragen werden.
+ * @see MutableTable
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public interface MutableTableModel extends TableModel {
+  /**
+   * Fordert das TableModel auf, eine bestimmte Zeile zu loeschen.
+   * @param row Zeilennummer
+   */
+  public void performRemoveRow(int row);
+
+  /**
+   * Fordert das TableModel auf, eine Zeile hinzuzufuegen. Das TableModel ist
+   * dafuer verwantwortlich, die benoetigten Daten zu ermitteln (z.B. ueber
+   * einen Anwender-Dialog).
+   */
+  public void performAddRow();
+
+  /**
+   * Fordert das TableModel auf, eine Tabellenzelle zu aendern. Das TableModel ist
+   * dafuer verwantwortlich, die benoetigten Daten zu ermitteln (z.B. ueber
+   * einen Anwender-Dialog).
+   * @param row Zeilennummer
+   * @param col Spalzennummer
+   */
+  public void performChangeData(int row, int col);
+
+  /**
+   * Initiert ein Neu-Aufbauen der Tabelle.
+   */
+  public void fireTableDataChanged();
+
+  /**
+   * Wird von {@link MutableTable#setModel(TableModel)}
+   * aufgerufen. Bietet die Moeglichkeit, Tabellenmodell-spezifische
+   * Eigenschaften an der darstellenden Tabelle automatisch durch
+   * das Tabellenmodell zu setzen.
+   * @param table Tabelle, in der das {@link TableModel} dargestellt wird
+   */
+  public void initTable(JTable table);
+
+}

Added: trunk/src/schmitzm/swing/table/TableComponentMouseListener.java
===================================================================
--- trunk/src/schmitzm/swing/table/TableComponentMouseListener.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/TableComponentMouseListener.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,103 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.table;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.Component;
+import javax.swing.JTable;
+import javax.swing.JButton;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * Dieser MouseListener schleust Maus-Ereignisse auf eine Tabellenzelle
+ * an die darin dargestellten {@link Component}-Objekte (z.B. einen Button) durch.
+ * Hierzu muss der {@link JTable} eine Instanz dieses Listeners hinzugefuegt werden.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class TableComponentMouseListener implements MouseListener {
+  private JTable table;
+
+  /**
+   * Erzeugt einen neuen Listener fuer eine Tabelle.
+   * @param table Tabelle fuer die die Maus-Ereignisse durchgeschleust werden
+   */
+  public TableComponentMouseListener(JTable table) {
+    this.table = table;
+  }
+
+  private void forwardEventToButton(MouseEvent e) {
+    TableColumnModel columnModel = table.getColumnModel();
+
+    // Feststellen, auf welche Zelle geklickt wurde
+    int column = columnModel.getColumnIndexAtX(e.getX());
+    int row    = e.getY() / table.getRowHeight();
+    if( row    >= table.getRowCount()    | row < 0 ||
+        column >= table.getColumnCount() || column < 0)
+      return;
+    Object value = table.getValueAt(row, column);
+
+    // nichts tun, wenn es sich nicht um eine Componente handelt
+    if(!(value instanceof Component))
+      return;
+    Component comp = (Component)value;
+
+    MouseEvent buttonEvent = (MouseEvent)SwingUtilities.convertMouseEvent(table, e, comp);
+    comp.dispatchEvent(buttonEvent);
+    // This is necessary so that when a button is pressed and released
+    // it gets rendered properly.  Otherwise, the button may still appear
+    // pressed down when it has been released.
+    table.repaint();
+  }
+
+  /**
+   * Leitet das MouseClicked-Ereignis an die {@link Component} weiter.
+   * @param e MouseEvent
+   */
+  public void mouseClicked(MouseEvent e) {
+    forwardEventToButton(e);
+  }
+
+  /**
+   * Leitet das MouseEntered-Ereignis an die {@link Component} weiter.
+   * @param e MouseEvent
+   */
+  public void mouseEntered(MouseEvent e) {
+    forwardEventToButton(e);
+  }
+
+  /**
+   * Leitet das MouseExited-Ereignis an die {@link Component} weiter.
+   * @param e MouseEvent
+   */
+  public void mouseExited(MouseEvent e) {
+    forwardEventToButton(e);
+  }
+
+  /**
+   * Leitet das MousePressed-Ereignis an die {@link Component} weiter.
+   * @param e MouseEvent
+   */
+  public void mousePressed(MouseEvent e) {
+    forwardEventToButton(e);
+  }
+
+  /**
+   * Leitet das MouseReleased-Ereignis an die {@link Component} weiter.
+   * @param e MouseEvent
+   */
+  public void mouseReleased(MouseEvent e) {
+    forwardEventToButton(e);
+  }
+}

Added: trunk/src/schmitzm/swing/table/package.html
===================================================================
--- trunk/src/schmitzm/swing/table/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/table/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,6 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code javax.swing.table}.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/swing/tree/ContentNode.java
===================================================================
--- trunk/src/schmitzm/swing/tree/ContentNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/tree/ContentNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,72 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.tree;
+
+/**
+ * Diese Klasse stellt Knoten dar, ein Objekt beinhaltet. Sie ist als
+ * Pondon zu {@link EmptyNode} entworfen, implementiert zur Zeit jedoch
+ * nichts anderes, als darauf zu achen, dass das User-Objekt
+ * nicht auf <code>null</code> gesetzt wird.
+ */
+public class ContentNode extends EditableNode {
+  private String desc = null;
+
+  /**
+   * Erzeugt einen neuen Knoten.
+   * @exception java.lang.UnsupportedOperationException falls <code>null</code>
+   *            als UserObject angegeben wird
+   */
+  public ContentNode(Object userObject, boolean editable) {
+    this(userObject, null, editable);
+  }
+
+  /**
+   * Erzeugt einen neuen Knoten.
+   * @exception java.lang.UnsupportedOperationException falls <code>null</code>
+   *            als UserObject angegeben wird
+   */
+  public ContentNode(Object userObject, String desc, boolean editable) {
+    super(userObject, editable);
+    this.desc = desc;
+    checkUserObject(userObject);
+  }
+
+  /**
+   * Setzt das User-Object neu.
+   * @exception java.lang.UnsupportedOperationException falls <code>null</code>
+   *            als UserObject angegeben wird
+   */
+  public void setUserObject(Object userObject) {
+    checkUserObject(userObject);
+    super.setUserObject(userObject);
+  }
+
+  /**
+   * Prueft ein Objekt auf <code>null</code>.
+   * @exception java.lang.UnsupportedOperationException falls <code>null</code>
+   *            als UserObject angegeben wird
+   */
+  private void checkUserObject(Object o) {
+    if (o==null)
+      throw new UnsupportedOperationException("ContentNode can not contain a null-UserObject!");
+  }
+
+  /**
+   * Liefert die Beschreibung, die fuer den Knoten angezeigt wird.
+   * Ist diese auf <code>null</code> gesetzt, wird die Standard-Bezeichnung
+   * <code>super.toString()</code> zurueckgegeben.
+   * @return String
+   */
+  public String toString() {
+    return desc != null ? desc : super.toString() ;
+  }
+}

Added: trunk/src/schmitzm/swing/tree/EditableNode.java
===================================================================
--- trunk/src/schmitzm/swing/tree/EditableNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/tree/EditableNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,59 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.tree;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * Diese Klasse erweitert den {@link javax.swing.tree.DefaultMutableTreeNode} um
+ * eine Editierbar?-Eigenschaft.
+ */
+public class EditableNode extends DefaultMutableTreeNode {
+  private boolean editable = false;
+  /**
+   * Erzeugt einen neuen Knoten. Diesem ist kein
+   * Inhalt zugeordnet, er kann aber Kindknoten erhalten.
+   */
+  public EditableNode(boolean editable) {
+    this(null, editable);
+  }
+
+  /**
+   * Erzeugt einen neuen Knoten. Diesem ist ein
+   * Inhalt zugeordnet und er kann Kindknoten erhalten.
+   */
+  public EditableNode(Object userObject, boolean editable) {
+    this(userObject, editable, true);
+  }
+
+  /**
+   * Erzeugt einen neuen Knoten. Diesem ist ein Inhalt zugeordnet.
+   * Ob er Kindknoten erhalten kann haengt vom
+   * <code>allowsChildren</code>-Parameter ab.
+   */
+  public EditableNode(Object userObject, boolean editable, boolean allowsChildren) {
+    super(userObject, allowsChildren);
+    this.editable = editable;
+  }
+
+  /**
+   * Prueft, ob der Knoten editierbar ist.
+   */
+  public boolean isEditable() {
+    return this.editable;
+  }
+}
+
+
+
+
+

Added: trunk/src/schmitzm/swing/tree/EmptyInnerNode.java
===================================================================
--- trunk/src/schmitzm/swing/tree/EmptyInnerNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/tree/EmptyInnerNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,93 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.tree;
+
+import javax.swing.tree.MutableTreeNode;
+
+/**
+ * Diese Klasse stellt einen inneren Knoten dar, der selbst kein Objekt
+ * beinhaltet, fuer den jedoch potentielle Kinder vorgesehen sind.
+ * Enthaelt er keine "richtigen" Kinder, erhaelt er einen Pseudo-Nachfolger
+ * "<leer>".
+ */
+public class EmptyInnerNode extends EmptyNode {
+  private final EmptyNode DUMMY_CHILD = new EmptyNode("<leer>");
+  /**
+   * Erzeugt einen neuen inneren Knoten.
+   * @param desc Beschreibung
+   */
+  public EmptyInnerNode(String desc) {
+    super(desc);
+    addDummyChild();
+  }
+
+  /**
+   * Fuegt den Pseudo-Nachfolger ein.
+   */
+  private void addDummyChild() {
+    super.add(DUMMY_CHILD);
+  }
+
+  /**
+   * Fuegt dem Knoten ein Kind hinzu. Der Pseudo-Nachfolger wird zuvor
+   * entfernt (sofern er vorhanden war).
+   */
+  public void add(MutableTreeNode newChild) {
+    if ( getChildCount()==1 && getFirstChild()==DUMMY_CHILD )
+      super.remove(0);
+    super.add(newChild);
+  }
+
+  /**
+   * Entfernt ein Kind des Knotens. Falls es sich um den letzten Kindknoten
+   * handelt, wird der Pseudo-Nachfolger hinzugefuegt. Der Pseude-Nachfolger
+   * selbst kann nicht entfernt werden.
+   */
+  public void remove(MutableTreeNode aChild) {
+    // Pseudo-Nachfolger kann nicht entfernt werden
+    if ( aChild==DUMMY_CHILD )
+      return;
+    super.remove(aChild);
+    if ( getChildCount() == 0 )
+      addDummyChild();
+  }
+
+  /**
+   * Entfernt ein Kind des Knotens. Falls es sich um den letzten Kindknoten
+   * handelt, wird der Pseudo-Nachfolger hinzugefuegt. Der Pseude-Nachfolger
+   * selbst kann nicht entfernt werden.
+   */
+  public void remove(int index) {
+    // Pseudo-Nachfolger kann nicht entfernt werden
+    if (getChildAt(index) == DUMMY_CHILD)
+      return;
+    super.remove(index);
+    // Wenn der entfernte Knoten der letzte war, wird wieder ein
+    // Pseudo-Nachfolger hinzugefuegt
+    if ( getChildCount() == 0 )
+      addDummyChild();
+  }
+
+  /**
+   * Entfernt alle Kinder des Knotens und fuegt dann den Pseude-Nachfolger
+   * wieder hinzu.
+   */
+  public void removeAllChildren() {
+    super.removeAllChildren();
+    // Dummy wird nur hinzugefuegt, wenn die super-Methode ihn
+    // nicht bereits hinzugefuegt hat (das waere der Fall, wenn
+    // die super-Methode sukzessive eine andere remove-Methode
+    // aufruft.
+    if ( getChildCount() == 0 )
+      addDummyChild();
+  }
+}

Added: trunk/src/schmitzm/swing/tree/EmptyNode.java
===================================================================
--- trunk/src/schmitzm/swing/tree/EmptyNode.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/tree/EmptyNode.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,52 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.swing.tree;
+
+/**
+ * Diese Klasse stellt einen Knoten dar, der kein Objekt beinhaltet
+ * (sondern nur eine Beschriftung). Er kann deshalb auch nicht
+ * editiert werden.
+ */
+public class EmptyNode extends EditableNode {
+  private String desc = "";
+
+  /**
+   * Erzeugt einen neuen leeren Knoten.
+   */
+  public EmptyNode() {
+    this("<empty>");
+  }
+
+  /**
+   * Erzeugt einen neuen leeren Knoten.
+   * @param desc Beschreibung fuer den Knoten
+   */
+  public EmptyNode(String desc) {
+    super(false);
+    this.desc = desc;
+  }
+
+  /**
+   * Liefert die Beschreibung des Knotens
+   * (anstelle von <code>getUserObject().toString()</code>).
+   */
+  public String toString() {
+    return desc;
+  }
+
+  /**
+   * Macht nichts, da der Knoten nicht editierbar ist!
+   * Ueberschreibt (sicherheitshalber) die Methode der Oberklasse.
+   */
+  public void setUserObject(Object object) {
+  }
+}

Added: trunk/src/schmitzm/swing/tree/package.html
===================================================================
--- trunk/src/schmitzm/swing/tree/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/swing/tree/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,7 @@
+<html>
+<body>
+	Dieses Paket enthält Erweiterungen des
+	<a href="http://java.sun.com" target=_blank>JDK</a>-Standard-Pakets {@code javax.swing.tree}.
+	Diese erleichtern die Arbeit mit <code>JTree</code> und <code>DefaultMutableTreeNode</code>.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/temp/BaseTypeUtil.java
===================================================================
--- trunk/src/schmitzm/temp/BaseTypeUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/temp/BaseTypeUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,445 @@
+/** SCHMITZM - This file is part of the java library of Martin O.J. Schmitz (SCHMITZM)
+
+    This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+    This library 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 Lesser General Public License for more details.
+    You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+    Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
+    Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
+    Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
+ **/
+
+package schmitzm.temp;
+
+// nur fuer Doku
+import schmitzm.data.property.Properties;
+
+/**
+ * Diese Klasse stellt statische Methoden zur Arbeit mit BaseTypes bereit.
+ * Mit BaseTypes sind folgende Klassen und Build-In-Types gemeint:
+ * <table align=center border=2 cellpadding=5><code>
+ * <tr><th>Klasse</th><th>entsprechender Build-In-Type</th></tr>
+ * <tr><td><code>java.lang.Byte</code></td><td><code>byte.class</code></td></tr>
+ * <tr><td><code>java.lang.Short</code></td><td><code>short.class</code></td></tr>
+ * <tr><td><code>java.lang.Integer</code></td><td><code>int.class</code></td></tr>
+ * <tr><td><code>java.lang.Long</code></td><td><code>long.class</code></td></tr>
+ * <tr><td><code>java.lang.Float</code></td><td><code>float.class</code></td></tr>
+ * <tr><td><code>java.lang.Double</code></td><td><code>double.class</code></td></tr>
+ * <tr><td><code>java.lang.Boolean</code></td><td><code>boolen.class</code></td></tr>
+ * <tr><td><code>java.lang.Character</code></td><td><code>char.class</code></td></tr>
+ * <tr><td><code>java.lang.String</code></td><td>---</td></tr>
+ * </table>
+ * <br>
+ * Seit JDK 1.5 koennen Build-In-Types auch als Objekte behandelt werden.
+ * Diese Klasse stellt Methoden zur Kompatibilitaet zur Verfuegung.<br>
+ * Die Methoden basieren jedoch sehr auf statischen Fallunterscheidungen!
+ * Deshalb sollen sie nur als voruebergehende Notloesung dienen und zu
+ * gegebener Zeit durch bessere (und u.U. effizientere Methoden) ersetzt werden.
+ * <br><br>
+ * Zur Zeit basieren folgende Methoden auf dieser Klasse:
+ * <ul>
+ * <li>{@link schmitzm.data.property.PropertyType#isValid(Class)}<br>
+ *     Check: Eine BaseType-Objekt ist auch gueltig fuer eine Build-in-Property</li>
+ * <li>{@link schmitzm.data.property.ValuePropertyType#isValid(Class)}<br>
+ *     Check: Um welche Art von BaseType handelt es sich</li>
+ * <li>{@link schmitzm.dipl.xulu.plugin.gui.DisplayContainer_Properties.PropertiesTableModel#isCellEditable(int,int)}<br>
+ *     Check auf BaseType (nur diese Zellen sind editierbar).</li>
+ * <li>{@link schmitzm.dipl.xulu.plugin.gui.DisplayContainer_Properties.PropertiesTableModel#setValueAt(Object,int,int)}<br>
+ *     Umwandlung von String in Basistyp.</li>
+ * <li>{@link schmitzm.dipl.xulu.plugin.io.misc.DynamicXuluObjectFactory_BasicStructure#createProperty()}<br>
+ *     Umwandlung von String in Basistyp.</li>
+ * <li>{@link schmitzm.dipl.xulu.plugin.model.TestModel.ContentManager}<br>
+ *     Pruefen, ob Property einen numerischen Integer/Long beinhaltet.</li>
+ * <li>{@link schmitzm.dipl.test.NumericMinMaxConstraint#isSatisfiedFor(Properties)}<br>
+ *     Pruefen, ob Property einen numerischen Wert beinhaltet.</li>
+ * </ul>
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class BaseTypeUtil {
+
+  /**
+   * Prueft, ob die angegebene Klasse einen Build-In-Type darstellt. Dies sind
+   * alle Typen, die oben in der rechten Spalte angegeben sind.
+   */
+  public static boolean isBuildInType(Class c) {
+    return c.equals(short.class) ||
+           c.equals(byte.class) ||
+           c.equals(int.class) ||
+           c.equals(long.class) ||
+           c.equals(float.class) ||
+           c.equals(double.class) ||
+           c.equals(char.class) ||
+           c.equals(boolean.class);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt einen Build-In-Type darstellt. Dies sind
+   * alle Typen, die oben in der rechten Spalte angegeben sind.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isBuildInType(Object o) {
+    return o!=null || isBuildInType(o.getClass());
+  }
+
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 8-bit-Ganzzahl darstellt.
+   * Also entweder <code>byte.class</code> oder <code>java.lang.Byte</code>.
+   */
+  public static boolean isByte(Class c) {
+    return c.equals(byte.class) || Byte.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 8-bit-Ganzzahl darstellt.
+   * Also entweder eine Instanz von <code>byte.class</code> oder
+   * <code>java.lang.Byte</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isByte(Object o) {
+    return o!=null && isByte(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 16-bit-Ganzzahl darstellt.
+   * Also entweder <code>short.class</code> oder <code>java.lang.Short</code>.
+   */
+  public static boolean isShort(Class c) {
+    return c.equals(short.class) || Short.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 16-bit-Ganzzahl darstellt.
+   * Also entweder eine Instanz von <code>short.class</code> oder
+   * <code>java.lang.Short</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isShort(Object o) {
+    return o!=null && isShort(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 32-bit-Ganzzahl darstellt.
+   * Also entweder <code>int.class</code> oder <code>java.lang.Integer</code>.
+   */
+  public static boolean isInteger(Class c) {
+    return c.equals(int.class) || Integer.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 32-bit-Ganzzahl darstellt.
+   * Also entweder eine Instanz von <code>int.class</code> oder
+   * <code>java.lang.Integer</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isInteger(Object o) {
+    return o!=null && isInteger(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 64-bit-Ganzzahl darstellt.
+   * Also entweder <code>long.class</code> oder <code>java.lang.Long</code>.
+   */
+  public static boolean isLong(Class c) {
+    return c.equals(long.class) || Long.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 64-bit-Ganzzahl darstellt.
+   * Also entweder eine Instanz von <code>long.class</code> oder
+   * <code>java.lang.Long</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isLong(Object o) {
+    return o!=null && isLong(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 32-bit-Gleitkommazahl darstellt.
+   * Also entweder <code>float.class</code> oder <code>java.lang.Float</code>.
+   */
+  public static boolean isFloat(Class c) {
+    return c.equals(float.class) || Float.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 32-bit-Gleitkommazahl darstellt.
+   * Also entweder eine Instanz von <code>float.class</code> oder
+   * <code>java.lang.Float</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isFloat(Object o) {
+    return o!=null && isFloat(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine 64-bit-Gleitkommazahl darstellt.
+   * Also entweder <code>double.class</code> oder <code>java.lang.Couble</code>.
+   */
+  public static boolean isDouble(Class c) {
+    return c.equals(double.class) || Double.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine 64-bit-Gleitkommazahl darstellt.
+   * Also entweder eine Instanz von <code>double.class</code> oder
+   * <code>java.lang.Double</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isDouble(Object o) {
+    return o!=null && isDouble(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse einen boolschen Wert darstellt.
+   * Also entweder <code>boolean.class</code> oder <code>java.lang.Boolean</code>.
+   */
+  public static boolean isBoolean(Class c) {
+    return c.equals(boolean.class) || Boolean.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt einen boolschen Wert darstellt.
+   * Also entweder eine Instanz von <code>boolean.class</code> oder
+   * <code>java.lang.Boolean</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isBoolean(Object o) {
+    return o!=null && isBoolean(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse ein Zeichen darstellt.
+   * Also entweder <code>char.class</code> oder <code>java.lang.Character</code>.
+   */
+  public static boolean isCharacter(Class c) {
+    return c.equals(char.class) || Character.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt ein Zeichen darstellt.
+   * Also entweder eine Instanz von <code>char.class</code> oder
+   * <code>java.lang.Character</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isCharacter(Object o) {
+    return o!=null && isCharacter(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine Zeichenkette darstellt.
+   * Also <code>java.lang.String</code>.
+   */
+  public static boolean isString(Class c) {
+    return String.class.isAssignableFrom(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine Zeichenkette darstellt.
+   * Also eine Instanz von <code>java.lang.String</code> ist.
+   * @return <code>false</code>, wenn das angegebene Objekt <code>null</code> ist
+   */
+  public static boolean isString(Object o) {
+    return o!=null && isString(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse einen Basistyp (inkl. String) oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Integer</code>) darstellt.
+   */
+  public static boolean isBaseType(Class c) {
+    return isByte(c) || isShort(c) || isInteger(c) || isLong(c) || isFloat(c) ||
+           isDouble(c) || isCharacter(c) || isString(c) || isBoolean(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt einen Basistyp (auch String) oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Integer</code>) darstellt.
+   */
+  public static boolean isBaseType(Object o) {
+    return o!=null && isBaseType(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse einen numerischen Basistyp oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Integer</code>) darstellt.
+   */
+  public static boolean isNumeric(Class c) {
+    return isByte(c) || isShort(c) || isInteger(c) || isLong(c) || isFloat(c) ||
+           isDouble(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt einen numerischen Basistyp oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Integer</code>) darstellt.
+   */
+  public static boolean isNumeric(Object o) {
+    return o!=null && isNumeric(o.getClass());
+  }
+
+  /**
+   * Prueft, ob die angegebene Klasse eine Dezimalzahl oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Double</code>) darstellt.
+   */
+  public static boolean isDecimal(Class c) {
+    return isFloat(c) || isDouble(c);
+  }
+
+  /**
+   * Prueft, ob das angegebene Objekt eine Dezimalzahl oder
+   * eine korrespondierende Klasse (z.B. <code>java.lang.Double</code>) darstellt.
+   */
+  public static boolean isDecimal(Object o) {
+    return o!=null && isDecimal(o.getClass());
+  }
+
+  /**
+   * Prueft, ob zwei BaseType-Klassen zueinander kompatibel sind
+   * @return <code>false</code> wenn eine der Klassen <b>keinen</b> BaseType
+   *         darstellt
+   */
+  public static boolean isEquivalent(Class c1, Class c2) {
+    return isByte(c1) && isByte(c2) ||
+           isShort(c1) && isShort(c2) ||
+           isInteger(c1) && isInteger(c2) ||
+           isLong(c1) && isLong(c2) ||
+           isFloat(c1) && isFloat(c2) ||
+           isDouble(c1) && isDouble(c2) ||
+           isBoolean(c1) && isBoolean(c2) ||
+           isCharacter(c1) && isCharacter(c2) ||
+           isString(c1) && isString(c2) ||
+           isBoolean(c1) && isBoolean(c2);
+  }
+
+  /**
+   * Prueft, ob zwei BaseType-Objekte zueinander kompatibel sind
+   * @return <code>false</code> wenn eines der Objekte <b>keinen</b> BaseType
+   *         darstellt oder <code>null</code> ist
+   */
+  public static boolean isEquivalent(Object o1, Object o2) {
+    return o1!=null && o2!=null && isEquivalent(o1.getClass(),o2.getClass());
+  }
+
+  /**
+   * Prueft, ob eine numerische Datentyp-Klasse in eine andere numerische
+   * Datentyp-Klasse (ohne Verlust) umgewandelt werden kann.
+   * @param fromClass Ausgangs-Datentyp
+   * @param to Class  Ziel-Datentyp
+   */
+  public static boolean isConvertable(Class<Number> fromClass, Class<Number> toClass) {
+    // gleiche Typen koennen immer ineinander umgewandelt werden
+    if ( fromClass.equals( toClass ) )
+      return true;
+
+    // Float kann in Double umgewandelt werden
+    if ( Float.class.equals(fromClass) )
+      return Double.class.equals(toClass);
+
+    // Integer kann in Long umgewandelt werden
+    if ( Integer.class.equals(fromClass) )
+      return Long.class.equals(toClass);
+
+    // Byte kann in Long und Integer umgewandelt werden
+    if ( Byte.class.equals(fromClass) )
+      return Integer.class.equals(toClass)
+          || Long.class.equals(toClass) ;
+
+    // Short kann in Long und Integer umgewandelt werden
+    if ( Short.class.equals(fromClass) )
+      return Integer.class.equals(toClass)
+          || Long.class.equals(toClass) ;
+
+    return false;
+  }
+
+  /**
+   * Wandelt einen numerischen Wert in einen bestimmten Datentyp um.
+   * Dabei kann
+   * @param source   numerisches Datenobjekt
+   * @param destType Typ, in den das Objekt umgewandelt wird
+   */
+  public static Object convertNumber(Number source, Class<Number> destType) {
+    if ( Double.class.equals(destType) )
+      return source.doubleValue();
+    if ( Float.class.equals(destType) )
+      return source.floatValue();
+    if ( Long.class.equals(destType) )
+      return source.longValue();
+    if ( Integer.class.equals(destType) )
+      return source.intValue();
+    if ( Byte.class.equals(destType) )
+      return source.byteValue();
+    if ( Short.class.equals(destType) )
+      return source.shortValue();
+    throw new UnsupportedClassVersionError("Unsupported numeric type: "+(destType != null ? destType.getSimpleName() : "null"));
+  }
+
+  /**
+   * Erzeugt eine BaseType-Instanz aus einem String.
+   * @param strValue Zeichenkette, die das Objekt darstellt
+   * @param destType BaseType in den konvertiert werden soll
+   */
+  public static <T> T convertFromString(String strValue, Class<? extends T> destType) {
+    if (!isBaseType(destType))
+      return null;
+
+    // Beim String ist nicht zu machen
+    if ( isString(destType) )
+      return (T)strValue;
+
+    // Bei allen anderen Typen wird ein Leerstring zu null
+    if ( strValue == null || strValue.equals("") )
+      return null;
+
+    // Bei Zeichen, wird das erste vom String genommen
+    if ( isCharacter(destType) )
+      return (T)new Character(strValue.charAt(0));
+    // Bei Zahlen oder Boolean wird versucht aus dem
+    // String zu parsen
+    if ( isShort(destType) )
+      return (T)new Short(strValue);
+    if ( isByte(destType) )
+      return (T)new Byte(strValue);
+    if ( isInteger(destType) )
+      return (T)new Integer(strValue);
+    if ( isLong(destType) )
+      return (T)new Long(strValue);
+    if ( isFloat(destType) )
+      return (T)new Float(strValue);
+    if ( isDouble(destType) )
+      return (T)new Double(strValue);
+    if ( isBoolean(destType) )
+      return (T)new Boolean(strValue);
+
+    return null;
+  }
+
+  /**
+   * Erzeugt eine BaseType-Instanz aus einem String. Es wird versucht, einen
+   * der folgenden Datentypen zu erzeugen:
+   * <ol>
+   * <li><code>Integer</code></li>
+   * <li><code>Double</code></li>
+   * <li><code>String</code></li>
+   * </ol>
+   * @param strValue Zeichenkette, die das Objekt darstellt
+   */
+  public static Object convertFromString(String strValue) {
+    // erst versuchen einen Integer zu erzeugen
+    try {
+      return Integer.parseInt(strValue);
+    } catch (NumberFormatException err) {
+    }
+
+    // dann versuchen einen Double zu erzeugen
+    try {
+      return Double.parseDouble(strValue);
+    } catch (NumberFormatException err) {
+    }
+
+    // sonst beim String belassen
+    return strValue;
+  }
+
+}

Added: trunk/src/schmitzm/temp/package.html
===================================================================
--- trunk/src/schmitzm/temp/package.html	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/temp/package.html	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,7 @@
+<html>
+<body>
+	Dieses Paket enthält Klassen, die nur temporär für das Xulu-Projekt Verwendung finden.
+	Sie implementieren "Not-Lösungen", die in einem späteren Entwicklungsschritt durch
+	bessere Implementierungen ersetzt und in andere Packages verlagert werden.
+</body>
+</html>
\ No newline at end of file

Added: trunk/src/schmitzm/xml/XMLUtil.java
===================================================================
--- trunk/src/schmitzm/xml/XMLUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/schmitzm/xml/XMLUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,98 @@
+package schmitzm.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+// W3C XML
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.SAXException;
+import org.xml.sax.ErrorHandler;
+import org.w3c.dom.Node;
+
+// JDOM XML
+import org.jdom.Document;
+import org.jdom.DefaultJDOMFactory;
+import org.jdom.Element;
+import org.jdom.output.XMLOutputter;
+import org.jdom.adapters.JAXPDOMAdapter;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.lang.LangUtil;
+
+
+/**
+ * Diese Klasse enthaelt statische Helper-Methoden fuer das Arbeiten mit XML.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class XMLUtil {
+  /** Logger, to give debug and warning messaged. */
+  private static Logger LOGGER = Logger.getLogger( XMLUtil.class.getName() );
+
+  /** Wrapper from JDOM to W3C. */
+  public static final JAXPDOMAdapter JDOM_TO_JAX = new JAXPDOMAdapter();
+
+//  /**
+//   * Returns a {@link DocumentBuilder}. If validating is true,
+//   * the contents is validated against the XSD specified in the file.
+//   */
+//  private final static DocumentBuilder getDocumentBuilder(final boolean validating) throws SAXException, IOException, ParserConfigurationException {
+//    // Create a builder factory
+//    final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+//
+//    factory.setNamespaceAware(true);
+//    final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+//    final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
+//    factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
+////    factory.setValidating(validating);
+//
+//    // Create the builder and parse the file
+//    final DocumentBuilder documentBuilder = factory.newDocumentBuilder();
+////    documentBuilder.setErrorHandler(new ErrorHandler() {
+////      public void error(final SAXParseException exception) throws SAXException {
+////        LOGGER.error("ErrorHandler.error");
+////        throw exception;
+////      }
+////      public void fatalError(final SAXParseException exception) throws SAXException {
+////        LOGGER.error("ErrorHandler.fataError");
+////        throw exception;
+////      }
+////      public void warning(final SAXParseException exception) throws SAXException {
+////        LOGGER.warn("ErrorHandler.warning", exception);
+////      }
+////    });
+////
+//    return documentBuilder;
+//  }
+
+//  /**
+//   * Wraps a {@linkplain Element JDOM-Element} to a {@linkplain Node W3C-Node}.
+//   * @param element a JDOM-Element
+//   */
+//  public static Node wrapElement(Element element) {
+////    String  xmlDefinition = new XMLOutputter().outputString( new Document(element,element.getDocument().getDocType()) );
+////    Document document = new DefaultJDOMFactory().document( element );
+//    String  xmlString = new XMLOutputter().outputString( element );
+//
+//    try {
+//      InputStream xmlStream = new ByteArrayInputStream(xmlString.getBytes());
+//      DocumentBuilder builder = getDocumentBuilder(false);
+//      org.w3c.dom.Document node = builder.parse(xmlStream);
+//
+////      org.w3c.dom.Document node = JDOM_TO_JAX.getDocument(xmlStream,true);
+//
+//      LOGGER.debug( xmlString );
+//      LOGGER.debug( node.hasAttributes() );
+//      xmlStream.close();
+//      return node;
+//    } catch ( Exception err ) {
+//      throw new UnsupportedOperationException("Error during XML wrapping", err);
+//    }
+//  }
+
+}

Added: trunk/src/skrueger/AttributeMetaData.java
===================================================================
--- trunk/src/skrueger/AttributeMetaData.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/AttributeMetaData.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,88 @@
+package skrueger;
+
+import org.apache.log4j.Logger;
+
+import skrueger.geotools.StyledMapInterface;
+import skrueger.i8n.Translation;
+
+/**
+ * This class holds meta information about an attribute/column. This
+ * information is used by {@link StyledMapInterface}.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+public class AttributeMetaData {
+	static private final Logger LOGGER = Logger
+			.getLogger(AttributeMetaData.class);
+	protected Translation title = new Translation();
+	protected Translation desc = new Translation();
+	protected boolean visible = false;
+	protected String unit = "";
+	protected int colIdx;
+
+	/**
+	 * Creates an {@link AttributeMetaData} object with the following information
+	 * @param colIdx The column index of this attribute in the underlying table/dbf/etc...
+	 * @param visible Shall this attribute be displayed or hidden from the user?
+	 * @param title {@link Translation} for Name
+	 * @param desc {@link Translation} for an attribute description
+	 * @param unit {@link String} of the unit that the information is in
+	 */
+	public AttributeMetaData(final int colIdx, final Boolean visible,
+			final Translation title, final Translation desc, final String unit) {
+	
+		this.colIdx = colIdx;
+		this.title = title;
+		this.desc = desc;
+		if (colIdx == 0){
+			// The first attribut is THE_GEOM and shall never be visible!
+			this.visible = false;
+		}else
+			this.visible = visible;
+		this.unit = unit;
+	}
+
+	/**
+	 * Creates a {@link AttributeMetaData} with default (no) values.
+	 */
+	public AttributeMetaData(final Integer col, final String defaultName) {
+		this(col, true, new Translation(defaultName), new Translation(""), "");
+	}
+
+	public Boolean isVisible() {
+		return visible;
+	}
+
+	public void setVisible(final Boolean visible) {
+		this.visible = visible;
+	}
+
+	/** @return the index of this attribute in the underlying table/dbf **/
+	public int getColIdx() {
+		return colIdx;
+	}
+
+	public Translation getTitle() {
+		return title;
+	}
+
+	public void setTitle(final Translation title) {
+		this.title = title;
+	}
+
+	public Translation getDesc() {
+		return desc;
+	}
+
+	public void setDesc(final Translation desc) {
+		this.desc = desc;
+	}
+
+	public String getUnit() {
+		return unit;
+	}
+
+	public void setUnit(final String unit) {
+		this.unit = unit;
+	}
+}

Added: trunk/src/skrueger/AttributeMetaDataAttributeTypeFilter.java
===================================================================
--- trunk/src/skrueger/AttributeMetaDataAttributeTypeFilter.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/AttributeMetaDataAttributeTypeFilter.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,59 @@
+package skrueger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.geotools.feature.AttributeType;
+import org.geotools.feature.FeatureType;
+
+import schmitzm.geotools.feature.AttributeTypeFilter;
+
+/**
+ * Implements an {@link AttributeTypeFilter} using the
+ * {@linkplain AttributeMetaData#isVisible() visible}-property of an
+ * {@link AttributeMetaData} map (or array).<br>
+ * If this filter is created from a {@code null} map or {@code null} array,
+ * the filter accepts ALL attributes except geometries.
+ * @see AttributeTypeFilter#NO_GEOMETRY
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class AttributeMetaDataAttributeTypeFilter implements AttributeTypeFilter {
+
+  /** The meta data of a set of attributes */
+  protected Map<Integer,AttributeMetaData> attrMetaDataMap = null;
+
+  /**
+   * Creates a new filter.
+   * @param attrMetaData the meta data of some attributes
+   */
+  public AttributeMetaDataAttributeTypeFilter(AttributeMetaData[] attrMetaData) {
+    this.attrMetaDataMap = new HashMap<Integer,AttributeMetaData>();
+    for (int i=0; attrMetaData!=null && i<attrMetaData.length; i++)
+      this.attrMetaDataMap.put(i, attrMetaData[i]);
+  }
+
+  /**
+   * Creates a new filter.
+   * @param attrMetaData the meta data of some attributes
+   */
+  public AttributeMetaDataAttributeTypeFilter(Map<Integer,AttributeMetaData> attrMetaData) {
+    this.attrMetaDataMap = attrMetaData;
+  }
+
+  /**
+   * Returns {@code true} if the attribute meta data at index {@code idx} is
+   * visible and the attribute is no geometry at all.
+   */
+  public boolean accept(AttributeType type, int idx) {
+    // if no meta data is given, all attributes (except Geometry)
+    // are treaten as visible
+    if ( attrMetaDataMap == null )
+      return NO_GEOMETRY.accept(type, idx);
+
+    AttributeMetaData metaData = attrMetaDataMap.get(idx);
+    return NO_GEOMETRY.accept(type, idx)  // no geometry attributes at all
+        && metaData != null               // meta data must be present for column
+        && metaData.isVisible();          // attribute must be visible
+  }
+}

Added: trunk/src/skrueger/RasterLegendData.java
===================================================================
--- trunk/src/skrueger/RasterLegendData.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/RasterLegendData.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,56 @@
+package skrueger;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import skrueger.i8n.Translation;
+
+/**
+ * Holds all the additional information needed to paint a Legend for a RasterLayer.
+ * So far, only Legends for one-band raster layers are supported.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ */
+public class RasterLegendData extends HashMap<Double, Translation> {
+	static private final Logger LOGGER = Logger.getLogger(RasterLegendData.class);
+	private Boolean paintGaps = false;
+
+	public Boolean isPaintGaps() {
+		return paintGaps;
+	}
+
+	public void setPaintGaps(boolean paintPaps) {
+		this.paintGaps = paintPaps;
+	}
+
+	/**
+	 * {@link #paintGaps} defines, if gaps should be painted between the legends colors,
+	 * indicating nominal values in the raster (e.g. classifications)
+	 */
+	public RasterLegendData(boolean paintGaps) {
+		super();
+		this.paintGaps = paintGaps;
+	}
+
+	public boolean getPaintGaps() {
+		return paintGaps ;
+	}
+
+	public List<Double> getSortedKeys(){
+		Object[] array = keySet().toArray();
+
+		Arrays.sort(array);
+
+		final LinkedList<Double> linkedList = new LinkedList<Double>();
+		for (Object o : array){
+			linkedList.add( (Double)o);
+		}
+
+		return linkedList;
+
+	}
+}

Added: trunk/src/skrueger/geotools/AbstractStyledMap.java
===================================================================
--- trunk/src/skrueger/geotools/AbstractStyledMap.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/AbstractStyledMap.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,253 @@
+package skrueger.geotools;
+
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+import org.geotools.styling.Style;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.lang.LangUtil;
+import skrueger.i8n.Translation;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * This class is a default implementation of {@link StyledMapInterface}.
+ * {@link StyledMapInterface#dispose()} and {@link StyledMapInterface#uncache()}
+ * must be implemented by the sub class. This class only implements the "hold"
+ * of an geo object of type {@code <E>}.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public abstract class AbstractStyledMap<E> implements StyledMapInterface<E> {
+  /** Logger for warning- and error messages. */
+  protected Logger LOGGER = LangUtil.createLogger(this);
+
+  /** Holds the unique ID of the geo object. */
+  protected String id = null;
+  /** Holds a short (language-specific) description of the geo object. */
+  protected Translation title = null;
+  /** Holds a long (language-specific) description of the geo object. */
+  protected Translation desc = null;
+  /** Holds the (language-specific) keywords to describe the geo object. */
+  protected Translation keywords = null;
+  /** Holds an icon to represent the geo object */
+  protected ImageIcon icon = null;
+  /** Holds the geo object represeneted by the map */
+  protected E geoObject = null;
+  /** Holds the CRS of the geo object */
+  protected CoordinateReferenceSystem crs = null;
+  /** Holds the bounds of the geo object */
+  protected Envelope envelope = null;
+  /** Holds the display style for the geo object */
+  protected Style style = null;
+
+  /**
+   * Creates a language specific styled map.
+   * @param geoObject the geo object
+   * @param envelope the bounds of the geo object
+   * @param crs the CRS of the geo object
+   * @param id a unique ID for the geo object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style
+   * @param icon an icon for the object
+   * @exception IllegalArgumentException if {@code null} is given as ID or
+   *            geo object
+   */
+  public AbstractStyledMap(E geoObject, Envelope envelope, CoordinateReferenceSystem crs, String id, Translation title, Translation desc, Translation keywords, Style style, ImageIcon icon) {
+    if ( id == null )
+      throw new IllegalArgumentException("ID is not allowed to be null!");
+    if ( geoObject == null )
+      throw new IllegalArgumentException("The GeoObject is not allowed to be null!");
+    this.id        = id;
+    this.geoObject = geoObject;
+    this.crs       = crs;
+    this.envelope  = envelope;
+    setTitle( title );
+    setDesc( desc );
+    setKeywords( keywords );
+    setStyle( style );
+    setImageIcon( icon );
+  }
+
+  /**
+   * Creates a non-translated styled map.
+   * @param geoObject the geo object
+   * @param envelope the bounds of the geo object
+   * @param crs the CRS of the geo object
+   * @param id a unique ID for the geo object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style
+   * @param icon an icon for the object
+   * @exception IllegalArgumentException if {@code null} is given as ID
+   */
+  public AbstractStyledMap(E geoObject, Envelope envelope, CoordinateReferenceSystem crs, String id, String title, String desc, String keywords, Style style, ImageIcon icon ) {
+    this(geoObject, envelope, crs, id, (Translation)null, null, null, style, icon);
+    setTitle( title );
+    setDesc( desc );
+    setKeywords( keywords );
+  }
+
+  /**
+   * Returns a ID for the geo object. The ID should be unique in a map ob
+   * {@linkplain StyledMapInterface styled map objects}
+   */
+  public String getId() {
+    return id;
+  }
+
+  /**
+   * Returns a short (language-specific) description of the geo object.
+   */
+  public Translation getTitle() {
+    return title;
+  }
+
+  /**
+   * Sets a short (language-specific) description of the geo object.
+   * If {@code title} is {@code null} an untranslated default title is set, so
+   * {@link #getTitle()} never returns {@code null}.
+   * @param title new description for the geo object
+   */
+  public void setTitle(Translation title) {
+    this.title = (title != null) ? title : new Translation("untitled");
+  }
+
+  /**
+   * Sets a short (non-translated) description of the geo object.
+   * If {@code title} is {@code null} an untranslated default title is set, so
+   * {@link #getTitle()} never returns {@code null}.
+   * @param title new description for the geo object
+   */
+  public void setTitle(String title) {
+    setTitle( title != null ? new Translation(title): (Translation)null );
+  }
+
+  /**
+   * Returns a long (language-specific) description of the object.
+   */
+  public Translation getDesc() {
+    return desc;
+  }
+
+  /**
+   * Sets a long (language-specific) description of the object.
+   * If {@code desc} is {@code null} an (untranslated) empty description is set, so
+   * {@link #getDesc()} never returns {@code null}.
+   * @param desc new description for the geo object
+  */
+  public void setDesc(Translation desc) {
+    this.desc = (desc != null) ? desc : new Translation("");
+  }
+
+  /**
+   * Sets a long (non-translated) description of the object.
+   * If {@code desc} is {@code null} an (untranslated) empty description is set, so
+   * {@link #getDesc()} never returns {@code null}.
+   * @param desc new description for the geo object
+  */
+  public void setDesc(String desc) {
+    setDesc( desc != null ? new Translation(desc) : (Translation)null);
+  }
+
+  /**
+   * Returns a (language-specific) key word sequence for the geo object.
+   */
+  public Translation getKeywords() {
+    return keywords;
+  }
+
+  /**
+   * Sets a (language-specific) key word sequence for the geo object.
+   * If {@code keywords} is {@code null} an (untranslated) empty string is set, so
+   * {@link #getKeywords()} never returns {@code null}.
+   * @param keywords Keywords
+   */
+  public void setKeywords(Translation keywords) {
+    this.keywords = (keywords != null) ? keywords : new Translation("");
+  }
+
+  /**
+   * Sets a (non-translated) key word sequence for the geo object.
+   * If {@code keywords} is {@code null} an (untranslated) empty string is set, so
+   * {@link #getKeywords()} never returns {@code null}.
+   * @param keywords Keywords
+   */
+  public void setKeywords(String keywords) {
+    setKeywords( keywords != null ? new Translation(keywords) : (Translation)null);
+  }
+
+  /**
+   * Returns the geo object representet in the map. Sub classes must override
+   * this method to implement "late loading" on first call.
+   * @return {@link #geoObject}
+   */
+  public E getGeoObject() {
+    return geoObject;
+  }
+
+  /**
+   * Returns the bounds of the geo object.
+   */
+  public Envelope getEnvelope() {
+    return envelope;
+  }
+
+  /**
+   * Returns the {@link CoordinateReferenceSystem} of the geo object.
+   */
+  public CoordinateReferenceSystem getCrs() {
+    return crs;
+  }
+
+  /**
+   * Returns {@link #crs CoordinateReferenceSystem.toString()}. This method
+   * can be overriden to create a "nicer" description.
+   */
+  public String getCRSString() {
+    return crs.toString();
+  }
+
+  /**
+   * Returns an icon, which represents the geo object.
+   */
+  public ImageIcon getImageIcon() {
+    return null;
+  }
+
+  /**
+   * Sets an icon, which represents the geo object.
+   * @param icon an icon
+   */
+  public void setImageIcon(ImageIcon icon) {
+    this.icon = icon;
+  }
+
+  /**
+   * Returns the display style for the geo object.
+   */
+  public Style getStyle() {
+    return style;
+  }
+
+  /**
+   * Sets the display style for the geo object.
+   * If {@code style} is {@code null} an default style is set, so
+   * {@link #getStyle()} never returns {@code null}.
+   * @see #createDefaultStyle()
+   */
+  public void setStyle(Style style) {
+    this.style = (style != null) ? style : createDefaultStyle();
+  }
+
+  /**
+   * Creates a default style for the geo object. This style is used whenever
+   * the style is set to {@code null}.
+   * @see #setStyle(Style)
+   */
+  protected abstract Style createDefaultStyle();
+}

Added: trunk/src/skrueger/geotools/MapContextManagerInterface.java
===================================================================
--- trunk/src/skrueger/geotools/MapContextManagerInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/MapContextManagerInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,86 @@
+package skrueger.geotools;
+import java.util.List;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.map.event.MapLayerListener;
+
+import skrueger.AttributeMetaData;
+import skrueger.RasterLegendData;
+
+/**
+ * Implementations of this class can can be used to fill/insert/remove a mapContext
+ * with {@link StyledMapInterface} objects.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ */
+public interface MapContextManagerInterface {
+
+	/**
+	 * Add a {@link StyledMapInterface} object into the underlying {@link MapContext}
+	 * as the topmost layer
+	 *
+	 * @param styledMapObject Obejct to add to the layer
+	 */
+	boolean addStyledLayer (StyledMapInterface<?> styledMapObject);
+
+	/**
+	 * @param mapContextIdx Index in the mapcontext, bottom first
+	 * @return sucessful?
+	 */
+	boolean removeStyledLayer (int mapContextIdx);
+
+	/**
+	 * Inserts a {@link StyledMapInterface} object into the underlying {@link MapContext}
+	 * at the given position
+	 */
+	boolean insertStyledLayer (StyledMapInterface<?> styledMapObject, int mapContextIdx);
+
+	/** Add {@link MapLayerListener} */
+	void addMapLayerListListener( MapLayerListListener listener);
+
+	/** Remove {@link MapLayerListener} */
+	void removeMapLayerListListener( MapLayerListListener listener);
+
+	/**
+	 * Help the GC by disposing this Component
+	 */
+	void dispose();
+
+	/**
+	 * Returns a ordered list of the layers that are contained in the underlying {@link MapContext}
+	 */
+	List<StyledMapInterface<?>> getStyledObjects();
+
+	/**
+	 * Returns a list of {@link AttributeMetaData} that shall be shown (e.g. when the Mouse clicked into the map)
+	 * Returns an empty list if the layer doesn't exist or is not backed by a {@link StyledFeatureCollectionInterface}
+	 */
+	List<AttributeMetaData> getVisibleAttribsFor(MapLayer layer);
+
+	/**
+	 * Returns the title of the layer
+	 * @param layer {@link MapLayer}
+	 * @return null, if the layer is unknown
+	 */
+	String getTitleFor(MapLayer layer);
+
+	/**
+	 * Returns the description of the layer
+	 * @param layer {@link MapLayer}
+	 * @return null, if the layer is unknown. Empty String if the description is empty
+	 */
+	String getDescFor(MapLayer layer);
+
+	/**
+	 * Returns the {@link RasterLegendData} object for the layer.
+	 * @return null, if the layer is not found or of type raster
+	 */
+	RasterLegendData getLegendMetaData(MapLayer layer);
+
+	/**
+	 * Ruturns the {@link StyledMapInterface} object that is associated with the layer or NULL if the layer can't be found.
+	 */
+	StyledMapInterface<?> getStyledObjectFor(MapLayer layer);
+}

Added: trunk/src/skrueger/geotools/MapPaneToolBar.java
===================================================================
--- trunk/src/skrueger/geotools/MapPaneToolBar.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/MapPaneToolBar.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,647 @@
+package skrueger.geotools;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.geotools.gui.GeoMapPane;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.MapContextControlPane;
+import schmitzm.geotools.gui.MapPaneStatusBar;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.swing.ButtonGroup;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * A toolbar to controll a {@link JMapPane} (Atlas visualization). This contains two types
+ * of buttons. A group of <i>tools</i> for the mouse actions on the map represented
+ * by {@link JToggleButton JToggleButtons}, where only one tool can be activated
+ * every time. And some (general) <i>actions</i>, represented by normal
+ * {@link JButton JButtons}. 
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ */
+public class MapPaneToolBar extends JToolBar {
+	private static final Logger LOGGER = Logger.getLogger(MapPaneToolBar.class.getName());
+	/** Constant for the tool "Panning" (10). */
+	public static final int TOOL_PAN = 10;
+	/** Constant for the tool "Zoom In" (30). */
+	public static final int TOOL_ZOOMIN = 30;
+	/** Constant for the tool "Zoom Out" (40). */
+	public static final int TOOL_ZOOMOUT = 40;
+	/** Constant for the tool "Info" (20). */
+	public static final int TOOL_INFO = 20;
+
+	/** Tool currently selected */
+    protected int selectedTool = TOOL_ZOOMIN;
+    /** Holds the tool buttons of the tool bar. */
+    protected SortedMap<Integer, JToggleButton> toolButtons = null;
+    /** Controls that only one tool button is activated. */
+    protected ButtonGroup toolButtonGroup = null;
+    /** Constant for the action "Zoom back" (100). */
+    public static final int ACTION_ZOOM_BACK = 100;
+    /** Constant for the action "Zoom forward" (110). */
+    public static final int ACTION_ZOOM_FORWARD = 110;
+
+    /** Holds the action buttons of the bar. */
+    protected SortedMap<Integer, JButton> actionButtons = null;
+
+	/** Holds the {@link JMapPane} this tool bar controls. */
+	protected JMapPane mapPane = null;
+
+	/**
+	 * A List to remember the last Envelopes that have been watched. Used for
+	 * the zoomBack- and zoomForwardButtons *
+	 */
+	protected ArrayList<Envelope> lastZooms = new ArrayList<Envelope>();
+	/** Holds the index to the current element in {@link #lastZooms}. */
+	protected int zoomBackIndex = 0;
+
+	/** Listener to sniff the zoom actions on the map. */
+	protected JMapPaneListener mapPaneListener = null;
+	
+	protected boolean zoomBackForwardButtonInAction;
+
+    /**
+     * Creates a new toolbar. Notice: This toolbar does nothing
+     * until {@link #setMapPane(JMapPane)} is called!
+     */
+    public MapPaneToolBar() {
+      this(null);
+    }
+    
+    /**
+	 * Creates a new tool bar.
+	 * @param mapPane {@link JMapPane} the tool bar controls
+	 */
+	public MapPaneToolBar(JMapPane mapPane) {
+	  super("Control the map", JToolBar.HORIZONTAL);
+      this.toolButtons     = new TreeMap<Integer,JToggleButton>();
+      this.toolButtonGroup = new ButtonGroup();
+      this.actionButtons   = new TreeMap<Integer,JButton>();
+      // Create a Listener to sniff the zooms on the JMapPane
+      this.mapPaneListener = new JMapPaneListener() {
+          public void performMapPaneEvent(JMapPaneEvent e) {
+              if ( !(e instanceof MapAreaChangedEvent) )
+                return;
+                
+              if ( zoomBackForwardButtonInAction ) {
+                zoomBackForwardButtonInAction = false;
+                return;
+              }
+             
+              Envelope oldMapArea = ((MapAreaChangedEvent)e).getOldMapArea();
+              if (lastZooms.size() == 0 && oldMapArea != null ) {
+                lastZooms.add(oldMapArea);
+                zoomBackIndex = 1;
+              }
+
+              final Envelope mapArea = ((MapAreaChangedEvent)e).getNewMapArea();
+              if (mapArea == null)
+                return;
+              
+              if (lastZooms.size() > 0 && mapArea.equals(lastZooms.get(lastZooms.size() - 1))) {
+                // LOGGER.debug("MapAreaChangedEvent ausgelassen bei der Zaehlung der Zoomschritt weil identisch");
+                return;
+              }
+
+              if (lastZooms.size() > 0)
+                while (zoomBackIndex < lastZooms.size())
+                  lastZooms.remove(lastZooms.size() - 1);
+
+              lastZooms.add(mapArea);
+              zoomBackIndex = lastZooms.size();
+              setButtonEnabled(ACTION_ZOOM_BACK, lastZooms.size() > 1);
+              setButtonEnabled(ACTION_ZOOM_FORWARD, false);
+          }
+      };    
+     
+      setMapPane(mapPane);
+	  setFloatable(false);
+	  setRollover(true);
+	  
+	  init();
+	}
+	
+	/**
+	 * Sets the {@link JMapPane} controlled by this tool bar.
+	 * @param mapPane {@link JMapPane} to control (if {@code null} this
+	 *                tool bar controls NOTHING!)
+	 */
+	public void setMapPane(JMapPane mapPane) {
+	  // Remove listener from old MapPane
+	  if ( this.mapPane != null )
+	    this.mapPane.removeMapPaneListener( mapPaneListener );
+      this.mapPane = mapPane;
+      if ( this.mapPane != null && mapPaneListener != null )
+        this.mapPane.addMapPaneListener( mapPaneListener );
+	}
+	
+	/**
+	 * Calls {@link #initTools()} and {@link #initActions()} and then puts
+	 * all tool buttons and all actions buttons to the tool bar.
+	 */
+	protected void init() {
+	  initTools();
+	  initActions();
+	  initToolBar();
+	}
+
+
+	/**
+	 * Creates the tool buttons, adds them to {@link #toolButtons} and finally
+	 * creates a button group for all tools. So sub-classes which override this
+	 * method should FIRST add their new tool buttons to {@link #toolButtons}
+	 * before calling {@code super.initTools()}.
+	 */
+	protected void initTools() {
+      // Panning
+      addTool( new MapPaneToolBarAction(
+          TOOL_PAN,
+          this,
+          "",
+          new ImageIcon(MapView.class.getResource("pan.png"))
+      ), false );
+      // Info
+      addTool( new MapPaneToolBarAction(
+          TOOL_INFO, 
+          this, 
+          "",
+          new ImageIcon(MapView.class.getResource("info.png"))
+      ), false );
+      // Zoom in
+      addTool( new MapPaneToolBarAction(
+          TOOL_ZOOMIN,
+          this,
+          "",
+          new ImageIcon(MapView.class.getResource("zoom_in.png"))
+      ), false );
+      // Zoom out
+      addTool( new MapPaneToolBarAction(
+          TOOL_ZOOMOUT,
+          this,
+          "",
+          new ImageIcon(MapView.class.getResource("zoom_out.png"))
+      ), false );
+      
+	  // set the selected tool enabled
+      setSelectedTool(selectedTool);
+      
+	}
+
+    /**
+     * Creates the action buttons and adds them to {@link #actionButtons}.
+     */
+    protected void initActions() {
+      // Action button to revert the last zoom
+      addAction( new MapPaneToolBarAction(
+          ACTION_ZOOM_BACK,
+          this,
+          "",
+          new ImageIcon(MapView.class.getResource("zoom_back.png"))
+      ), false);
+      setButtonEnabled( ACTION_ZOOM_BACK, false );
+
+      // Action button to redo the last zoom
+      addAction( new MapPaneToolBarAction(
+          ACTION_ZOOM_FORWARD,
+          this,
+          "",
+          new ImageIcon(MapView.class.getResource("zoom_forward.png"))
+      ), false);
+      setButtonEnabled( ACTION_ZOOM_FORWARD, false );
+    }
+    
+    /**
+     * Clears the GUI of all components and adds all tool and action buttons to the
+     * tool bar.
+     */
+    protected void initToolBar() {
+      setAlignmentY( 1f );
+      removeAll();
+      // Separator to the left of the tool actions to start
+      // the tool buttons with the map (not with the coordinate grid)
+      Dimension dimension = new Dimension( 49,10);
+      addSeparator(dimension);
+      // Tool buttons
+      for (JToggleButton b : toolButtons.values())
+        add(b);
+      // Space between tool buttons and action buttons
+      Dimension dimension2 = new Dimension( 10,10);
+      this.addSeparator(dimension2);
+      // Action buttons
+      for (JButton b : actionButtons.values())
+        add(b);
+    }
+    
+	/**
+	 * Performs the activation of a tool.
+	 * @param tool the tool to activate
+	 * @param e    the event of the button
+	 */
+	public void performToolButton(int tool, ActionEvent e) {
+	  if ( mapPane == null )
+	    return;
+	  
+	  selectedTool = tool;
+	  
+      switch( tool ) {
+        case TOOL_PAN:
+          // Set the mouse tool to "Panning"
+          mapPane.setWindowSelectionState(JMapPane.NONE);
+          mapPane.setState(JMapPane.PAN);
+          mapPane.setHighlight(false);
+          mapPane.setNormalCursor(SwingUtil.PAN_CURSOR);
+          break;
+        case TOOL_INFO: 
+          // Set the mouse tool to "Info"
+          mapPane.setWindowSelectionState(JMapPane.NONE);
+          mapPane.setState(JMapPane.SELECT_TOP);
+          mapPane.setHighlight(true);
+          mapPane.setNormalCursor(SwingUtil.CROSSHAIR_CURSOR);
+          break;
+        case TOOL_ZOOMIN:
+          // Set the mouse tool to "Zoom in"
+          mapPane.setWindowSelectionState(JMapPane.ZOOM_IN);
+          mapPane.setState(JMapPane.ZOOM_IN);
+          mapPane.setHighlight(false);
+          mapPane.setNormalCursor(SwingUtil.ZOOMIN_CURSOR);
+          break;
+        case TOOL_ZOOMOUT:
+          // Set the mouse tool to "Zoom out"
+          mapPane.setWindowSelectionState(JMapPane.NONE);
+          mapPane.setState(JMapPane.ZOOM_OUT);
+          mapPane.setHighlight(false);
+          mapPane.setNormalCursor(SwingUtil.ZOOMOUT_CURSOR);
+          break;
+        default:
+          // Set map actions to default
+          mapPane.setWindowSelectionState(JMapPane.NONE);
+          mapPane.setState(JMapPane.NONE);
+          mapPane.setHighlight(false);
+          mapPane.setNormalCursor(null);
+          break;
+      }
+      mapPane.updateCursor();
+	}
+	
+    /**
+     * Performs the action of an action button.
+     * @param tool the action
+     * @param e    the event of the button
+     */
+	protected void performActionButton(int action, ActionEvent e) {
+      if ( mapPane == null )
+        return;
+
+      // Perform the action "Zoom back": Revert the last zoom
+      if ( action == ACTION_ZOOM_BACK ) {
+	    if (zoomBackIndex <= 1)
+          return;
+  
+        zoomBackForwardButtonInAction = true;
+        zoomBackIndex--;
+        getButton(ACTION_ZOOM_FORWARD).setEnabled(true);
+        getButton(ACTION_ZOOM_BACK).setEnabled( zoomBackIndex > 1 );
+  
+        mapPane.setMapArea( lastZooms.get(zoomBackIndex-1) );
+        mapPane.refresh();
+      }
+
+      // Perform the action "Zoom forward": Redo the last zoom
+      if ( action == ACTION_ZOOM_FORWARD ) {
+        if (zoomBackIndex < lastZooms.size()) {
+          zoomBackForwardButtonInAction = true;
+          zoomBackIndex++;
+          getButton(ACTION_ZOOM_BACK).setEnabled(true);
+          getButton(ACTION_ZOOM_FORWARD).setEnabled(zoomBackIndex < lastZooms.size());
+
+          mapPane.setMapArea( lastZooms.get(zoomBackIndex-1) );
+          mapPane.refresh();
+        }
+      }
+	}
+	
+	
+	/**
+	 * Adds a tool to the tool bar. Does nothing if a tool or action with the
+	 * specified ID already exists!
+	 * @param buttonAction action for the toggle button
+	 * @param resetToolBar indicates whether the toolbar GUI is reset after adding
+	 *                     the button (if adding several actions it useful only to
+	 *                     reset the GUI for the last added tool) 
+	 */
+	public void addTool(MapPaneToolBarAction buttonAction, boolean resetToolBar) {
+	  if ( isButtonIDUsed(buttonAction.getID()) ) {
+	    LOGGER.warn("addTool(.) ignored because ID already used for tool or action: "+buttonAction.getID());
+	    return;
+	  }
+	  JToggleButton button = new JToggleButton(buttonAction);
+	  button.setBorder( BorderFactory.createRaisedBevelBorder() );
+	  toolButtonGroup.add(button);
+	  toolButtons.put(buttonAction.getID(), button);
+	  if ( resetToolBar )
+	    initToolBar();
+	}
+
+    /**
+     * Adds a tool to the tool bar and resets the toolbar GUI.
+     * @param buttonAction action for the toggle button
+     */
+    public void addTool(MapPaneToolBarAction buttonAction) {
+      addTool(buttonAction, true);
+    }
+
+    /**
+     * Adds an action to the tool bar. Does nothing if a tool or action with the
+     * specified ID already exists!
+     * @param buttonAction action for the button
+     * @param resetToolBar indicates whether the toolbar GUI is reset after adding
+     *                     the button (if adding several actions it useful only to
+     *                     reset the GUI for the last added tool) 
+     */
+    public void addAction(MapPaneToolBarAction buttonAction, boolean resetToolBar) {
+      if ( isButtonIDUsed(buttonAction.getID()) ) {
+        LOGGER.warn("addAction(.) ignored because ID already used for tool or action: "+buttonAction.getID());
+        return;
+      }
+      JButton button = new JButton(buttonAction);
+      actionButtons.put( buttonAction.getID(), button );
+      if ( resetToolBar )
+        initToolBar();
+    }
+
+    /**
+     * Adds an action to the tool bar and resets the toolbar GUI.
+     * @param buttonAction action for the toggle button
+     */
+    public void addAction(MapPaneToolBarAction buttonAction) {
+      addAction(buttonAction, true);
+    }
+    
+    /**
+     * Returns the button for a specific tool or action.
+     * @param id the constant for a tool
+     * @return a {@link JButton} if {@code id} specifies an {@linkplain #getActionButton(int) action button}
+     *         or {@link JToogleButton} if {@code id} specifies a {@linkplain #getToolButton(int) tool button}
+     */
+    public AbstractButton getButton(int id) {
+      AbstractButton button = toolButtons.get(id);
+      if ( button == null )
+        button = actionButtons.get(id);
+      if ( button == null )
+        LOGGER.warn("Unknown tool or action ID: "+id);
+      return button;
+    }
+
+    /**
+     * Returns the button for a specific tool.
+     * @param tool the constant for a tool
+     */
+	public JToggleButton getToolButton(int tool) {
+      AbstractButton button = getButton(tool);
+      if ( button != null && !(button instanceof JToggleButton) ) {
+        LOGGER.warn("ID specifies no tool: "+tool);
+        button = null;
+      }
+      return (JToggleButton)button; 
+    }
+
+    /**
+     * Returns the button for a specific action.
+     * @param action the constant an action 
+     */
+    public JButton getActionButton(int action) {
+      AbstractButton button = getButton(action);
+      if ( button != null && !(button instanceof JButton) ) {
+        LOGGER.warn("ID specifies no action: "+action);
+        button = null;
+      }
+      return (JButton)button; 
+
+    }
+
+	/**
+	 * Sets the selected tool.
+	 * @param tool ID of the tool
+	 */
+	public void setSelectedTool(Integer tool) {
+	  if ( tool == null )
+	    toolButtonGroup.setUnselected();
+	  
+	  JToggleButton button = getToolButton(tool);
+	  if ( button == null )
+	    return;
+	  button.setSelected( true );
+	  button.getAction().actionPerformed(null);
+	}
+	
+	/**
+	 * Returns the selected tool.
+	 * @return -1 if no tool is active
+	 */
+	public int getSelectedTool() {
+	  if ( toolButtonGroup.getSelectedButton() == null )
+	    return -1;
+	  return selectedTool;
+	}
+	
+    /**
+     * Sets whether a tool or action is activated or not. The visible property
+     * of the button is not affected.
+     * @param id tool or actionID
+     * @param enabled if {@code true} the tool becomes available
+     */
+    public void setButtonEnabled(int id, boolean enabled) {
+      AbstractButton button = getButton(id);
+      if ( button == null )
+        return;
+      button.setEnabled( enabled );
+    }
+
+    /**
+     * Sets whether a tool or action is activated or not.
+     * @param id tool or actionID
+     * @param enabled if {@code true} the tool becomes available
+     * @param hideOnDisable if {@code true} the button is also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+	public void setButtonEnabled(int id, boolean enabled, boolean hideOnDisable) {
+	  AbstractButton button = getButton(id);
+	  if ( button == null )
+	    return;
+	  button.setEnabled( enabled );
+	  // if button is enabled, it becomes visible anyway
+	  // if button is disabled and the "hide" option is set, it is also hidden 
+	  if ( enabled )
+	    button.setVisible( true );
+	  else
+	    button.setVisible( !hideOnDisable );
+	}
+
+    /**
+     * Checks whether a ID is already used for a tool or action.
+     * @param tool tool ID
+     */
+    public boolean isButtonIDUsed(int id) {
+      return toolButtons.get(id) != null || actionButtons.get(id) != null;
+    }
+
+    /**
+     * Checks whether a tool is activated.
+     * @param tool tool ID
+     * @return {@code false} if an unknown ID is specified
+     */
+    public boolean isButtonEnabled(int id) {
+      AbstractButton button = getButton(id);
+      if ( button != null )
+        return button.isEnabled();
+      return false;
+    }
+
+    /**
+     * Sets the activation for all tools.
+     * @param enabled if {@code true} all tools becomes available
+     * @param hideOnDisable if {@code true} the buttons are also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+    public void setAllToolsEnabled(boolean enabled, boolean hideOnDisable) {
+      for (int tool : toolButtons.keySet())
+        setButtonEnabled(tool,enabled,hideOnDisable);
+    }	
+
+    /**
+     * Sets the activation for all actions.
+     * @param enabled if {@code true} all actions becomes available
+     * @param hideOnDisable if {@code true} the buttons are also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+    public void setAllActionsEnabled(boolean enabled, boolean hideOnDisable) {
+      for (int tool : actionButtons.keySet())
+        setButtonEnabled(tool,enabled,hideOnDisable);
+    }   
+    
+    /**
+     * Returns the maximum ID of tools. 
+     */
+    public int getMaxToolID() {
+      return toolButtons.lastKey();
+    }
+
+    /**
+     * Returns the minimum ID of tools. 
+     */
+    public int getMinToolID() {
+      return toolButtons.firstKey();
+    }
+
+    /**
+     * Returns the maximum ID of actions. 
+     */
+    public int getMaxActionID() {
+      return actionButtons.lastKey();
+    }
+
+    /**
+     * Returns the minimum ID of actions. 
+     */
+    public int getMinActionID() {
+      return actionButtons.firstKey();
+    }
+    
+    /**
+     * Extends the {@link AbstractAction} with maintaining an ID and
+     * the {@link MapPaneToolBar} the actions controls.
+     * Additionally this class automatically calls {@link MapPaneToolBar#performToolButton(int, ActionEvent)}
+     * or {@link MapPaneToolBar#performActionButton(int, ActionEvent)}
+     * depending on whether the action is added via {@link MapPaneToolBar#addTool(MapPaneToolBarAction)}
+     * or {@link MapPaneToolBar#addAction(MapPaneToolBarAction)}.
+     * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+     */
+    public static class MapPaneToolBarAction extends AbstractAction {
+      /** The ID of the action */
+      protected int id = -1;
+      /** The tool bar, this action is made for. */
+      protected MapPaneToolBar toolBar = null;
+
+      /**
+       * Creates a new action with a dummy description and no icon.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       */
+      public MapPaneToolBarAction(int id, MapPaneToolBar toolBar) {
+        this(id,toolBar,""+id);
+      }
+
+      /**
+       * Creates a new action without an icon.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       * @param name    description used for buttons or menus 
+       */
+      public MapPaneToolBarAction(int id, MapPaneToolBar toolBar, String name) {
+        this(id,toolBar,name,null);
+      }
+
+      /**
+       * Creates a new action.
+       * @param id      unique ID for the action
+       * @param toolBar toolbar this action is made for
+       * @param name    description used for buttons or menus 
+       * @param icon    icon used for buttons or menus 
+       */
+      public MapPaneToolBarAction(int id, MapPaneToolBar toolBar, String name, Icon icon) {
+        super(name,icon);
+        this.id      = id;
+        this.toolBar = toolBar;
+      }
+
+      /**
+       * Calls {@link MapPaneToolBar#performToolButton(int, ActionEvent)}
+       * or {@link MapPaneToolBar#performActionButton(int, ActionEvent)}
+       * depending on whether the action is added to the toolbar via
+       * {@link MapPaneToolBar#addTool(MapPaneToolBarAction)}
+       * or {@link MapPaneToolBar#addAction(MapPaneToolBarAction)}.
+       */
+      public void actionPerformed(ActionEvent e) {
+        if ( toolBar.toolButtons.get(id) != null )
+          toolBar.performToolButton(id, e);
+        if ( toolBar.actionButtons.get(id) != null )
+          toolBar.performActionButton(id, e);
+      }
+      
+      /**
+       * Returns the (unique) id of this action.
+       * @return
+       */
+      public int getID() {
+        return id;
+      }
+    }
+}

Added: trunk/src/skrueger/geotools/MapView.java
===================================================================
--- trunk/src/skrueger/geotools/MapView.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/MapView.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,406 @@
+package skrueger.geotools;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.geotools.gui.GeoMapPane;
+import schmitzm.geotools.gui.JMapPane;
+import schmitzm.geotools.gui.MapContextControlPane;
+import schmitzm.geotools.gui.MapPaneStatusBar;
+import schmitzm.geotools.map.event.JMapPaneEvent;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapAreaChangedEvent;
+import schmitzm.geotools.styling.ColorMapManager;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * Achtung! Dieser code ist verwuestet
+ */
+
+/**
+ * TODO DOKU und initialize schöner machen. SK
+ */
+public class MapView extends JPanel {
+	private static final Logger LOGGER = Logger.getLogger(MapView.class);
+
+	private final JSplitPane splitPane = new JSplitPane(
+			JSplitPane.HORIZONTAL_SPLIT);
+
+	protected MapPaneStatusBar statusBar = null;
+	
+	/**
+	 * Komponente, in der die Karten, der Massstab und das Koordinaten-Raster
+	 * dargestellt werden
+	 */
+	protected final GeoMapPane geoMapPane = new GeoMapPane();
+
+//
+//	/** This is the layered Pane that holds the buttons and maybe the flying logo * */
+//	protected JLayeredPane layeredPane;
+
+	protected boolean zoomBackForwardButtonInAction;
+
+	private MapPaneToolBar jToolBar;
+
+	/**
+	 * Creates a new {@link MapView}. A {@link MapView} is a combination of a
+	 * {@link GeoMapPane}, a {@link MapContextManagerInterface} on the left,
+	 * and some buttons floating over the {@link JMapPane}
+	 */
+	public MapView(Window owner2, MapPaneToolBar toolBar) {
+		super(new BorderLayout());
+		// Call initialize() by yourself afterwards.
+		// Needed because variables for the overwritten methods
+		// are not yet set.
+		getGeoMapPane().getMapPane().setWaitCursorComponent(owner2);
+		if ( toolBar == null )
+		  toolBar = new MapPaneToolBar(getMapPane());
+		jToolBar = toolBar; 
+	}
+
+    /**
+     * Creates a new {@link MapView}. A {@link MapView} is a combination of a
+     * {@link GeoMapPane}, a {@link MapContextManagerInterface} on the left,
+     * and some buttons floating over the {@link JMapPane}
+     */
+    public MapView(Window owner2) {
+      this(owner2, null);
+    }
+
+    // /**
+	// * Nuetzlich wenn die Componente gedruckt wird. Dann wird der Hintergrund
+	// auf {@link Color#WHITE} gesetzt.zoomoutToggleButton
+	// *
+	// * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+	// Kr&uuml;ger</a>
+	// */
+	// @Override
+	// public void print(Graphics g) {
+	//		
+	// // SK: Ich habe keine Ahnung mehr, was das soll... Ich habs mal
+	// auskommentiert.
+	//
+	// // wrap in try/finally so that we always restore the state
+	// try {
+	// panToggleButton.setVisible(false);
+	// zoominToggleButton.setVisible(false);
+	// zoomoutToggleButton.setVisible(false);
+	// infoToggleButton.setVisible(false);
+	//
+	// super.print(g);
+	// } finally {
+	// panToggleButton.setVisible(true);
+	// zoominToggleButton.setVisible(true);
+	// zoomoutToggleButton.setVisible(true);
+	// infoToggleButton.setVisible(true);
+	// }
+	// }
+
+	/**
+	 * Calls #getSidePanel() which can be overwritten.
+	 * 
+	 * @see #adjustSizeOfGeoMapPane()
+	 */
+	public void initialize() {
+		// horizontales SplitPane initialisieren
+
+		// Status-Zeile
+		statusBar = new MapPaneStatusBar(getGeoMapPane()
+				.getMapPane());
+		statusBar.setBorder(BorderFactory.createCompoundBorder(BorderFactory
+				.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(2,
+				5, 2, 5)));
+		this.add(statusBar, BorderLayout.SOUTH);
+
+		splitPane.setDividerLocation(0.4);
+		splitPane.setDividerSize(5);
+
+		splitPane.add(getSidePane());
+
+		/***********************************************************************
+		 * To the right side we now add a JPanel that consists of a toolbar and
+		 * a gmp
+		 */
+		JPanel newRight = new JPanel(new BorderLayout());
+		newRight.add(getToolBar(), BorderLayout.NORTH);
+		newRight.add(getGeoMapPane(), BorderLayout.CENTER);
+		splitPane.add(newRight);
+
+		// gpSize = new Dimension(40,50);
+		// getGeoMapPane().setSize(gpSize);
+
+		// layeredPane = new JLayeredPane();
+		// layeredPane.add(getGeoMapPane(), JLayeredPane.DEFAULT_LAYER);
+		// createMapButtons(layeredPane);
+
+		// //****************************************************************************
+		// // The JLayeredPane doesn't manage the size of its layers.. we do it
+		// // This listener will actually correct the size of the JLayeredPane
+		// //****************************************************************************
+		// splitPane.addPropertyChangeListener(new PropertyChangeListener() {
+		//
+		// public void propertyChange(final PropertyChangeEvent evt) {
+		// final JSplitPane sp = (JSplitPane) evt.getSource();
+		//
+		// adjustSizeOfGeoMapPane();
+		//
+		// }
+		//
+		// });
+
+		this.add(splitPane, BorderLayout.CENTER);
+	}
+
+	/**
+	 * Returns the tool bar which controls the active mouse actions on the map.
+	 * @return
+	 */
+	public MapPaneToolBar getToolBar() {
+  	  return jToolBar;
+	}
+
+	/**
+	 * Returns the split pane which divides the layer list from the map panel.
+	 */
+	public JSplitPane getSplitPane() {
+		return this.splitPane;
+	}
+
+	//
+	// /**
+	// * Called by the listerner. Calculates the exact size of the {@link
+	// GeoMapPane} and sets it.
+	// */
+	// private void adjustSizeOfGeoMapPane() {
+	// final int newWidth = (int) (splitPane.getSize().getWidth()
+	// - splitPane.getComponent(1).getSize().getWidth()
+	// - splitPane.getComponent(0).getSize().getWidth())
+	// - 4;
+	// final int newHeight = (int) splitPane.getComponent(1).getSize()
+	// .getHeight();
+	//
+	// if (newWidth <= 20 )
+	// return;
+	//
+	// gpSize.setSize(newWidth, newHeight);
+	//
+	// getGeoMapPane().getMapPane().setWaitCursorDuringNextRepaint(true);
+	//
+	// getGeoMapPane().setSize( gpSize );
+	// // getGeoMapPane().refreshMap(); nicht nötig .. schön
+	//
+	// positionLayeredObjects();
+	// }
+//
+//	/**
+//	 * Adds the floating Map Contol Buttons to the {@link JLayeredPane}, with Z =
+//	 * 10
+//	 * 
+//	 * @param lp
+//	 *            {@link JLayeredPane} that contains the {@link GeoMapPane}
+//	 */
+//	private void createMapButtons(final JLayeredPane lp) {
+//
+//		final Integer zlayer = new Integer(10);
+//
+//		toggleButtonsList = new LinkedList<JToggleButton>();
+//		toggleButtonsList.add(infoToggleButton);
+//		toggleButtonsList.add(panToggleButton);
+//		toggleButtonsList.add(zoominToggleButton);
+//		toggleButtonsList.add(zoomoutToggleButton);
+//
+//
+//		positionLayeredObjects();
+//
+//		final ButtonGroup bg = new ButtonGroup();
+//		for (final JToggleButton b : toggleButtonsList) {
+//			// final FontMetrics fm = b.getFontMetrics( b.getFont() );
+//			// final int w = fm.stringWidth( b.getText() );
+//
+//			// b.setBorder( BorderFactory.createRaisedBevelBorder() );
+//			b.setBorder(BorderFactory.createEmptyBorder());
+//			b.setSize(new Dimension(32, 32));
+//			bg.add(b);
+//			lp.add(b, zlayer);
+//		}
+//
+//		zoomBackButton.setSize(32, 32);
+//		zoomBackButton.setBorder(BorderFactory.createEmptyBorder());
+//		lp.add(zoomBackButton, zlayer);
+//
+//		zoomForwardButton.setSize(32, 32);
+//		zoomForwardButton.setBorder(BorderFactory.createEmptyBorder());
+//		lp.add(zoomForwardButton, zlayer);
+//	}
+
+//	/**
+//	 * This method will update the
+//	 */
+//	public void updateIconsForButtons() {
+//		if (zoomoutToggleButton.isSelected())
+//			zoomoutToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("zoom_out.png")));
+//		else
+//			zoomoutToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("zoom_out.png")));
+//
+//		if (zoominToggleButton.isSelected())
+//			zoominToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("zoom_in.png")));
+//		else
+//			zoominToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("zoom_in.png")));
+//
+//		if (infoToggleButton.isSelected())
+//			infoToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("info.png")));
+//		else
+//			infoToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("info_aus.png")));
+//
+//		if (panToggleButton.isSelected())
+//			panToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("pan.png")));
+//		else
+//			panToggleButton.setIcon(new ImageIcon(MapView.class
+//					.getResource("pan_aus.png")));
+//
+//	}
+
+	/**
+	 * Sets the active tool.
+	 * Simply calls {@link MapPaneToolBar#setSelectedTool(Integer)}.
+	 * @param tool
+	 *            One of {@link #TOOL_INFO}, {@link #TOOL_PAN} .. constants
+	 */
+	public void setSelectedTool(Integer tool) {
+	  jToolBar.setSelectedTool(tool);
+	}
+	
+    /**
+     * Sets whether a tool is activated or not.
+     * Simply calls {@link MapPaneToolBar#setButtonEnabled(int, boolean, boolean)}.
+     * @param tool tool ID
+     * @param enabled if {@code true} the tool becomes available
+     * @param hideOnDisable if {@code true} the button is also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+	public void setToolEnabled(Integer tool, boolean enabled, boolean hideOnDisable) {
+	   jToolBar.setButtonEnabled(tool,enabled,hideOnDisable);
+	}
+
+    /**
+     * Sets the activation for all tools.
+     * Simply calls {@link MapPaneToolBar#setAllToolsEnabled(boolean, boolean)}.
+     * @param enabled if {@code true} all tool becomes available
+     * @param hideOnDisable if {@code true} the buttons are also hidden if
+     *                      {@code enabled} is {@code false}
+     */
+    public void setAllToolsEnabled(boolean enabled, boolean hideOnDisable) {
+      jToolBar.setAllToolsEnabled(enabled, hideOnDisable);
+    }	
+
+    /**
+	 * Checks whether a tool is activated.
+     * Simply calls {@link MapPaneToolBar#isButtonEnabled(Integer)}.
+	 * @param tool tool ID
+	 * @return {@code false} if an unknown ID is specified
+	 */
+	public boolean isToolEnabled(Integer tool) {
+	  return jToolBar.isButtonEnabled(tool);
+	}
+//
+//	/**
+//	 * Positions the Map Control Buttons on top of the {@link GeoMapPane}
+//	 * 
+//	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+//	 * 
+//	 */
+//	protected void positionLayeredObjects() {
+//
+//		// Abstand zwischen den Buttons und Button zu Rand
+//		final int gap = 10;
+//
+//		// 45 entspicht der breite des Grids/ der Gradanzeige
+//		// TODO den wert aus der geomappane auslesen...
+//		int x = GridPanel.VERT_WIDTH;
+//
+//		for (final JToggleButton b : toggleButtonsList) {
+//			x += gap;
+//			// final FontMetrics fm = b.getFontMetrics( b.getFont() );
+//			b.setLocation(x, 10);
+//
+//			final int w = 32;
+//			x += w;
+//			// y += b.getWidth();
+//		}
+//
+//		// Und noch die zwei ZoomBackup und ZoomForward Buttons
+//		zoomBackButton.setLocation(x + 32 + gap, 10);
+//		zoomForwardButton.setLocation(x + 64 + gap * 2, 10);
+//	}
+
+	/**
+	 * called by initialize() to fill the left of the XULUMapView Supposed to be
+	 * overwritten by AtlasMapView or DesignMapView
+	 */
+	public JComponent getSidePane() {
+		return new MapContextControlPane(getGeoMapPane().getMapPane(),
+				new ColorMapManager());
+	}
+
+	/**
+	 * Liefert die Status-Zeile, in der die Koordinaten und Raster-Werte
+	 * angezeigt werden. 
+	 */
+	public MapPaneStatusBar getStatusBar() {
+	  return this.statusBar;
+	}
+	
+	/**
+	 * Liefert den Karten-Bereich der Komponente.
+	 */
+	public final JMapPane getMapPane() {
+		return getGeoMapPane().getMapPane();
+	}
+
+	// /**
+	// * Liefert das in einem Layer dargestellte Objekt.
+	// *
+	// * @param layer
+	// * ein Layer
+	// */
+	// public Object getMapObject(final MapLayer layer) {
+	// // return layerObjects.get(layer);
+	// return null;
+	// }
+
+	public GeoMapPane getGeoMapPane() {
+		return geoMapPane;
+	}
+
+	public int getSelectedTool() {
+		return jToolBar.getSelectedTool();
+	}
+
+}

Added: trunk/src/skrueger/geotools/StyledDataStoreInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledDataStoreInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledDataStoreInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,18 @@
+//package skrueger.geotools;
+//
+//import java.util.Map;
+//
+//import org.geotools.data.DataStore;
+//import org.geotools.feature.FeatureCollection;
+//
+//import skrueger.AttributeMetaData;
+//
+///**
+// * {@link StyledMapInterface} which contains a {@link DataStore} as geo object.<br>
+// * @deprecated changing to StyledFeatureSourceInterface
+// */
+//public interface StyledDataStoreInterface extends StyledMapInterface<DataStore> {
+//
+//	public abstract Map<Integer,AttributeMetaData> getAttributeMetaDataMap();
+//
+//}

Added: trunk/src/skrueger/geotools/StyledFS.java
===================================================================
--- trunk/src/skrueger/geotools/StyledFS.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledFS.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,215 @@
+package skrueger.geotools;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+import org.geotools.data.FeatureSource;
+import org.geotools.feature.AttributeType;
+import org.geotools.styling.Style;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.styling.StylingUtil;
+import skrueger.AttributeMetaData;
+import skrueger.i8n.Translation;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+/**
+ * This class enables a non Atlas context to use the Atlas LayerPanel
+ * {@link JPanel} as a {@link MapContextManagerInterface}
+ * 
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+public class StyledFS implements StyledFeatureSourceInterface {
+	private static final Logger LOGGER = Logger.getLogger(StyledFS.class);
+
+	private final FeatureSource fs;
+
+	/**
+	 * A unique ID which identifies the Layer in the Atlas. It's more important
+	 * than it should be ;-)
+	 */
+	private String id;
+
+	private Style style;
+
+	private Translation title;
+
+	private Translation desc;
+
+	private File sldFile;
+
+	private HashMap<Integer, AttributeMetaData> map;
+
+	/**
+	 * This class enables a non Atlas context to use the Atlas LayerPanel
+	 * {@link JPanel} as a {@link MapContextManagerInterface}
+	 * 
+	 * @param fs
+	 *            {@link FeatureSource} that is beeing styled.
+	 * 
+	 * @param sldFile
+	 *            may be <code>null</code>. Otherwise the SLD {@link File} to
+	 *            import and associate with this {@link StyledFS}
+	 */
+	public StyledFS(FeatureSource fs, File sldFile) {
+
+		super();
+		this.fs = fs;
+		id = StyledFS.class.getSimpleName()
+				+ new Random(new Date().getTime()).nextInt(10000000);
+
+		this.sldFile = sldFile;
+
+		if ((sldFile != null) && (sldFile.exists())) {
+			try {
+				style = StylingUtil.loadSLD(sldFile)[0];
+			} catch (FileNotFoundException e) {
+				LOGGER
+						.debug("The SLD file passed was empty. Leaving the Style untouched. (We are in the constructor.. so its null");
+			}
+		}
+
+		title = new Translation();
+		title.fromOneLine(sldFile.getName());
+
+		desc = new Translation();
+		desc.fromOneLine(sldFile.getAbsolutePath());
+	}
+
+	public void dispose() {
+	}
+
+	/**
+	 * Returnes human readable {@link String} of the CRS natively used by this
+	 * {@link DpLayer}
+	 * 
+	 * If crs == null, it will call {@link #getGeoObject()}
+	 * 
+	 */
+	public String getCRSString() {
+		if (getCrs() == null)
+			return "CRS?";
+
+		return getCrs().getName().getCode();
+	}
+
+	public CoordinateReferenceSystem getCrs() {
+		return fs.getSchema().getDefaultGeometry().getCoordinateSystem();
+	}
+
+	public Translation getDesc() {
+		return desc;
+	}
+
+	public Envelope getEnvelope() {
+		try {
+			return fs.getBounds();
+		} catch (IOException e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	public FeatureSource getGeoObject() throws Exception {
+		return fs;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public ImageIcon getImageIcon() {
+		return null;
+	}
+
+	public URL getInfoURL() {
+		return null;
+	}
+
+	public Translation getKeywords() {
+		return null;
+	}
+
+	public Style getStyle() {
+		return style;
+	}
+
+	public Translation getTitle() {
+		return title;
+	}
+
+	public boolean isDisposed() {
+		return false;
+	}
+
+	public boolean isHideInLegend() {
+		return false;
+	}
+
+	public void setDesc(Translation dec) {
+		this.desc = dec;
+	}
+
+	public void setImageIcon(ImageIcon icon) {
+		// TODO Auto-generated method stub
+
+	}
+
+	public void setKeywords(Translation keywords) {
+	}
+
+	public void setStyle(Style style) {
+		this.style = style;
+
+	}
+
+	public void setTitle(Translation title) {
+		this.title = title;
+
+	}
+
+	public void uncache() {
+	}
+
+	public Map<Integer, AttributeMetaData> getAttributeMetaDataMap() {
+		if (map == null) {
+			map = new HashMap<Integer, AttributeMetaData>();
+			
+			// Leaving out the first one, it will be the_geom
+			for (int i = 1; i < fs.getSchema().getAttributeCount(); i++) {
+				AttributeType att = fs.getSchema().getAttributeType(i);
+				
+				AttributeMetaData attMetaData = new AttributeMetaData(i, att.getLocalName());
+				map.put(i, attMetaData);
+			}
+		}
+		return map;
+	}
+
+	/**
+	 * @return The {@link File} where the SLD was loaded from or
+	 *         <code>null</code> if there didn't exist a {@link File}. (It
+	 *         could be a WFS or a PostGIS
+	 * 
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public File getSldFile() {
+		return sldFile;
+	}
+
+	public void setSldFile(File sldFile) {
+		this.sldFile = sldFile;
+	}
+
+}

Added: trunk/src/skrueger/geotools/StyledFeatureCollection.java
===================================================================
--- trunk/src/skrueger/geotools/StyledFeatureCollection.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledFeatureCollection.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,246 @@
+package skrueger.geotools;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.HashMap;
+import javax.swing.ImageIcon;
+
+import org.geotools.styling.Style;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureType;
+import org.geotools.feature.AttributeType;
+
+import schmitzm.geotools.feature.FeatureUtil;
+
+import skrueger.i8n.Translation;
+import skrueger.AttributeMetaData;
+
+
+/**
+ * This class provides a simple implementation of {@link StyledMapInterface}
+ * for {@link FeatureCollection}. The uncache functionality is not supported,
+ * because this class bases on an existing {@link FeatureCollection} object in
+ * memory.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StyledFeatureCollection extends AbstractStyledMap<FeatureCollection> implements StyledFeatureCollectionInterface {
+
+  /** Holds the meta data for displaying a legend. */
+  protected Map<Integer,AttributeMetaData> attrMetaData = null;
+
+  /**
+   * Creates a styled {@link FeatureCollection} with language-specific informations.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param attrMetaData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, Translation title, Translation desc, Translation keywords, Style style, Map<Integer,AttributeMetaData> attrMetaData, ImageIcon icon) {
+    super(fc, fc.getBounds(), fc.getSchema().getDefaultGeometry().getCoordinateSystem(), id, title, desc, keywords, style, icon);
+    setAttributeMetaData( attrMetaData );
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with language-specific informations.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style with attribute meta data information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, Translation title, Translation desc, Translation keywords, StyledMapStyle<Map<Integer,AttributeMetaData>> style, ImageIcon icon) {
+    super(fc, fc.getBounds(), fc.getSchema().getDefaultGeometry().getCoordinateSystem(), id, title, desc, keywords, style != null ? style.getGeoObjectStyle() : null, icon);
+    setAttributeMetaData( style != null ? style.getMetaData() : null );
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with a language-specific title,
+   * no long description, no keywords, default attribute meta data and no icon.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   * @see #createDefaultAttributeMetaDataMap(FeatureCollection)
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, Translation title, Style style) {
+    this(fc, id, title, null, null, style, null, null);
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with non-translated informations.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param attrMetaData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, String title, String desc, String keywords, Style style, Map<Integer,AttributeMetaData> attrMetaData, ImageIcon icon) {
+    this(fc, id, (Translation)null, null, null, style, attrMetaData, icon);
+    setTitle(title);
+    setDesc(desc);
+    setKeywords(keywords);
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with non-translated informations.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style with attribute meta data information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, String title, String desc, String keywords, StyledMapStyle<Map<Integer,AttributeMetaData>> style, ImageIcon icon) {
+    this(fc,
+         id,
+         title,
+         desc,
+         keywords,
+         style != null ? style.getGeoObjectStyle() : null,
+         style != null ? style.getMetaData() : null,
+         icon
+    );
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with a non-translated title,
+   * no long description, no keywords, default attribute meta data and no icon.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   * @see #createDefaultAttributeMetaDataMap(FeatureCollection)
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, String title, Style style) {
+    this(fc, id, title, null, null, style, null, null);
+  }
+
+  /**
+   * Creates a styled {@link FeatureCollection} with a non-translated title,
+   * no long description, no keywords, default attribute meta data and no icon.
+   * @param fc the {@link FeatureCollection}
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   * @see #createDefaultAttributeMetaDataMap(FeatureCollection)
+   */
+  public StyledFeatureCollection(FeatureCollection fc, String id, String title, StyledMapStyle<Map<Integer,AttributeMetaData>> style) {
+    this(
+      fc,
+      id,
+      title,
+      null,
+      null,
+      style != null ? style.getGeoObjectStyle() : null,
+      style != null ? style.getMetaData() : null,
+      null
+    );
+  }
+
+  /**
+   * Creates a default style for the {@link FeatureCollection}.
+   * @see FeatureUtil#createDefaultStyle(FeatureCollection)
+   */
+  protected Style createDefaultStyle() {
+    return FeatureUtil.createDefaultStyle( geoObject );
+  }
+
+  /**
+   * Returns the meta data needed for displaying a legend.
+   */
+  public Map<Integer,AttributeMetaData> getAttributeMetaDataMap() {
+    return attrMetaData;
+  }
+
+  /**
+   * Sets the meta data needed for displaying a legend.
+   * If {@code legendData} is {@code null} an empty map is set, so
+   * {@link #getAttributeMetaDataMap()} never returns {@code null}.
+   * @param attrMetaData map of attribute meta data
+   */
+  public void setAttributeMetaData(Map<Integer,AttributeMetaData> attrMetaData) {
+    this.attrMetaData = (attrMetaData != null) ? attrMetaData : createDefaultAttributeMetaDataMap(geoObject);
+  }
+
+  /**
+   * Creates non-translated default meta data for a {@link FeatureCollection}
+   * with all attributes visible and no unit set.
+   * @param fc a {@link FeatureCollection}
+   */
+  public static Map<Integer,AttributeMetaData> createDefaultAttributeMetaDataMap(FeatureCollection fc) {
+    HashMap<Integer,AttributeMetaData> metaDataMap = new HashMap<Integer,AttributeMetaData>();
+    FeatureType ftype = fc.getSchema();
+    for (int i=0; i<ftype.getAttributeCount(); i++) {
+      AttributeType aType = ftype.getAttributeType(i);
+      if ( aType != ftype.getDefaultGeometry() )
+        metaDataMap.put(
+          i,
+          new AttributeMetaData(
+            i,  // Column no.
+            true, // visible
+            new Translation( aType.getName() ), // Column name
+            new Translation(), // description
+            "" // Unit
+          )
+        );
+    }
+    return metaDataMap;
+  }
+
+  /**
+   * Simply sets the {@link #geoObject}, {@link #crs}, {@link #envelope} and
+   * {@link #attrMetaData} to {@code null}.
+   */
+  public void dispose() {
+    this.geoObject    = null;
+    this.envelope     = null;
+    this.crs          = null;
+    this.attrMetaData = null;
+  }
+
+  /**
+   * Tests whether the geo object is disposed.
+   */
+  public boolean isDisposed() {
+    return geoObject == null;
+  }
+
+  /**
+   * Does nothing, because the {@link AbstractStyledMap} bases on existing
+   * objects (in memory) which can not be uncached and reloaded.
+   */
+  public void uncache() {
+    LOGGER.warn("Uncache functionality is not supported. Object remains in memory.");
+  }
+
+
+  /*
+   * (non-Javadoc)
+   * @see skrueger.geotools.StyledMapInterface#getInfoURL()
+   */
+	public URL getInfoURL() {
+		return null;
+	}
+
+	public boolean isHideInLegend() {
+		return false;
+	}
+}

Added: trunk/src/skrueger/geotools/StyledFeatureCollectionInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledFeatureCollectionInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledFeatureCollectionInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,16 @@
+package skrueger.geotools;
+
+import java.util.Map;
+
+import org.geotools.feature.FeatureCollection;
+
+import skrueger.AttributeMetaData;
+
+/**
+ * {@link StyledMapInterface} which contains a {@link FeatureCollection} as geo object.<br>
+ */
+public interface StyledFeatureCollectionInterface extends StyledMapInterface<FeatureCollection> {
+
+	public abstract Map<Integer,AttributeMetaData> getAttributeMetaDataMap();
+
+}

Added: trunk/src/skrueger/geotools/StyledFeatureSourceInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledFeatureSourceInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledFeatureSourceInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,16 @@
+package skrueger.geotools;
+
+import java.util.Map;
+
+import org.geotools.data.FeatureSource;
+
+import skrueger.AttributeMetaData;
+
+/**
+ * {@link StyledMapInterface} which contains a {@link FeatureSource} as geo object.<br>
+ */
+public interface StyledFeatureSourceInterface extends StyledMapInterface<FeatureSource> {
+
+	public abstract Map<Integer,AttributeMetaData> getAttributeMetaDataMap();
+
+}

Added: trunk/src/skrueger/geotools/StyledGridCoverage.java
===================================================================
--- trunk/src/skrueger/geotools/StyledGridCoverage.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledGridCoverage.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,216 @@
+package skrueger.geotools;
+
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import org.geotools.styling.Style;
+import org.geotools.coverage.grid.GridCoverage2D;
+
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.grid.GridUtil;
+
+import skrueger.i8n.Translation;
+import skrueger.RasterLegendData;
+
+/**
+ * This class provides a simple implementation of {@link StyledMapInterface}
+ * for {@link GridCoverage2D}. The uncache functionality is not supported,
+ * because this class bases on an existing {@link GridCoverage2D} object in
+ * memory.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StyledGridCoverage extends AbstractStyledMap<GridCoverage2D> implements StyledGridCoverageInterface {
+
+  /** Holds the meta data for displaying a legend. */
+  protected RasterLegendData legendData = null;
+
+  /**
+   * Creates a styled grid with language-specific informations.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, Translation title, Translation desc, Translation keywords, Style style, RasterLegendData legendData, ImageIcon icon) {
+    super(gc, JTSUtil.createEnvelope(gc.getEnvelope()), gc.getCoordinateReferenceSystem(), id, title, desc, keywords, style, icon);
+    setLegendMetaData(legendData);
+  }
+
+  /**
+   * Creates a styled grid with language-specific informations.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style with legend information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, Translation title, Translation desc, Translation keywords, StyledMapStyle<RasterLegendData> style, ImageIcon icon) {
+    super(gc, JTSUtil.createEnvelope(gc.getEnvelope()), gc.getCoordinateReferenceSystem(), id, title, desc, keywords, style != null ? style.getGeoObjectStyle() : null, icon);
+    setLegendMetaData( style != null ? style.getMetaData() : null );
+  }
+
+  /**
+   * Creates a styled grid with a language-specific title, no long description, no
+   * keywords and no icon.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, Translation title, Style style, RasterLegendData legendData) {
+    this(gc, id, title, null, null, style, legendData, null);
+  }
+
+  /**
+   * Creates a styled grid with non-translated informations.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, String title, String desc, String keywords, Style style, RasterLegendData legendData, ImageIcon icon) {
+    this(gc, id, (Translation)null, null, null, style, legendData, icon);
+    setTitle(title);
+    setDesc(desc);
+    setKeywords(keywords);
+  }
+
+  /**
+   * Creates a styled grid with non-translated informations.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style with legend information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, String title, String desc, String keywords, StyledMapStyle<RasterLegendData> style, ImageIcon icon) {
+    this(gc,
+         id,
+         title,
+         desc,
+         keywords,
+         style != null ? style.getGeoObjectStyle() : null,
+         style != null ? style.getMetaData() : null,
+         icon
+    );
+  }
+
+  /**
+   * Creates a styled grid with a non-translated title, no long description, no
+   * keywords and no icon.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, String title, Style style, RasterLegendData legendData) {
+    this(gc, id, title, null, null, style, legendData, null);
+  }
+
+  /**
+   * Creates a styled grid with a non-translated title, no long description, no
+   * keywords and no icon.
+   * @param gc the grid
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style with legend information
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverage(GridCoverage2D gc, String id, String title, StyledMapStyle<RasterLegendData> style) {
+    this(gc,
+         id,
+         title,
+         null,
+         null,
+         style != null ? style.getGeoObjectStyle() : null,
+         style != null ? style.getMetaData() : null,
+         null
+    );
+  }
+
+  /**
+   * Creates a default style for a {@link GridCoverage2D}.
+   * @see GridUtil#createDefaultStyle()
+   */
+  protected Style createDefaultStyle() {
+    return GridUtil.createDefaultStyle();
+  }
+
+  /**
+   * Returns the meta data needed for displaying a legend.
+   */
+  public RasterLegendData getLegendMetaData() {
+    return legendData;
+  }
+
+  /**
+   * Sets the meta data needed for displaying a legend.
+   * If {@code legendData} is {@code null} an empty {@link RasterLegendData}
+   * (without gaps) is set, so {@link #getLegendMetaData()} never returns {@code null}.
+   * @param legendData legend meta data
+   */
+  public void setLegendMetaData(RasterLegendData legendData) {
+    this.legendData = (legendData != null) ? legendData : new RasterLegendData(false);
+  }
+
+  /**
+   * Simply sets the {@link #geoObject}, {@link #crs}, {@link #envelope} and
+   * {@link #legendData} to {@code null}.
+   */
+  public void dispose() {
+    this.geoObject  = null;
+    this.envelope   = null;
+    this.crs        = null;
+    this.legendData = null;
+  }
+
+  /**
+   * Tests whether the geo object is disposed.
+   * @return boolean
+   */
+  public boolean isDisposed() {
+    return geoObject == null;
+  }
+
+  /**
+   * Does nothing, because the {@link AbstractStyledMap} bases on existing
+   * objects (in memory) which can not be uncached and reloaded.
+   */
+  public void uncache() {
+    LOGGER.warn("Uncache functionality is not supported. Object remains in memory.");
+  }
+
+  /*
+   * (non-Javadoc)
+   * @see skrueger.geotools.StyledMapInterface#getInfoURL()
+   */
+  public URL getInfoURL() {
+	return null;
+  }
+
+  public boolean isHideInLegend() {
+	return false;
+   }
+  
+}

Added: trunk/src/skrueger/geotools/StyledGridCoverageInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledGridCoverageInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledGridCoverageInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,10 @@
+package skrueger.geotools;
+import org.geotools.coverage.grid.GridCoverage2D;
+
+/**
+ * A {@link StyledRasterInterface} that is typed to wrap a {@link GridCoverage2D}
+ * 
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+public interface StyledGridCoverageInterface extends StyledRasterInterface<GridCoverage2D>{
+};
\ No newline at end of file

Added: trunk/src/skrueger/geotools/StyledGridCoverageReader.java
===================================================================
--- trunk/src/skrueger/geotools/StyledGridCoverageReader.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledGridCoverageReader.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,217 @@
+package skrueger.geotools;
+
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import org.geotools.styling.Style;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.grid.GridUtil;
+
+import skrueger.i8n.Translation;
+import skrueger.RasterLegendData;
+
+/**
+ * This class provides a simple implementation of {@link StyledMapInterface}
+ * for {@link AbstractGridCoverage2DReader}. The uncache functionality is not supported,
+ * because if the coverage is read once this class bases on an existing {@link GridCoverage2D}
+ * object in memory.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StyledGridCoverageReader extends AbstractStyledMap<AbstractGridCoverage2DReader> implements StyledGridCoverageReaderInterface {
+
+  /** Holds the meta data for displaying a legend. */
+  protected RasterLegendData legendData = null;
+
+  /**
+   * Creates a styled grid with language-specific informations.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, Translation title, Translation desc, Translation keywords, Style style, RasterLegendData legendData, ImageIcon icon) {
+    super(gcr, JTSUtil.createEnvelope(gcr.getOriginalEnvelope()), gcr.getCrs(), id, title, desc, keywords, style, icon);
+    setLegendMetaData(legendData);
+  }
+
+  /**
+   * Creates a styled grid with language-specific informations.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a (language-specific) short description
+   * @param desc a (language-specific) long description
+   * @param keywords (language-specific) keywords for the geo objects
+   * @param style a display style with legend information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, Translation title, Translation desc, Translation keywords, StyledMapStyle<RasterLegendData> style, ImageIcon icon) {
+    super(gcr, JTSUtil.createEnvelope(gcr.getOriginalEnvelope()), gcr.getCrs(), id, title, desc, keywords, style != null ? style.getGeoObjectStyle() : null, icon);
+    setLegendMetaData( style != null ? style.getMetaData() : null );
+  }
+
+  /**
+   * Creates a styled grid with a language-specific title, no long description, no
+   * keywords and no icon.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, Translation title, Style style, RasterLegendData legendData) {
+    this(gcr, id, title, null, null, style, legendData, null);
+  }
+
+  /**
+   * Creates a styled grid with non-translated informations.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style (if {@code null}, a default style is created)
+   * @param legendData meta data for displaying a legend
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, String title, String desc, String keywords, Style style, RasterLegendData legendData, ImageIcon icon) {
+    this(gcr, id, (Translation)null, null, null, style, legendData, icon);
+    setTitle(title);
+    setDesc(desc);
+    setKeywords(keywords);
+  }
+
+  /**
+   * Creates a styled grid with non-translated informations.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param desc a long description
+   * @param keywords keywords for the geo objects
+   * @param style a display style with legend information
+   * @param icon an icon for the object (can be {@code null})
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, String title, String desc, String keywords, StyledMapStyle<RasterLegendData> style, ImageIcon icon) {
+    this(gcr,
+         id,
+         title,
+         desc,
+         keywords,
+         style != null ? style.getGeoObjectStyle() : null,
+         style != null ? style.getMetaData() : null,
+         icon
+    );
+  }
+
+  /**
+   * Creates a styled grid with a non-translated title, no long description, no
+   * keywords and no icon.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style (if {@code null}, a default style is created)
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, String title, Style style, RasterLegendData legendData) {
+    this(gcr, id, title, null, null, style, legendData, null);
+  }
+
+  /**
+   * Creates a styled grid with a non-translated title, no long description, no
+   * keywords and no icon.
+   * @param gcr the grid reader
+   * @param id a unique ID for the object
+   * @param title a short description
+   * @param style a display style with legend information
+   * @exception IllegalArgumentException if {@code null} is given as ID or geo object
+   */
+  public StyledGridCoverageReader(AbstractGridCoverage2DReader gcr, String id, String title, StyledMapStyle<RasterLegendData> style) {
+    this(gcr,
+         id,
+         title,
+         null,
+         null,
+         style != null ? style.getGeoObjectStyle() : null,
+         style != null ? style.getMetaData() : null,
+         null
+    );
+  }
+
+  /**
+   * Creates a default style for a {@link GridCoverage2D}.
+   * @see GridUtil#createDefaultStyle()
+   */
+  protected Style createDefaultStyle() {
+    return GridUtil.createDefaultStyle();
+  }
+
+  /**
+   * Returns the meta data needed for displaying a legend.
+   */
+  public RasterLegendData getLegendMetaData() {
+    return legendData;
+  }
+
+  /**
+   * Sets the meta data needed for displaying a legend.
+   * If {@code legendData} is {@code null} an empty {@link RasterLegendData}
+   * (without gaps) is set, so {@link #getLegendMetaData()} never returns {@code null}.
+   * @param legendData legend meta data
+   */
+  public void setLegendMetaData(RasterLegendData legendData) {
+    this.legendData = (legendData != null) ? legendData : new RasterLegendData(false);
+  }
+
+  /**
+   * Simply sets the {@link #geoObject}, {@link #crs}, {@link #envelope} and
+   * {@link #legendData} to {@code null}.
+   */
+  public void dispose() {
+    this.geoObject  = null;
+    this.envelope   = null;
+    this.crs        = null;
+    this.legendData = null;
+  }
+
+  /**
+   * Tests whether the geo object is disposed.
+   * @return boolean
+   */
+  public boolean isDisposed() {
+    return geoObject == null;
+  }
+
+  /**
+   * Does nothing, because the {@link AbstractStyledMap} bases on existing
+   * objects (in memory) which can not be uncached and reloaded.
+   */
+  public void uncache() {
+    LOGGER.warn("Uncache functionality is not supported. Object remains in memory.");
+  }
+
+  /*
+   * (non-Javadoc)
+   * @see skrueger.geotools.StyledMapInterface#getInfoURL()
+   */
+  public URL getInfoURL() {
+    return null;
+  }
+
+  public boolean isHideInLegend() {
+    return false;
+  }
+  
+}

Added: trunk/src/skrueger/geotools/StyledGridCoverageReaderInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledGridCoverageReaderInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledGridCoverageReaderInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,10 @@
+package skrueger.geotools;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+
+/**
+ * A {@link StyledRasterInterface} that is typed to wrap a {@link AbstractGridCoverage2DReader}
+ * 
+ * @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a>
+ */
+public interface StyledGridCoverageReaderInterface extends StyledRasterInterface<AbstractGridCoverage2DReader>{
+};
\ No newline at end of file

Added: trunk/src/skrueger/geotools/StyledMapInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledMapInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledMapInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,140 @@
+package skrueger.geotools;
+
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import org.geotools.styling.Style;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import skrueger.i8n.Translation;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+// fuer Doku
+import org.geotools.feature.FeatureCollection;
+import org.geotools.coverage.grid.GridCoverage2D;
+import skrueger.geotools.MapContextManagerInterface;
+import skrueger.AttributeMetaData;
+import skrueger.RasterLegendData;
+
+/**
+ * This class is the top interface for styled objects to be managed in
+ * {@link MapContextManagerInterface}. The (rough) classe structure is the
+ * following:
+ * <ul>
+ * <li><b>{@link StyledMapInterface StyledMapInterface<E>}</b>
+ * <ul>
+ * <li>{@link #getId()} -> String</li>
+ * <li>{@link #getKeywords() get/setKeywords()} -> {@link Translation}</li>
+ * <li>{@link #getTitle() set/getTitle()} -> {@link Translation} (short
+ * description for layer list)</li>
+ * <li>{@link #getDesc() set/getDesc()} -> {@link Translation} (long
+ * description for details)</li>
+ * <li>{@link #getCrs()} -> {@link CoordinateReferenceSystem}</li>
+ * <li>{@link #getCRSString()} -> String (readable description of CRS)</li>
+ * <li>{@link #getEnvelope()} -> {@link Envelope} (JTS-Envelope)</li>
+ * <li>{@link #getGeoObject()} -> E (GridCoverage/FeatureCollection/...)</li>
+ * <li>{@link #getStyle() set/getStyle()} -> {@link Style}</li>
+ * <li>{@link #isHideInLegend()} -> Boolean</li> *
+ * <li>{@link #uncache()}</li>
+ * <li>{@link #dispose()}</li>
+ * </ul>
+ * </li>
+ * <li><b>{@link StyledFeatureCollectionInterface} extends
+ * {@link StyledMapInterface StyledMapInterface<FeatureCollection>}</b>
+ * <ul>
+ * <li>{@link StyledFeatureCollectionInterface#getAttributeMetaDataMap()} ->
+ * Map<Integer,AttributeMetaData></li>
+ * </ul>
+ * </li>
+ * <li><b>{@link StyledRasterInterface} extends
+ * {@link StyledMapInterface StyledMapInterface<GridCoverage2D>}</b>
+ * <ul>
+ * <li>{@link StyledRasterInterface#getLegendMetaData()} ->
+ * {@link RasterLegendData}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <br>
+ * <b>Restrictions:</b>
+ * <ul>
+ * <li>layer list only depends on {@link StyledMapInterface}</li>
+ * <li>methods returning {@link Translation} must not return {@code null}</li>
+ * <li>methods returning {@link AttributeMetaData}-Map must not return
+ * {@code null}</li>
+ * <li>static helper method to get a new {@link AttributeMetaData}-map withe
+ * the visible attributes only</li>
+ * <li>static helper method to create a "default" {@link AttributeMetaData}-map
+ * for a {@link FeatureCollection} with all attributes visible and without real
+ * translations, but the attribute name as description.</li>
+ * </ul>
+ */
+public interface StyledMapInterface<E> {
+	public String getId();
+
+	public Translation getTitle();
+
+	public void setTitle(Translation title);
+
+	public Translation getDesc();
+
+	public void setDesc(Translation dec);
+
+	public Translation getKeywords();
+
+	public void setKeywords(Translation keywords);
+
+	public CoordinateReferenceSystem getCrs();
+
+	public String getCRSString();
+
+	public Envelope getEnvelope();
+
+	/**
+	 * @return return an ImageIcon - <code>null</code> is valid and no icon or
+	 *         a default icon will then be shown
+	 */
+	public ImageIcon getImageIcon();
+
+	public void setImageIcon(ImageIcon icon);
+
+	/**
+	 * Returns the underlying GeoTools Object
+	 * 
+	 * @throws RuntimeException
+	 */
+	public E getGeoObject() throws Exception;
+
+	public Style getStyle();
+
+	public void setStyle(Style style);
+
+	/**
+	 * Returns the {@link URL} to a (HTML) file that provides more information
+	 * about this layer. If no HTML if associated with this
+	 * {@link StyledMapInterface}, then <code>null</code> will be returned.
+	 * 
+	 * @return null or an {@link URL}
+	 */
+	public URL getInfoURL();
+
+	/**
+	 * Should be called when this Object is not needed anymore.
+	 */
+	public void dispose();
+
+	/** Is the object already disposed? * */
+	public boolean isDisposed();
+
+	/**
+	 * Clears any caches. For example the GeoObject could be released, and
+	 * reread on next call of getGeoObject()
+	 */
+	public void uncache();
+
+	/**
+	 * If true, this layer will not be shown in the legend. Default = false
+	 */
+	public boolean isHideInLegend();
+}

Added: trunk/src/skrueger/geotools/StyledMapStyle.java
===================================================================
--- trunk/src/skrueger/geotools/StyledMapStyle.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledMapStyle.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,58 @@
+package skrueger.geotools;
+
+import org.geotools.styling.Style;
+
+/**
+ * This class combines a Geotools visualisation {@link Style} with additional
+ * meta data needed for visualisation (for example legend data).
+ * The class {@code E} defines the type of the meta data.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StyledMapStyle<E> {
+  /** Holds the Geotools {@link Style} for the geo object visualisation. */
+  protected Style geoObjectStyle = null;
+  /** Holds the additional meta data for object visualisation (for example
+   *  legend information). */
+  protected E metaData = null;
+
+  /**
+   * Creates a new style for a {@link StyledMapInterface}.
+   * @param style Style
+   * @param metaData E
+   */
+  public StyledMapStyle(Style style, E metaData) {
+    setGeoObjectStyle(style);
+    setMetaData(metaData);
+  }
+
+  /**
+   * Returns the additional meta data needed for object visualisation.
+   */
+  public E getMetaData() {
+    return metaData;
+  }
+
+  /**
+   * Sets the additional meta data needed for object visualisation.
+   * @param metaData the meta data
+   */
+  public void setMetaData(E metaData) {
+    this.metaData = metaData;
+  }
+
+  /**
+   * Returns the Geotools style for the object visualisation.
+   */
+  public Style getGeoObjectStyle() {
+    return geoObjectStyle;
+  }
+
+  /**
+   * Sets the Geotools style for the object visualisation.
+   * @param style a Geotools visualisation style
+   */
+  public void setGeoObjectStyle(Style style) {
+    this.geoObjectStyle = style;
+  }
+}

Added: trunk/src/skrueger/geotools/StyledMapUtil.java
===================================================================
--- trunk/src/skrueger/geotools/StyledMapUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledMapUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,683 @@
+package skrueger.geotools;
+
+import java.text.DecimalFormat;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.net.URL;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.map.MapLayer;
+import org.geotools.map.DefaultMapLayer;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+import org.geotools.styling.ColorMap;
+import org.geotools.styling.ColorMapEntry;
+import org.geotools.styling.Style;
+
+import org.apache.log4j.Logger;
+
+import org.jdom.Element;
+import org.jdom.Document;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.XMLOutputter;
+
+import schmitzm.geotools.styling.StylingUtil;
+import skrueger.AttributeMetaData;
+import skrueger.RasterLegendData;
+import skrueger.i8n.Translation;
+import schmitzm.io.IOUtil;
+import java.io.File;
+import java.io.FileNotFoundException;
+import schmitzm.lang.LangUtil;
+import schmitzm.swing.SwingUtil;
+
+import java.io.FileWriter;
+
+/**
+ * This class provides static helper methods for dealing with
+ * {@link StyledMapInterface} stuff.
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+ * @version 1.0
+ */
+public class StyledMapUtil {
+  private static final Logger LOGGER = Logger.getLogger(StyledMapUtil.class.getName());
+  private static final SAXBuilder SAX_BUILDER = new SAXBuilder();
+  private static final XMLOutputter XML_OUTPUTTER = new XMLOutputter();
+
+  /** URL for Atlas XML schema */
+  public static final String AMLURI = "http://www.wikisquare.de/AtlasML";
+  /** Name of the XML Element for the attribute meta data map */
+  public static final String ELEM_NAME_AMD = "attributeMetaData";
+  /** Name of the XML Element for the raster legend data */
+  public static final String ELEM_NAME_RLD = "rasterLegendData";
+  /** Name of the XML Element for an attribute meta data map entry */
+  public static final String ELEM_NAME_ATTRIBUTE = "dataAttribute";
+  /** Name of the XML Element for an raster legend data entry */
+  public static final String ELEM_NAME_RASTERLEGEND = "rasterLegendItem";
+  /** Name of the XML Element for a translation */
+  public static final String ELEM_NAME_TRANSLATION = "translation";
+
+  /**
+   * Creates a Geotools {@link MapLayer} from an object. If the object is a
+   * {@link StyledMapInterface} then its sytle is used. In case of direct
+   * Geotools objects ({@link GridCoverage2D}, {@link AbstractGridCoverage2DReader},
+   * {@link FeatureCollection}) a default style is generated.
+   * @param object an Object
+   * @exception Exception if {@code null} is given as object or an error occurs during layer creation
+   */
+  public static MapLayer createMapLayer(Object object) throws Exception {
+    return createMapLayer(object,null);
+  }
+
+  /**
+   * Creates a Geotools {@link MapLayer} from an object. If the object is a
+   * {@link StyledMapInterface} then its sytle is used. In case of direct
+   * Geotools objects ({@link GridCoverage2D}, {@link AbstractGridCoverage2DReader},
+   * {@link FeatureCollection}) a default style is generated.
+   * @param object an Object
+   * @param forcedStyle (SLD-)Style to force for the object
+   * @exception Exception if {@code null} is given as object or an error occurs during layer creation
+   */
+  public static MapLayer createMapLayer(Object object, Style forcedStyle) throws Exception {
+    MapLayer layer     = null;
+    Style    style     = null;
+    if ( object instanceof StyledMapInterface ) {
+      style =  ((StyledMapInterface<?>)object).getStyle();
+      object = ((StyledMapInterface<?>)object).getGeoObject();
+    }
+    if ( forcedStyle != null )
+      style = forcedStyle;
+    if ( style == null )
+      style = StylingUtil.createDefaultStyle(object);
+
+    if (object instanceof GridCoverage2D)
+      layer = new DefaultMapLayer( (GridCoverage2D) object, style);
+    if (object instanceof AbstractGridCoverage2DReader)
+      layer = new DefaultMapLayer( (AbstractGridCoverage2DReader) object, style);
+    if (object instanceof FeatureCollection)
+      layer = new DefaultMapLayer( (FeatureCollection) object, style);
+
+    if ( layer == null )
+      throw new Exception("Can not create MapLayer from "+(object == null ? "null" : object.getClass()));
+
+    return layer;
+  }
+
+  /**
+   * Creates an default instance of {@link StyledMapInterface} for a Geotools
+   * object ({@link GridCoverage2D}, {@link FeatureCollection}) with a default
+   * style.
+   * @param object an Object
+   * @param title  title for the object
+   * @exception UnsupportedOperationException if {@code null} is given as object or an error occurs during creation
+   */
+  public static StyledMapInterface<?> createStyledMap(Object object, String title) {
+     return createStyledMap(object, title, null);
+  }
+
+  /**
+   * Creates an default instance of {@link StyledMapInterface} for a Geotools
+   * object ({@link GridCoverage2D}, {@link FeatureCollection}) with a given
+   * style.
+   * @param object an Object
+   * @param title  title for the object
+   * @param style  style and meta data for the object
+   * @exception UnsupportedOperationException if {@code null} is given as object or an error occurs during creation
+   */
+  public static StyledMapInterface<?> createStyledMap(Object object, String title, StyledMapStyle style) {
+    StyledMapInterface<?> styledObject = null;
+
+    String id = (title != null) ? title : "defaultID";
+
+    if ( object instanceof GridCoverage2D )
+      styledObject = new StyledGridCoverage(
+          (GridCoverage2D)object,
+          id,
+          title,
+          style
+      );
+    else if ( object instanceof AbstractGridCoverage2DReader )
+           styledObject = new StyledGridCoverageReader(
+               (AbstractGridCoverage2DReader)object,
+               id,
+               title,
+               style
+           );
+    else if ( object instanceof FeatureCollection )
+      styledObject = new StyledFeatureCollection(
+          (FeatureCollection)object,
+          id,
+          title,
+          style
+      );
+
+    if ( styledObject == null )
+      throw new UnsupportedOperationException("Can not create StyledMapInterface object from "+(object == null ? "null" : object.getClass()));
+
+    return styledObject;
+  }
+
+  /**
+   * Parses a {@link AttributeMetaData} object from an JDOM-{@link Element}.
+   * This method works like {@link AMLImport#parseDataAttribute(org.w3c.dom.Node},
+   * but for JDOM.
+   * @param element {@link Element} to parse
+   */
+  public static AttributeMetaData parseAttributeMetaData(final Element element) {
+    final Integer col = Integer.valueOf(element.getAttributeValue("col"));
+    final Boolean visible = Boolean.valueOf(element.getAttributeValue("visible"));
+    final String unit = element.getAttributeValue("unit");
+
+    Translation name = new Translation();
+    Translation desc = new Translation();
+    for (final Element childElement : (List<Element>)element.getChildren()) {
+      if (childElement.getName() == null)
+        continue;
+
+      if (childElement.getName().equals("name"))
+        name = parseTranslation(childElement);
+      else if (childElement.getName().equals("desc"))
+        desc = parseTranslation(childElement);
+    }
+    return new AttributeMetaData(col, visible, name, desc, unit);
+  }
+
+  /**
+   * Parses a {@link AttributeMetaData} map from an JDOM-{@link Element}
+   * with {@code <attribute>}-childs.
+   * @param element {@link Element} to parse
+   */
+  public static Map<Integer,AttributeMetaData> parseAttributeMetaDataMap(final Element element) {
+    HashMap<Integer,AttributeMetaData> metaData = new HashMap<Integer,AttributeMetaData>();
+    List<Element> attributesElements = element.getChildren( ELEM_NAME_ATTRIBUTE );
+    for (Element attibuteElement : attributesElements)
+    {
+      AttributeMetaData attrMetaData = parseAttributeMetaData( attibuteElement );
+      metaData.put( attrMetaData.getColIdx(), attrMetaData );
+    }
+    return metaData;
+  }
+
+  /**
+   * Loads a {@link AttributeMetaData} object from an URL.
+   * @param documentUrl {@link URL} to parse
+   * @see #parseAttributeMetaData(Element)
+   */
+  public static Map<Integer,AttributeMetaData> loadAttributeMetaDataMap(final URL documentUrl) throws Exception {
+    Document document = SAX_BUILDER.build(documentUrl);
+    return parseAttributeMetaDataMap( document.getRootElement() );
+  }
+
+  /**
+   * Creates an JDOM {@link Element} for the given {@link AttributeMetaData}
+   * object.
+   * @param amd meta data for one attribute
+   */
+  public static Element createAttributeMetaDataElement(final AttributeMetaData amd) {
+    final Element element = new Element( ELEM_NAME_ATTRIBUTE , AMLURI);
+    element.setAttribute("col", String.valueOf( amd.getColIdx() ) );
+    element.setAttribute("visible", String.valueOf( amd.isVisible() ) );
+    element.setAttribute("unit", amd.getUnit() );
+    // Creating a aml:name tag...
+    element.addContent( createTranslationElement("name", amd.getTitle()) );
+    // Creating a aml:desc tag...
+    element.addContent( createTranslationElement("desc", amd.getDesc()) );
+    return element;
+  }
+
+  /**
+   * Creates an JDOM {@link Element} for the given {@link AttributeMetaData}
+   * map.
+   * @param amdMap map of attribute meta data
+   */
+  public static Element createAttributeMetaDataMapElement(final Map<Integer,AttributeMetaData> amdMap) {
+    final Element element = new Element( ELEM_NAME_AMD , AMLURI);
+    for (AttributeMetaData amd : amdMap.values())
+      element.addContent( createAttributeMetaDataElement( amd ) );
+    return element;
+  }
+
+  /**
+   * Saves a {@link AttributeMetaData AttributeMetaData-Map} to an URL.
+   * @param amdMap map of {@link AttributeMetaData}
+   * @param documentUrl {@link URL} to store the XML
+   */
+  public static void saveAttributeMetaDataMap(final Map<Integer,AttributeMetaData> amdMap, final URL documentUrl) throws Exception {
+    // Create XML-Document
+    final FileWriter out = new FileWriter( new File(documentUrl.toURI()) );
+    XML_OUTPUTTER.output(
+      createAttributeMetaDataMapElement(amdMap),
+      out
+    );
+    out.flush();
+    out.close();
+  }
+
+
+
+  /**
+   * Parses a {@link RasterLegendData} object from an JDOM-{@link Element}.
+   * This method works like {@link AMLImport#parseRasterLegendData(org.w3c.dom.Node},
+   * but for JDOM.
+   * @param element {@link Element} to parse
+   */
+  public static RasterLegendData parseRasterLegendData(Element element) {
+
+    final boolean paintGaps = Boolean.valueOf( element.getAttributeValue("paintGaps") );
+
+    RasterLegendData rld = new RasterLegendData(paintGaps);
+
+    for ( Element childElement : (List<Element>)element.getChildren() ) {
+      final String name = childElement.getName();
+      // Cancel if it's an attribute
+      if ( childElement.getChildren().size() == 0 )
+        continue;
+
+      if (name.equals( ELEM_NAME_RASTERLEGEND )) {
+        final String valueAttr = childElement.getAttributeValue("value");
+        if ( valueAttr == null )
+          throw new UnsupportedOperationException("Attribute 'value' missing for definition of <"+ELEM_NAME_RASTERLEGEND+">");
+        final double value = Double.valueOf(valueAttr);
+
+        // first and only item should be the label
+        final Element labelElement = childElement.getChild("label");
+        // id label element is missing, the translation is searched directly
+        // as childs of the rasterLegendItem element
+        Translation label = parseTranslation( labelElement != null ? labelElement : childElement );
+        rld.put(value, label);
+      }
+    }
+
+    return rld;
+  }
+
+  /**
+   * Loads a {@link RasterLegendData} object from an URL.
+   * @param documentUrl {@link URL} to parse
+   * @see #parseAttributeMetaData(Element)
+   */
+  public static RasterLegendData loadRasterLegendData(final URL documentUrl) throws Exception {
+    Document document = SAX_BUILDER.build(documentUrl);
+    return parseRasterLegendData( document.getRootElement() );
+  }
+
+ /**
+   * Creates an JDOM {@link Element} for the given {@link RasterLegendData}
+   * map.
+   * @param rld raster legend data
+   */
+  public static Element createRasterLegendDataElement(final RasterLegendData rld) {
+    final Element element = new Element( ELEM_NAME_RLD , AMLURI);
+    element.setAttribute("paintGaps", rld.isPaintGaps().toString());
+    for (Double key : rld.getSortedKeys()) {
+      Element item = new Element( ELEM_NAME_RASTERLEGEND, AMLURI);
+      item.setAttribute("value", key.toString());
+      item.addContent( createTranslationElement("label", rld.get(key)) );
+      element.addContent(item);
+    }
+    return element;
+  }
+
+  /**
+   * Creates {@link RasterLegendData} from a {@link ColorMap}.
+   * @param colorMap  a color map
+   * @param paintGaps indicated whether gaps are painted between the legend items 
+   * @param digits    number of digits the grid value classes (and legend) are
+   *                  rounded to (null means no round; >= 0 means digits after comma;
+   *                  < 0 means digits before comma)    */
+  public static RasterLegendData generateRasterLegendData(ColorMap colorMap, boolean paintGaps, Integer digits) {
+    DecimalFormat    decFormat = digits != null ? new DecimalFormat( SwingUtil.getNumberFormatPattern(digits) ) : null;
+    RasterLegendData rld       = new RasterLegendData(paintGaps);
+    for (ColorMapEntry cme : colorMap.getColorMapEntries())
+    {
+      double value = StylingUtil.getQuantityFromColorMapEntry(cme);
+      String label = cme.getLabel();
+      // if no label is set (e.g. quantitative style),
+      // use the value as label
+      if ( label == null || label.equals("") )
+        if ( digits == null )
+          label = String.valueOf(value);
+        else
+          label = decFormat.format( LangUtil.round(value, digits) ); 
+      rld.put( value, new Translation("   "+label) );
+    }
+    return rld;
+  }
+
+  /**
+   * Creates {@link RasterLegendData} from the {@link ColorMap} of a style.
+   * @param style     a raster style (must contain a  {@link RasterSymbolizer})
+   * @param paintGaps indicated whether gaps are painted between the legend items 
+   * @param digits    number of digits the grid value classes (and legend) are
+   *                  rounded to (null means no round; >= 0 means digits after comma;
+   *                  < 0 means digits before comma)    */
+  public static RasterLegendData generateRasterLegendData(Style style, boolean paintGaps, Integer digits) {
+    ColorMap colorMap = StylingUtil.getColorMapFromStyle(style);
+    if ( colorMap == null)
+      throw new IllegalArgumentException("Color map can not be determined from style!");
+    return generateRasterLegendData(colorMap, paintGaps, digits);
+  }
+
+  /**
+   * Saves a {@link RasterLegendData} to an URL.
+   * @param rld raster legend data
+   * @param documentUrl {@link URL} to store the XML
+   */
+  public static void saveRasterLegendData(final RasterLegendData rld, final URL documentUrl) throws Exception {
+    // Create XML-Document
+    final FileWriter out = new FileWriter( new File(documentUrl.toURI()) );
+    XML_OUTPUTTER.output(
+      createRasterLegendDataElement(rld),
+      out
+    );
+    out.flush();
+    out.close();
+  }
+
+  /**
+   * Parses a {@link Translation} object from an JDOM-{@link Element}.
+   * This method works like {@link AMLImport#parseTranslation(org.w3c.dom.Node},
+   * but for JDOM.
+   * @param element {@link Element} to parse
+   */
+  public final static Translation parseTranslation(final Element element) {
+    Translation trans = new Translation();
+
+    if (element == null)
+     return trans;
+
+    for (final Element translationElement : (List<Element>)element.getChildren()) {
+      final String name = translationElement.getName();
+      if (name == null)
+        continue;
+
+      // lang attribute
+      String lang = translationElement.getAttributeValue("lang");
+      // set the default, if no language code is set
+      if ( lang == null )
+        lang = Translation.DEFAULT_KEY;
+
+      final String translationText = translationElement.getValue();
+      if (translationText == null)
+        trans.put(lang, "");
+      else
+        trans.put(lang, translationText);
+    }
+
+    // if no <translation> is given, the value of the node should
+    // be used as a default translation
+    if (trans.size() == 0)
+      trans.put( Translation.DEFAULT_KEY, element.getValue() );
+    //     trans = new Translation( ((List<Element>)element.getChildren()).get(0).getValue() );
+
+    return trans;
+  }
+
+  /**
+   * Creates an JDOM {@link Element} for the given {@link Translation}.
+   * @param tagname Name of the Element
+   * @param translation Translation to store in the Element
+   */
+  public final static Element createTranslationElement(String tagname, Translation translation) {
+    Element element = new Element(tagname, AMLURI);
+    if ( translation == null )
+      throw new UnsupportedOperationException("Translation element can not be created from null!");
+
+    // If only a default translation is set, the <translation lang="..">..</tranlation>
+    // part is not used
+    if (translation.keySet().size() == 1 && translation.get(Translation.DEFAULT_KEY) != null) {
+      element.addContent( translation.get(Translation.DEFAULT_KEY) );
+      return element;
+    }
+
+    // add a <translation lang="..">..</tranlation> part to the element for
+    // all languages
+    for (String lang : translation.keySet()) {
+      Element translationElement = new Element( ELEM_NAME_TRANSLATION , AMLURI);
+      translationElement.setAttribute("lang", lang);
+      String translationString = translation.get(lang);
+      if (translationString == null)
+       translationString = "";
+      translationElement.addContent( translationString );
+      element.addContent(translationElement);
+    }
+
+    return element;
+  }
+
+
+  /**
+   * Sets a style to {@link StyledMapInterface}.
+   * @param styledObject a styled object
+   * @param style a Style
+   */
+  public static void setStyledMapStyle(StyledMapInterface styledObject, StyledMapStyle<?> style) {
+    // set SLD style
+    styledObject.setStyle( style.getGeoObjectStyle() );
+    // set meta data
+    if ( styledObject        instanceof StyledGridCoverageInterface &&
+         (style.getMetaData() instanceof RasterLegendData || style.getMetaData() == null) ) {
+      RasterLegendData sourceRld = (RasterLegendData)style.getMetaData();
+      RasterLegendData destRld = ((StyledGridCoverageInterface)styledObject).getLegendMetaData();
+      if ( destRld != null && sourceRld != null ) {
+        destRld.setPaintGaps(sourceRld.isPaintGaps());
+        destRld.clear();
+        destRld.putAll( sourceRld );
+      }
+      return;
+    }
+    if ( styledObject        instanceof StyledFeatureCollectionInterface &&
+         (style.getMetaData() instanceof Map || style.getMetaData() == null) ) {
+      Map<Integer, AttributeMetaData> sourceAmd = (Map<Integer, AttributeMetaData>)style.getMetaData();
+      Map<Integer, AttributeMetaData> destAmd   = ((StyledFeatureCollectionInterface)styledObject).getAttributeMetaDataMap();
+      if ( destAmd != null && sourceAmd != null ) {
+        destAmd.clear();
+        destAmd.putAll( sourceAmd );
+      }
+      return;
+    }
+
+    throw new UnsupportedOperationException("Style is not compatible to object: " +
+                                            (style.getMetaData() == null ? null : style.getMetaData().getClass().getSimpleName()) +
+                                            " <-> " +
+                                            (styledObject == null ? null : styledObject.getClass().getSimpleName()));
+  }
+
+  /**
+   * Returns the style a {@link StyledMapInterface} as a {@link StyledMapStyle}.
+   * @param styledObject a styled object
+   * @return {@code StyledMapStyle<RasterLegendData>} for {@link StyledGridCoverageInterface}
+   *         or {@code StyledMapStyle<Map<Integer,AttributeMetaData>>} for
+   *         {@link StyledFeatureCollectionInterface}
+   */
+  public static StyledMapStyle<?> getStyledMapStyle(StyledMapInterface styledObject) {
+    if ( styledObject instanceof StyledGridCoverageInterface )
+      return getStyledMapStyle( (StyledGridCoverageInterface)styledObject );
+    if ( styledObject instanceof StyledFeatureCollectionInterface )
+      return getStyledMapStyle( (StyledFeatureCollectionInterface)styledObject );
+    throw new UnsupportedOperationException("Unknown type of StyledMapInterface: "+(styledObject == null ? null : styledObject.getClass().getSimpleName()));
+  }
+
+  /**
+   * Returns the style and raster meta data of a {@link StyledGridCoverageInterface}
+   * as a {@link StyledMapStyle}.
+   * @param styledGC a styled grid coverage
+   */
+  public static StyledMapStyle<RasterLegendData> getStyledMapStyle(StyledGridCoverageInterface styledGC) {
+    return new StyledMapStyle<RasterLegendData>(
+      styledGC.getStyle(),
+      styledGC.getLegendMetaData()
+    );
+  }
+
+  /**
+   * Returns the style and attribute meta data of a {@link StyledFeatureCollectionInterface}
+   * as a {@link StyledMapStyle}.
+   * @param styledFC a styled feature collection
+   */
+  public static StyledMapStyle<Map<Integer,AttributeMetaData>> getStyledMapStyle(StyledFeatureCollectionInterface styledFC) {
+    return new StyledMapStyle<Map<Integer,AttributeMetaData>>(
+      styledFC.getStyle(),
+      styledFC.getAttributeMetaDataMap()
+    );
+  }
+
+  /**
+   * Loads a {@linkplain Style SLD-Style} and {@linkplain RasterLegendData Raster-LegendData}
+   * for a given geo-object (raster) source. The SLD file must be present. A missing
+   * raster legend-data file is tolerated.
+   * @param geoObjectURL URL of the (already read) raster object
+   * @param sldExt file extention for the SLD file
+   * @param rldExt file extention for the raster legend-data file
+   * @return {@code null} in case of any error
+   */
+  public static StyledMapStyle<RasterLegendData> loadStyledRasterStyle(URL geoObjectURL, String sldExt, String rldExt) {
+    RasterLegendData metaData = null;
+    Style sldStyle = null;
+    try {
+      Style[] styles = StylingUtil.loadSLD(IOUtil.changeUrlExt(geoObjectURL, sldExt));
+      // SLD must be present
+      if ( styles == null || styles.length == 0 )
+        return null;
+      sldStyle = styles[0];
+    }
+    catch (Exception err) {
+      // SLD must be present
+      LangUtil.logDebugError(LOGGER,err);
+      return null;
+    }
+
+    try {
+      metaData = StyledMapUtil.loadRasterLegendData( IOUtil.changeUrlExt(geoObjectURL,rldExt) );
+    } catch (FileNotFoundException err) {
+      // ignore missing raster legend data
+    } catch (Exception err) {
+      // any other error during legend data creation leads to error
+      LangUtil.logDebugError(LOGGER,err);
+      return null;
+    }
+    return new StyledMapStyle<RasterLegendData>(sldStyle, metaData);
+  }
+
+  /**
+   * Loads a {@linkplain Style SLD-Style} from a {@code .sld} file and
+   * {@linkplain RasterLegendData Raster-LegendData} from a {@code .rld} file
+   * for a given geo-object (raster) source. The SLD file must be present. A missing
+   * raster legend-data file is tolerated.
+   * @param geoObjectURL URL of the (already read) raster object
+   * @param sldExt file extention for the SLD file
+   * @param rldExt file extention for the raster legend-data file
+   * @return {@code null} in case of any error
+   */
+  public static StyledMapStyle<RasterLegendData> loadStyledRasterStyle(URL geoObjectURL) {
+    return loadStyledRasterStyle(geoObjectURL, "sld", "rld");
+  }
+
+  /**
+   * Loads a {@linkplain Style SLD-Style} and a {@linkplain AttributeMetaData AttributeMetaData-Map}
+   * for a given geo-object (feature) source. The SLD file must be present. A missing
+   * attribute meta-data file is tolerated.
+   * @param geoObjectURL URL of the (already read) feature object
+   * @param sldExt file extention for the SLD file
+   * @param rldExt file extention for the raster legend-data file
+   * @return {@code null} in case of any error
+   */
+  public static StyledMapStyle<Map<Integer,AttributeMetaData>> loadStyledFeatureStyle(URL geoObjectURL, String sldExt, String rldExt) {
+    Map<Integer,AttributeMetaData> metaData = null;
+    Style                          sldStyle = null;
+    try {
+      Style[] styles = StylingUtil.loadSLD(IOUtil.changeUrlExt(geoObjectURL, sldExt));
+      // SLD must be present
+      if ( styles == null || styles.length == 0 )
+        return null;
+      sldStyle = styles[0];
+    } catch (Exception err) {
+      // SLD must be present
+      LangUtil.logDebugError(LOGGER,err);
+      return null;
+    }
+
+    try {
+      metaData = StyledMapUtil.loadAttributeMetaDataMap( IOUtil.changeUrlExt(geoObjectURL,rldExt) );
+    } catch (FileNotFoundException err) {
+      // ignore missing attribute meta data
+    } catch (Exception err) {
+      // any other error during meta data creation leads to error
+      LangUtil.logDebugError(LOGGER,err);
+      return null;
+    }
+
+    return new StyledMapStyle<Map<Integer,AttributeMetaData>>(sldStyle, metaData);
+  }
+
+  /**
+   * Loads a {@linkplain Style SLD-Style} from a {@code .sld} file and
+   * {@linkplain AttributeMetaData AttributeMetaData-Map} from a {@code .amd} file
+   * for a given geo-object (feature) source. The SLD file must be present. A missing
+   * attribute meta-data file is tolerated.
+   * @param geoObjectURL URL of the (already read) feature object
+   * @param sldExt file extention for the SLD file
+   * @param rldExt file extention for the raster legend-data file
+   * @return {@code null} in case of any error
+   */
+  public static StyledMapStyle<Map<Integer,AttributeMetaData>> loadStyledFeatureStyle(URL geoObjectURL) {
+    return loadStyledFeatureStyle(geoObjectURL, "sld", "amd");
+  }
+
+  /**
+   * Stores a {@linkplain Style SLD-Style} and {@linkplain RasterLegendData Raster-LegendData}
+   * for a given geo-object (raster) source.
+   * @param style  style to save
+   * @param geoObjectURL URL of the raster object
+   * @param sldExt file extention for the SLD file
+   * @param mdExt file extention for the meta-data file
+   */
+  public static <T> void saveStyledMapStyle(StyledMapStyle<T> style, URL geoObjectURL, String sldExt, String mdExt) throws Exception {
+    // Store the SLD
+    Style sldStyle = style.getGeoObjectStyle();
+    if ( sldStyle != null ) {
+      StylingUtil.saveStyleToSLD(
+         sldStyle,
+         IOUtil.changeFileExt(
+              new File(geoObjectURL.toURI()),
+              sldExt
+         )
+      );
+    }
+
+    // Store the meta data
+    T metaData = style.getMetaData();
+    if ( metaData != null ) {
+      if ( metaData instanceof RasterLegendData ) {
+        saveRasterLegendData(
+            (RasterLegendData)metaData,
+            IOUtil.changeUrlExt(geoObjectURL,mdExt)
+        );
+//      } else if ( metaData instanceof Map<Integer,AttributeMetaData> ) { // LEIDER NICHT KOMPILIERBAR!!
+      } else if ( metaData instanceof Map ) {
+        saveAttributeMetaDataMap(
+            (Map<Integer,AttributeMetaData>)metaData,
+            IOUtil.changeUrlExt(geoObjectURL,mdExt)
+        );
+      } else
+        throw new UnsupportedOperationException("Export for meta data not yet supported: "+metaData.getClass().getSimpleName());
+    }
+  }
+
+  /**
+   * Stores the {@linkplain Style SLD-Style} to a {@code .sld} file and
+   * the meta data ({@link RasterLegendData} or {@link AttributeMetaData})
+   * to a {@code .rld} or {@code .amd} file.
+   * for a given geo-object source.
+   * @param style  style to save
+   * @param geoObjectURL URL of the (already read) raster object
+   */
+  public static void saveStyledMapStyle(StyledMapStyle<?> style, URL geoObjectURL) throws Exception {
+    if ( style.getMetaData() instanceof RasterLegendData )
+      saveStyledMapStyle(style,geoObjectURL, "sld", "rld");
+    else
+      saveStyledMapStyle(style,geoObjectURL, "sld", "amd");
+  }
+
+}

Added: trunk/src/skrueger/geotools/StyledRasterInterface.java
===================================================================
--- trunk/src/skrueger/geotools/StyledRasterInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/StyledRasterInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,19 @@
+package skrueger.geotools;
+
+import skrueger.RasterLegendData;
+
+/**
+ * A {@link StyledMapInterface} that is associated to a {@link RasterLegendData}. The datatype is not yet defined.
+ * 
+ * @see StyledGridCoverageInterface
+ * 
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+public interface StyledRasterInterface<E> extends StyledMapInterface<E> {
+
+	/**
+	 * @return A {@link RasterLegendData} object with pairs of value / label information
+	 */
+	RasterLegendData getLegendMetaData();
+
+}

Added: trunk/src/skrueger/geotools/ZoomRestrictableGridInterface.java
===================================================================
--- trunk/src/skrueger/geotools/ZoomRestrictableGridInterface.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/ZoomRestrictableGridInterface.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,27 @@
+package skrueger.geotools;
+
+/**
+ * Classes that implement {@link ZoomRestrictableGridInterface} can be restricted to their max- and/or min zoom scale
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ */
+public interface ZoomRestrictableGridInterface {
+
+	/**
+	 * The resolution is calculated by number of units in layer's crs / number of pixels.
+	 * null is allowed as return value.
+	 *
+	 * @return the maximum resolution this grid provides. (the smalles value)
+	 */
+	Double getMaxResolution();
+
+	/**
+	 * The resolution is calculated by number of units in layer's crs / number of pixels.
+	 * null is allowed as return value.
+	 *
+	 * @return the minimum resolution this grid provides. (the biggest value)
+	 */
+	Double getMinResolution();
+
+}

Added: trunk/src/skrueger/geotools/info.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/info.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/geotools/io/GeoImportUtilURL.java
===================================================================
--- trunk/src/skrueger/geotools/io/GeoImportUtilURL.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/geotools/io/GeoImportUtilURL.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,158 @@
+package skrueger.geotools.io;
+
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+
+import org.apache.log4j.Logger;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.factory.Hints;
+import org.geotools.gce.geotiff.GeoTiffReader;
+import org.geotools.geometry.Envelope2D;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.io.GeoImportUtil;
+import schmitzm.io.IOUtil;
+
+/**
+ * Erweiterungen von Martin's {@link GeoImportUtil} classe fuer die konsequente Benutzung mit {@link URL}s.
+ * TODO Diese Klasse sollte vielleicht mit der {@link GeoImportUtil} zusammengefuegt werden.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ *
+ */
+public class GeoImportUtilURL extends GeoImportUtil {
+	final static private Logger log = Logger.getLogger(GeoImportUtilURL.class);
+
+	/**
+	 * Read a {@link GridCoverage2D} from an image file. .prj and .wld files are usually expected
+	 */
+	public static GridCoverage2D readGridFromImage(URL url) throws IOException {
+		return readGridFromImage(url, null);
+	}
+
+	/**
+	 * Read a {@link GridCoverage2D} from an image file. .wld file is usually expected also. The CRS can be given as the crs parameter. null is valid.
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a> (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static GridCoverage2D readGridFromImage(URL url,
+			CoordinateReferenceSystem crs) throws IOException {
+		GridCoverage2D gc = null;
+
+		BufferedImage im = ImageIO.read(url);
+		if (im == null)
+			throw new IIOException("No image reader found for this image type!");
+
+		// World-File einlesen
+		double[] tfwInfo = null;
+
+		for (WORLD_POSTFIXES pf : WORLD_POSTFIXES.values()){
+			if (tfwInfo == null) {
+				try {
+					tfwInfo = readWorldFile( IOUtil.changeUrlExt(url, pf.toString() ).openStream());
+				} catch (Exception e) {
+				}
+			}
+		}
+
+		if (tfwInfo == null)
+			throw new IllegalArgumentException(
+					"No georeferencing information found.\n"
+					+ "Attach a .wld file to "+url+"please.");
+
+		float w = (float) (im.getWidth() * tfwInfo[0]); // reale Breite =
+		// RasterSpalten *
+		// hor. Aufloesung
+		float h = (float) (im.getHeight() * (-tfwInfo[3])); // reale Hoehe =
+		// RasterZeilen
+		// * vert.
+		// Aufloesung
+		float x = (float) tfwInfo[4]; // Suedwestliche Ecke!
+		float y = (float) tfwInfo[5] - h; // Suedwestliche Ecke (im
+		// tfw-File steht die
+		// Nordwestliche!)
+		// ggf. Projektion einlesen
+		if (crs == null) {
+			crs = determineProjection( IOUtil.changeUrlExt(url, "prj") );
+		}
+		Envelope2D envelope = new Envelope2D(crs, new Rectangle2D.Float(x, y,
+				w, h));
+		// WICHTIG: Name des Rasters sollte Leer-String sein, da ansonsten
+		// das
+		// Coloring des Rasters nicht klappt.
+		// --> Name der Categories und ColorMapEntries-Labels muss (warum auch immer) mit dem Namen
+		// des Rasters uebereinstimmen!!!
+		gc = new GridCoverageFactory().create("", im, envelope);
+
+		return gc;
+	}
+
+	/**
+	 * Diese Methode importiert ein Raster aus einer Datei im GeoTIFF-Format.
+	 * Zunaechst wird versucht, die Geo-Informationen (Referenz+CRS) aus den
+	 * TIFF-Metadaten zu ermitteln. Ist dies nicht erfolgreich, werden ein
+	 * gleichnamiges World-File (.tfw) und Projection-File (.prj) herangezogen.
+	 * Als Projektion (.prj) ist sowohl ein EPSG-Code "EPSG:...", als auch eine
+	 * WKT-Definition erlaubt. Kann kein CRS ermitteln werden, wird
+	 * {@link #DEFAULT_CRS} als CRS verwendet.
+	 *
+	 * @param geotiffURL
+	 *            URL to GeoTIFF-File
+	 * @param crs
+	 *            erzwungenes CoordinateReferenceSystem fuer das Raster (kann
+	 *            {@code null} sein)
+	 * @throws java.lang.Exception
+	 *             bei irgendeinem Fehler
+	 *
+	 * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+	 *         (University of Bonn/Germany)
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static GridCoverage2D readGridFromGeoTiff(URL geotiffURL,
+			CoordinateReferenceSystem crs) throws Exception {
+		GridCoverage2D gc = null;
+		log.debug("Loading GeoTiff from URL = " + geotiffURL + " now.");
+
+		// Versuchen Geo-Information aus Tiff zu lesen
+		try {
+
+			// Wenn CRS angegeben, dieses durch Hint erzwingen
+			Hints hints = new Hints(null);
+
+			if (crs != null)
+				hints.put(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, crs);
+			// Reader (mit Metadaten) erzeugen
+
+			log.debug("First try to create GeoTiff reader");
+
+			GeoTiffReader reader = new GeoTiffReader(geotiffURL, hints);
+
+			// Wenn kein Referenzsystem vorhanden, versuchen ein prj-File zu
+			// verwenden
+			if (reader.getOriginalEnvelope().getCoordinateReferenceSystem() == null) {
+				log
+						.warn("No projection information found in GeoTIFF. Using prj-file...");
+
+				final CoordinateReferenceSystem determineProjection = determineProjection(IOUtil.changeUrlExt(
+						geotiffURL, "prj"));
+				hints.put(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM,
+						determineProjection);
+				reader = new GeoTiffReader(geotiffURL, hints);
+			}
+			gc = (GridCoverage2D) reader.read(null);
+		} catch (UnsupportedOperationException err) {
+			log.warn("No GeoTIFF information found. Using simple image + world- and prj-file...");
+			gc = readGridFromImage(geotiffURL, crs); //TODO style
+		}
+		return gc;
+	}
+
+
+}

Added: trunk/src/skrueger/geotools/pan.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/pan.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/geotools/zoom_back.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/zoom_back.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/geotools/zoom_forward.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/zoom_forward.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/geotools/zoom_in.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/zoom_in.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/geotools/zoom_out.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/geotools/zoom_out.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/i8n/I8NUtil.java
===================================================================
--- trunk/src/skrueger/i8n/I8NUtil.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/i8n/I8NUtil.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,48 @@
+package skrueger.i8n;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class I8NUtil {
+	
+	public static List<String> languageCodes = new LinkedList<String>();
+	static {
+        for (String code : java.util.Locale.getISOLanguages()) {
+        	languageCodes.add(code);
+        }
+    }
+//	
+//	/**
+//	 * @Returns an ImageIcon for a given ISO code or null.
+//	 * @param code ISO Country Code
+//	 */
+//	public static ImageIcon getFlagIcon(String code) {
+//		
+//		String ressourcename = "resource/flags/" + code.toUpperCase() + ".gif";
+//		
+//		URL resourceURL = TranslationEditJPanel.class.getResource(ressourcename);
+//		
+//		if (resourceURL != null)
+//			return new ImageIcon( resourceURL);
+//		
+//		return new ImageIcon();
+//	}
+//	
+//	/**
+//	 * @Returns an {@link ImageIcon} flag for the language setup as Translation language
+//	 */
+//	public static ImageIcon getFlagIcon() {
+//		return getFlagIcon( Translation.getActiveLang() );
+//	}
+	
+	
+	/**
+	 * @author Stefan Alfons Krüger
+	 * @param code
+	 * @return true if the code paramter is a valid ISO Language code
+	 */
+	public static boolean isValidISOLangCode(String code) {
+		return languageCodes.contains(code);
+	}
+}
+

Added: trunk/src/skrueger/i8n/SwitchLanguageDialog.java
===================================================================
--- trunk/src/skrueger/i8n/SwitchLanguageDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/i8n/SwitchLanguageDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,248 @@
+package skrueger.i8n;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseWheelListener;
+import java.util.List;
+import java.util.Locale;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.swing.SwingUtil;
+import skrueger.swing.OkButton;
+import skrueger.swing.TranslationEditJPanel;
+
+public class SwitchLanguageDialog extends JDialog {
+	protected Logger LOGGER = Logger.getLogger(SwitchLanguageDialog.class);
+
+	private static final long serialVersionUID = 1L;
+
+	private JPanel jContentPane = null;
+
+	private JLabel jLabelFlagimage = null;
+
+	private JPanel jPanel = null;
+
+	private JButton jButton = null;
+
+	private JPanel jPanel1 = null;
+
+	private JLabel jLabel = null;
+
+	private JComboBox jComboBox = null;
+
+	private final List<String> languages;
+
+	/**
+	 * A dialog to select one of the available languages. If only one language
+	 * is available, select it directly. Creating this object automatically
+	 * makes it visible.
+	 * 
+	 * @param owner
+	 * @param atlasConfig
+	 */
+	public SwitchLanguageDialog(final Window owner, final List<String> languages) {
+		super(owner);
+		this.languages = languages;
+
+		Translation.setActiveLang(languages.get(0));
+
+		if (languages.size() == 1) {
+			LOGGER.debug("Only language '" + languages.get(0)
+					+ "' is available. It has been selected automatically.");
+			return;
+		}
+
+		initialize();
+
+		setVisible(true);
+	}
+
+	/**
+	 * This method initializes this
+	 * 
+	 * @return void
+	 */
+	private void initialize() {
+		this.setContentPane(getJContentPane());
+		setModal(true);
+		SwingUtil.centerFrameOnScreenRandom(this);
+		pack();
+	}
+
+	/**
+	 * This method initializes jContentPane
+	 * 
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJContentPane() {
+		if (jContentPane == null) {
+			final GridBagConstraints gridBagConstraints11 = new GridBagConstraints();
+			gridBagConstraints11.gridx = 1;
+			gridBagConstraints11.fill = GridBagConstraints.HORIZONTAL;
+			gridBagConstraints11.gridy = 1;
+			final GridBagConstraints gridBagConstraints3 = new GridBagConstraints();
+			gridBagConstraints3.gridx = 1;
+			gridBagConstraints3.fill = GridBagConstraints.HORIZONTAL;
+			gridBagConstraints3.gridy = 2;
+			final GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+			gridBagConstraints1.gridx = 0;
+			gridBagConstraints1.fill = GridBagConstraints.BOTH;
+			gridBagConstraints1.gridwidth = 2;
+			gridBagConstraints1.anchor = GridBagConstraints.NORTH;
+			gridBagConstraints1.gridy = 0;
+			jLabelFlagimage = new JLabel(new ImageIcon(
+					TranslationEditJPanel.class
+							.getResource("resource/flags.jpg")));
+			jContentPane = new JPanel();
+			jContentPane.setLayout(new GridBagLayout());
+			jContentPane.add(jLabelFlagimage, gridBagConstraints1);
+			jContentPane.add(getJPanel(), gridBagConstraints3);
+			jContentPane.add(getJPanel1(), gridBagConstraints11);
+		}
+		return jContentPane;
+	}
+
+	/**
+	 * This method initializes jPanel
+	 * 
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJPanel() {
+		if (jPanel == null) {
+			final GridBagConstraints gridBagConstraints4 = new GridBagConstraints();
+			gridBagConstraints4.gridx = 0;
+			gridBagConstraints4.anchor = GridBagConstraints.EAST;
+			gridBagConstraints4.weightx = 1.0;
+			gridBagConstraints4.insets = new Insets(5, 5, 5, 5);
+			gridBagConstraints4.gridy = 0;
+			jPanel = new JPanel();
+			jPanel.setLayout(new GridBagLayout());
+			jPanel.add(getJButton(), gridBagConstraints4);
+		}
+		return jPanel;
+	}
+
+	/**
+	 * This method initializes jButton
+	 * 
+	 * @return javax.swing.JButton
+	 */
+	private JButton getJButton() {
+		if (jButton == null) {
+			jButton = new OkButton();
+			jButton.setEnabled(false);
+
+			jButton.addActionListener(new ActionListener() {
+
+				public void actionPerformed(ActionEvent e) {
+					dispose();
+				}
+
+			});
+		}
+		return jButton;
+	}
+
+	/**
+	 * This method initializes jPanel1
+	 * 
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJPanel1() {
+		if (jPanel1 == null) {
+			final GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
+			gridBagConstraints2.fill = GridBagConstraints.VERTICAL;
+			gridBagConstraints2.gridy = 0;
+			gridBagConstraints2.weightx = 1.0;
+			gridBagConstraints2.insets = new Insets(5, 5, 5, 5);
+			gridBagConstraints2.anchor = GridBagConstraints.WEST;
+			gridBagConstraints2.gridx = 1;
+			final GridBagConstraints gridBagConstraints = new GridBagConstraints();
+			gridBagConstraints.gridx = 0;
+			gridBagConstraints.insets = new Insets(0, 5, 0, 0);
+			gridBagConstraints.gridy = 0;
+			jLabel = new JLabel();
+			jLabel.setText("Select language: ");
+			jPanel1 = new JPanel();
+			jPanel1.setLayout(new GridBagLayout());
+			jPanel1.add(jLabel, gridBagConstraints);
+			jPanel1.add(getJComboBox(), gridBagConstraints2);
+		}
+		return jPanel1;
+	}
+
+	/**
+	 * This method initializes jComboBox
+	 * 
+	 * @return javax.swing.JComboBox
+	 */
+	private JComboBox getJComboBox() {
+		if (jComboBox == null) {
+			jComboBox = new JComboBox();
+
+			jComboBox.addMouseWheelListener(new MouseWheelListener() {
+				public void mouseWheelMoved(java.awt.event.MouseWheelEvent e) {
+
+					if ((e.getWheelRotation() < 0)) {
+						if (jComboBox.getSelectedIndex() < jComboBox
+								.getItemCount() - 1)
+							jComboBox.setSelectedIndex(jComboBox
+									.getSelectedIndex() + 1);
+					} else {
+						if (jComboBox.getSelectedIndex() > 0)
+							jComboBox.setSelectedIndex(jComboBox
+									.getSelectedIndex() - 1);
+					}
+				}
+			});
+
+			String[] langNames = new String[languages.size() + 1];
+			for (int i = 0; i < languages.size(); i++) {
+
+				Locale locale = null;
+				for (Locale l : Locale.getAvailableLocales()) {
+					if (l.getLanguage().equals(languages.get(i))) {
+						locale = l;
+					}
+				}
+
+				langNames[i] = locale.getDisplayLanguage(locale) + " / "
+						+ locale.getDisplayLanguage() + " / "
+						+ languages.get(i);
+			}
+			langNames[languages.size()] = "?";
+
+			jComboBox.setModel(new DefaultComboBoxModel(langNames));
+			jComboBox.setSelectedItem(langNames[languages.size()]);
+
+			jComboBox.addActionListener(new ActionListener() {
+
+				public void actionPerformed(final ActionEvent e) {
+					if (jComboBox.getSelectedIndex() == languages.size()) {
+						getJButton().setEnabled(false);
+						return;
+					}
+					Translation.setActiveLang(languages.get(jComboBox
+							.getSelectedIndex()));
+					getJButton().setEnabled(true);
+				}
+
+			});
+		}
+		return jComboBox;
+	}
+
+} // @jve:decl-index=0:visual-constraint="0,0"

Added: trunk/src/skrueger/i8n/Translation.java
===================================================================
--- trunk/src/skrueger/i8n/Translation.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/i8n/Translation.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,194 @@
+package skrueger.i8n;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a {@link HashMap} of translations.
+ * toString() returns the appropriate translation
+ *
+ * @author @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+
+public class Translation extends HashMap<String, String> {
+	public static final String NO_TRANSLATION = "NO TRANSLATION";
+	public static final String DEFAULT_KEY = "default";
+	static final Logger log = Logger.getLogger( Translation.class );
+	static String activeLang = "fr";
+
+	static {
+		
+		//TODO default aus Locale auslesen und mit möglichen vergleichen... mmm.. vor laden von atlasml immer DEFAULT_KEY, also hier nicht
+
+		  // Get default locale
+	    Locale locale = Locale.getDefault();
+		setActiveLang(locale.getLanguage());
+	}
+	
+	@Override
+	public Translation clone() {
+		return (Translation) super.clone();
+	}
+
+	/**
+	 * Get the two-letter language sting that is active
+	 */
+	public static String getActiveLang() {
+		return activeLang;
+	}
+
+	/**
+	 * Set up the {@link Translation}-system to use language.
+	 * @param activeLang
+	 */
+	public static void setActiveLang(String activeLang) {
+		if (!I8NUtil.isValidISOLangCode(activeLang)) {
+			throw new IllegalArgumentException("'"+activeLang+"' is not a valid ISO language code.");
+		}
+
+		Locale.setDefault(new Locale(activeLang));
+		Translation.activeLang = activeLang;
+		log.info("Translation-system switched to "+activeLang);
+	}
+
+	/**
+	 * Initilises a new {@link Translation} with a default translation.
+	 * Other translations may be added later.
+	 *
+	 * @param defaultTranslation
+	 *
+	 * @deprecated SK: The concept of the default translation doesn't seem so nice to me anymore..
+	 * I would prefer the default translation to be set for all valid languages.
+	 *
+	 * @see public Translation(List<String> languages, String defaultTranslation) {
+	 *
+	 */
+	public Translation(String defaultTranslation) {
+		put(DEFAULT_KEY, defaultTranslation);
+	}
+
+	/**
+	 * Initilises a new {@link Translation}, an uses the given String to
+	 * initialise the {@link Translation} for all languages codes passed.
+	 *
+	 * The translations can be cahnged later
+	 */
+	public Translation(List<String> languages, String defaultTranslation) {
+		// put(DEFAULT_KEY, defaultTranslation);
+		if (languages == null) {
+			put(DEFAULT_KEY, defaultTranslation);
+		}
+		else for (String code : languages){
+			put(code, defaultTranslation);
+		}
+	}
+
+	/**
+	 * Sometimes Translations are optional, like for keywords.
+	 */
+	public Translation() {
+		super();
+	}
+
+	/**
+	 * Fills the {@link Translation} with the values coded into the String
+	 * Format of {@link String} is: "de{Baum}en{tree}"
+	 * <p>
+	 * <ul>
+	 * <li> If <code>oneLineCoded</code> is empty or null, NO TRANSLATION is set.
+	 * <li> If format can't be recognized, the {@link String} is interpreted as the translation in the <code>{@value #DEFAULT_KEY}</code> language
+	 *
+	 * @author Stefan Alfons Krüger
+	 */
+	public void fromOneLine( final String oneLineCoded) {
+		clear();
+		if ( (oneLineCoded == null) || (oneLineCoded.equals("")) ) {
+			put(DEFAULT_KEY,NO_TRANSLATION);
+			return;
+		}
+
+		if (oneLineCoded.indexOf("}") == -1) {
+		//	log.warn("The String '"+oneLineCoded+"' is not in oneLine coded => put(DEFAULT_KEY,oneLineCoded);");
+			put(DEFAULT_KEY,oneLineCoded);
+		}
+
+		String eatUp = oneLineCoded;
+		while ( eatUp.indexOf("}") != -1) {
+			String substring = eatUp.substring(0, eatUp.indexOf("}"));
+
+//			log.debug("substring = "+substring);
+			String key   = substring.substring(0, substring.indexOf("{") );
+			String value = substring.substring(substring.indexOf("{")+1, substring.length() );
+//			log.debug("key="+key);
+//			log.debug("value="+value);
+			put(key,value);
+			eatUp = eatUp.substring(eatUp.indexOf("}")+1);
+		}
+	}
+
+	/**
+	 * Exports the Translations to a String of the Format: "de{Baum}en{tree}"
+	 * @author Stefan Alfons Krüger
+	 */
+	public String toOneLine(){
+		StringBuffer oneLine = new StringBuffer();
+		for (String key: keySet()) {
+			oneLine.append(key+"{"+ get(key) + "}");
+		}
+		return oneLine.toString();
+	}
+
+	/**
+	 * Returns the right translation by using the {@link #activeLang} field.
+	 * If no translation is set, an ugly String {@link #NO_TRANSLATION} will re returned. This might be changed for the final release.
+	 * If the correct language was not found, any entry in the {@link Translation} {@link HashMap} will be returned, that contains
+	 *  more than an empty string.
+	 */
+	@Override
+	public String toString(){
+		if ( get(activeLang) != null ) {
+			return get(activeLang);
+		}
+                //****************************************************************************
+                // MS: The ISDSS needs the concept of the default lang!! So I took the
+                //     following in again!!
+                //****************************************************************************
+//		else return "";
+//		//****************************************************************************
+//		// The following is commented out.. the concept of the default lang seems to be bad....
+//		//****************************************************************************
+                // MS:
+		else {
+			if ( get(DEFAULT_KEY) != null ) {
+//				log.debug("default lng returned, cuz the translation to "+activeLang+" was not found. Schmeiss raus martin, wenn du das mit der default trans geklärt hast.");
+				return get(DEFAULT_KEY);
+			}
+
+			// log.debug("return first best <> '' ");
+			if (size() > 0)
+				for ( String s : values() ) {
+					if ( (s != null) && (s.trim().length()>0) )
+						return s;
+				}
+		}
+		log.warn("No translation found!");
+		return NO_TRANSLATION;
+	}
+
+	/**
+	 * Copy this {@link Translation} to another {@link Translation}
+	 * e.g. for editing
+	 *
+	 * @return the destination {@link Translation}
+	 */
+	public Translation copy(Translation backup) {
+		if (backup == null) throw new IllegalArgumentException("Target translation may not be null.");
+		for (String s : keySet() ) {
+			backup.put(s, get(s) );
+		}
+		return backup;
+	}
+
+}

Added: trunk/src/skrueger/swing/CancelButton.java
===================================================================
--- trunk/src/skrueger/swing/CancelButton.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/CancelButton.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,39 @@
+package skrueger.swing;
+
+import java.awt.Dimension;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+/**
+ * A Cancel {@link JButton} without text, but with an expressive {@link Icon}
+ * stat symbolizes CANCEL
+ * 
+ * @author Stefan Alfons Krüger
+ * 
+ */
+public class CancelButton extends JButton {
+	public static final Icon ICON_CANCEL_SMALL 		= new ImageIcon( CancelButton.class.getResource("small/cancel.png") );
+
+	private static final Dimension DIMENSION = new Dimension(35,25);
+	
+	/**
+	 * Creates a {@link JButton} with an icon that symbolizes OK
+	 */
+	public CancelButton() {
+		super(ICON_CANCEL_SMALL);
+		setMaximumSize(DIMENSION);
+	}
+
+	/**
+	 * Creates a {@link JButton} with an icon that symbolizes OK
+	 */
+	public CancelButton(Action a) {
+		super(a);
+		setIcon(ICON_CANCEL_SMALL);
+		setMaximumSize(DIMENSION);
+	}
+
+
+}

Added: trunk/src/skrueger/swing/OkButton.java
===================================================================
--- trunk/src/skrueger/swing/OkButton.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/OkButton.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,39 @@
+package skrueger.swing;
+
+import java.awt.Dimension;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+
+/**
+ * An Ok {@link JButton} without text, but with an expressive {@link Icon} that
+ * symbolizes OK
+ * 
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+ */
+public class OkButton extends JButton {
+
+	public static final Icon ICON_OK_SMALL = new ImageIcon(OkButton.class
+			.getResource("small/ok.png"));
+
+	private static final Dimension DIMENSION = new Dimension(35, 25);
+
+	/**
+	 * Creates a {@link JButton} with an icon that symbolizes OK
+	 */
+	public OkButton() {
+		super(ICON_OK_SMALL);
+		setMaximumSize(DIMENSION);
+	}
+
+	/**
+	 * Creates a {@link JButton} with an icon that symbolizes OK
+	 */
+	public OkButton(Action a) {
+		super(a);
+		setIcon(ICON_OK_SMALL);
+		setMaximumSize(DIMENSION);
+	}
+}

Added: trunk/src/skrueger/swing/TranslationAskJDialog.java
===================================================================
--- trunk/src/skrueger/swing/TranslationAskJDialog.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/TranslationAskJDialog.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,243 @@
+package skrueger.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
+
+import schmitzm.lang.LangUtil;
+import schmitzm.lang.ResourceProvider;
+import schmitzm.swing.SwingUtil;
+import skrueger.i8n.Translation;
+
+public class TranslationAskJDialog extends JDialog {
+
+	/**
+	 * {@link ResourceProvider}, der die Lokalisation fuer GUI-Komponenten des
+	 * Package {@code skrueger.swing} zur Verfuegung stellt. Diese sind in
+	 * properties-Datein unter {@code skrueger.swing.resource.locales}
+	 * hinterlegt.
+	 */
+	public static ResourceProvider RESOURCE = new ResourceProvider(LangUtil
+			.extendPackagePath(TranslationAskJDialog.class,
+					"resource.locales.SwingResourceBundle"), Locale.ENGLISH);
+
+	private String[] backup = new String[50]; // Maximum 50 languages ;-)
+	private OkButton okButton;
+	private CancelButton cancelButton;
+
+	public static final String PROPERTY_CANCEL_AND_CLOSE = "CANCEL";
+	public static final String PROPERTY_APPLY_AND_CLOSE = "APPLY";
+
+	private final JComponent[] translationEditJPanelsOrJustComponents;
+
+	private boolean hasBeenCanceled;
+
+	/**
+	 * Since the registerKeyboardAction() method is part of the JComponent class
+	 * definition, you must define the Escape keystroke and register the
+	 * keyboard action with a JComponent, not with a JDialog. The JRootPane for
+	 * the JDialog serves as an excellent choice to associate the registration,
+	 * as this will always be visible. If you override the protected
+	 * createRootPane() method of JDialog, you can return your custom JRootPane
+	 * with the keystroke enabled:
+	 */
+	@Override
+	protected JRootPane createRootPane() {
+		KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+		JRootPane rootPane = new JRootPane();
+		rootPane.registerKeyboardAction(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				cancel();
+			}
+
+		}, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+		return rootPane;
+	}
+
+	/**
+	 * This class handles the cancel button itself. You may still want to listen
+	 * to PROPERTY_APPLY_AND_CLOSE events.
+	 * 
+	 * This dialog is modal. The dialog has to be set visible afterwards.
+	 */
+	public TranslationAskJDialog(Dialog owner,
+			final JComponent... translationEditJPanels) {
+		super(owner);
+		this.translationEditJPanelsOrJustComponents = translationEditJPanels;
+		init();
+	}
+
+	/**
+	 * This class handles the cancel button itself. You may still want to listen
+	 * to PROPERTY_APPLY_AND_CLOSE events.
+	 * This dialog is modal. The dialog has to be set visible afterwards. 
+	 */
+	public TranslationAskJDialog(Window owner,
+			final JComponent... translationEditJPanels) {
+		super(owner);
+		this.translationEditJPanelsOrJustComponents = translationEditJPanels;
+		init();
+	}
+
+	private void init() {
+		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+		addWindowListener(new WindowAdapter() {
+
+			public void windowClosing(WindowEvent e) {
+				cancel();
+			}
+
+		});
+		SwingUtil.centerFrameOnScreen(this);
+		Box box = Box.createVerticalBox();
+		for (JComponent panel : translationEditJPanelsOrJustComponents) {
+			box.add(panel);
+		}
+		JPanel cp = new JPanel(new BorderLayout());
+		cp.add(box, BorderLayout.CENTER);
+		cp.add(getButtons(), BorderLayout.SOUTH);
+		setContentPane(cp);
+
+		// dialog.getRootPane().setDefaultButton(okButton);
+
+		setTitle(RESOURCE.getString("translation_dialog_title")); // i8n
+		setModal(true);
+		pack();
+	}
+
+	protected void cancel() {
+		firePropertyChange(PROPERTY_CANCEL_AND_CLOSE, null, null);
+		restore();
+		setVisible(false);
+		dispose();
+	}
+
+	private void restore() {
+		int count = 0;
+		for (JComponent component : translationEditJPanelsOrJustComponents) {
+			if (component instanceof TranslationEditJPanel) {
+				TranslationEditJPanel tep = (TranslationEditJPanel) component;
+				tep.getTranslation().fromOneLine(backup[count]);
+			}
+			count++;
+		}
+	}
+
+	private JComponent getButtons() {
+		JPanel jPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+		if (okButton == null) {
+			okButton = new OkButton(new AbstractAction() {
+				{
+					// Set a mnemonic character. In most look and feels, this
+					// causes the
+					// specified character to be underlined This indicates that
+					// if the component
+					// using this action has the focus and In some look and
+					// feels, this causes
+					// the specified character in the label to be underlined and
+					putValue(Action.MNEMONIC_KEY, new Integer(
+							java.awt.event.KeyEvent.VK_E));
+
+					// Set tool tip text
+					putValue(Action.SHORT_DESCRIPTION,
+							"Accept the changes made to the translation.");
+
+				}
+
+				public void actionPerformed(ActionEvent evt) {
+					TranslationAskJDialog.this.firePropertyChange(
+							PROPERTY_APPLY_AND_CLOSE, null, null);
+					setVisible(false);
+					dispose();
+					System.out.println("OK button action performed");
+				}
+
+			});
+			// okButton.addKeyListener( new KeyListener() {
+			//
+			// public void keyTyped(KeyEvent e) {
+			// if ()
+			// okButton.action(new KEyPreEvent(), what)
+			// }
+			//				
+			// });
+		}
+		jPanel.add(okButton);
+
+		if (cancelButton == null) {
+			cancelButton = new CancelButton(new AbstractAction("") {
+				public void actionPerformed(ActionEvent evt) {
+					// restore();
+					TranslationAskJDialog.this.firePropertyChange(
+							PROPERTY_CANCEL_AND_CLOSE, null, null);
+					setVisible(false);
+					setHasBeenCanceled(true);
+					dispose();
+				}
+			});
+		}
+		jPanel.add(cancelButton);
+
+		return jPanel;
+	}
+
+	public static void main(String[] args) {
+		ArrayList<String> lang = new ArrayList<String>();
+		lang.add("de");
+		lang.add("en");
+		lang.add("fr");
+
+		Translation transe = new Translation();
+		transe.put("de", "Terciopelo-Lanzenotter");
+		TranslationEditJPanel p1 = new TranslationEditJPanel(
+				"Name von New Group", transe, lang);
+
+		Translation transe2 = new Translation();
+		transe2
+				.put(
+						"de",
+						"Terciopelo-Lanzenotter (Bothrops asper) ist eine in Mittelamerika und im Nordwesten Südamerikas weit verbreitete Schlangenart.");
+		TranslationEditJPanel p2 = new TranslationEditJPanel(
+				"Description of Animal:", transe2, lang);
+
+		// JFrame frame = new JFrame();
+		// frame.setContentPane(p1);
+		// frame.pack();
+		// frame.setVisible(true);
+
+		TranslationAskJDialog dialog = new TranslationAskJDialog(null, p1, p2);
+		dialog.setVisible(true);
+	}
+
+	private void setHasBeenCanceled(boolean hasBeenCanceled) {
+		this.hasBeenCanceled = hasBeenCanceled;
+	}
+
+	/**
+	 * After the modal dialog has been closed, this allows to find out, wether the dialog has been canceled.
+	 * @return
+	 */
+	public boolean isHasBeenCanceled() {
+		return hasBeenCanceled;
+	}
+
+}

Added: trunk/src/skrueger/swing/TranslationEditJPanel.java
===================================================================
--- trunk/src/skrueger/swing/TranslationEditJPanel.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/TranslationEditJPanel.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,230 @@
+package skrueger.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SpringLayout;
+import javax.swing.SwingConstants;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.swing.SpringUtilities;
+import schmitzm.swing.SwingUtil;
+import skrueger.i8n.Translation;
+
+/**
+ * A JPanel that asks the user for translation of several strings 
+ * @author Stefan Alfons Krüger
+ *
+ */
+public class TranslationEditJPanel extends JPanel {
+	public static final String PROPERTY_CANCEL_AND_CLOSE = "CANCEL";
+	public static final String PROPERTY_APPLY_AND_CLOSE = "APPLY";
+	private static JDialog dialog;
+	private static OkButton okButton;
+	
+	private static CancelButton cancelButton;
+	Logger log = Logger.getLogger(TranslationEditJPanel.class);
+	private List<String> languages;
+	private JPanel translationGrid;
+	private Translation trans;
+
+	public TranslationEditJPanel(Translation trans, List<String> languages_) {
+		this(null, trans, languages_);
+	}
+
+	public TranslationEditJPanel(String question, Translation trans,
+			List<String> languages_) {
+		super(new BorderLayout());
+		
+		if (trans == null) trans = new Translation();
+
+		this.trans = trans;
+		this.languages = languages_;
+		
+		
+
+		add(getTranslationGrid(), BorderLayout.CENTER);
+
+		if (question != null) {
+			JLabel questionLable = new JLabel(question);
+			questionLable.setBorder(BorderFactory.createEmptyBorder(
+					6, 6, 6, 6));
+			add(questionLable, BorderLayout.NORTH);
+		}
+	}
+
+	private JPanel getTranslationGrid() {
+		if (translationGrid == null) {
+			translationGrid = new JPanel(new SpringLayout());
+
+			for (String langId : languages) {
+
+				// language code : entry field for translation
+				JLabel langDesc = new JLabel(langId.toUpperCase() + " :"); // i8n
+				langDesc.setHorizontalAlignment(SwingConstants.RIGHT);
+				langDesc.setVerticalAlignment(SwingConstants.NORTH);
+
+				TranslationJTextField langTextField = new TranslationJTextField(
+						trans, langId);
+				// Setting a size 
+				langTextField.setPreferredSize( new Dimension(360,22));
+				langDesc.setLabelFor(langTextField);
+				translationGrid.add(langDesc);
+				translationGrid.add(langTextField);
+			}
+
+			// Lay out the panel.
+			SpringUtilities.makeCompactGrid(translationGrid, languages.size(), // rows,
+					2, // cols
+					6, 6, // initX, initY
+					6, 6); // xPad, yPad
+
+		}
+		return translationGrid;
+	}
+
+	/**
+	 * Merges a few {@link TranslationEditJPanel}s and shows them together..
+	 * So far this is working on the Translation object directly, Cancel not possible.
+	 * @param translationEditJPanel
+	 * 
+	 * @deprecated Use {@link TranslationAskJDialog}
+	 */
+	public static void ask(Frame parentFrame, final TranslationEditJPanel... translationEditJPanels) {
+		dialog = new JDialog(parentFrame);
+	//	backup( translationEditJPanels );
+		showDialog(dialog, translationEditJPanels);
+	}
+
+	
+	/**
+	 * Merges a few {@link TranslationEditJPanel}s and shows them together..
+	 * So far this is working on the Translation object directly, Cancel not possible.
+	 * @param translationEditJPanel
+
+	 * @deprecated User {@link TranslationAskJDialog}	 * 
+	 */
+	public static void ask(Window parentWindow, TranslationEditJPanel... translationEditJPanels) {
+		dialog = new JDialog(parentWindow);
+	//	backup( translationEditJPanels );
+		showDialog(dialog, translationEditJPanels);
+	}
+	
+	/**
+	 * Merges a few {@link TranslationEditJPanel}s and shows them together..
+	 * So far this is working on the Translation object directly, Cancel not possible.
+	 * @param translationEditJPanel
+	 * 
+	 * 	 * @deprecated User {@link TranslationAskJDialog}
+	 */
+	public static void ask(JDialog parentDialog, TranslationEditJPanel... translationEditJPanels) {
+		dialog = new JDialog(parentDialog);
+	//	backup( translationEditJPanels );
+		showDialog(dialog, translationEditJPanels);
+	}
+	
+	
+	/**
+	 * 	 * @deprecated User {@link TranslationAskJDialog}
+	 * @param d
+	 * @param translationEditJPanels 
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	public static void showDialog(JDialog d, JComponent... translationEditJPanels) {
+		dialog = d;
+		dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+		SwingUtil.centerFrameOnScreen(dialog);
+		Box box = Box.createVerticalBox();
+		for (JComponent panel : translationEditJPanels) {
+			box.add(panel);
+		}
+		JPanel cp = new JPanel( new BorderLayout());
+		cp.add( box, BorderLayout.CENTER);
+		cp.add( getButtons(), BorderLayout.SOUTH  );
+		dialog.setContentPane(cp);
+		
+		// dialog.getRootPane().setDefaultButton(okButton);
+		
+		dialog.setTitle("Please translate"); //i8n
+		dialog.setModal(true);
+		dialog.pack();
+		dialog.setVisible(true);
+	}
+
+	/**
+	 * @deprecated Use TranslationAskJDialog
+	 * @return 
+	 * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons Kr&uuml;ger</a>
+	 */
+	private static JComponent getButtons() {
+		JPanel jPanel = new JPanel();
+		if (okButton == null) {
+			okButton = new OkButton(new AbstractAction("enter") {
+	            public void actionPerformed(ActionEvent evt) {
+	            	firePropertyChange(PROPERTY_APPLY_AND_CLOSE, null, null);
+	            	dialog.dispose();
+	            }
+	        } );
+		}
+		jPanel.add(okButton);
+
+		if (cancelButton == null) {
+			cancelButton = new CancelButton(new AbstractAction("") {
+	            public void actionPerformed(ActionEvent evt) {
+	            	// restore();
+	            	firePropertyChange(PROPERTY_CANCEL_AND_CLOSE, null, null);
+	            	dialog.dispose();
+	            }
+	        } );
+		}
+		jPanel.add(okButton);
+
+		return jPanel;
+	}
+	
+	public Translation getTranslation() {
+		return trans;
+	}
+//
+//	
+//	public static void main(String[] args) {
+//		ArrayList<String> lang = new ArrayList <String> ();
+//		lang.add("de");
+//		lang.add("en");
+//		lang.add("fr");
+//		
+//		Translation transe = new Translation();
+//		transe.put("de", "Terciopelo-Lanzenotter");
+//		TranslationEditJPanel p1 = new TranslationEditJPanel("Name von New Group",
+//				transe, lang);
+//		
+//		
+//		Translation transe2 = new Translation();
+//		transe2.put("de", "Terciopelo-Lanzenotter (Bothrops asper) ist eine in Mittelamerika und im Nordwesten Südamerikas weit verbreitete Schlangenart.");
+//		TranslationEditJPanel p2 = new TranslationEditJPanel("Description of Animal:",
+//				transe2, lang);
+//		
+////		JFrame frame = new JFrame();
+////		frame.setContentPane(p1);
+////		frame.pack();
+////		frame.setVisible(true);
+//		
+//		ask(new Frame(),p1,p2);
+//	}
+}

Added: trunk/src/skrueger/swing/TranslationJTextField.java
===================================================================
--- trunk/src/skrueger/swing/TranslationJTextField.java	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/TranslationJTextField.java	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1,110 @@
+package skrueger.swing;
+
+import java.awt.Color;
+
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.apache.log4j.Logger;
+
+import skrueger.i8n.Translation;
+
+/**
+ * A {@link JTextArea} that signals red when it is not filled by a value.
+ * 
+ * @author Stefan Alfons Krüger
+ * 
+ */
+public class TranslationJTextField extends JTextField {
+	static final Logger log = Logger.getLogger(TranslationJTextField.class);
+    int wasValid = 99; // 0 = false, 1 = true, 99 = undefined 
+	private String langCode;
+	private Translation trans;
+	
+	/**
+	 * This RED indicates a missing translation 
+	 * 
+	 * TODO to properties ;-)
+	 */
+	final static public Color emptyColor = new Color(240,190,190);
+
+	/**
+	 * Creates a new {@link TranslationJTextField}
+	 * @param trans 
+	 * @param langCode
+	 */
+	public TranslationJTextField(Translation trans, String langCode) {
+		super(trans.get(langCode));
+		this.trans = trans;
+		this.langCode = langCode;
+		
+		/** SK: Change  26.Mai 
+		 * Use the default for an empty field **/
+		if (trans.get(langCode) == null) {
+			String defaultTrans = trans.get( Translation.DEFAULT_KEY);
+			trans.put(langCode, defaultTrans);
+			setText( defaultTrans );
+		}
+		
+		checkValid();
+
+		// This Listener colors the JTextfiel red if it is empty
+		getDocument().addDocumentListener(new DocumentListener() {
+
+			// This method is called after an insert into the document
+			public void insertUpdate(DocumentEvent evt) {
+				checkValid();
+			}
+
+			// This method is called after a removal from the document
+			public void removeUpdate(DocumentEvent evt) {
+				checkValid();
+			}
+
+			// This method is called after one or more attributes have changed.
+			// This method is not called when characters are inserted with
+			// attributes.
+			public void changedUpdate(DocumentEvent evt) {
+				checkValid();
+			}
+		});
+	}
+
+	/**
+	 * If the getText == "". then set Background color red.
+	 * This change is only done if the state really changed.
+	 * And directly store the change in the {@link Translation} object
+	 */
+	protected void checkValid() {
+		
+		String trimmedText = getText().trim();
+		
+		if (trimmedText.equals("")) {
+			// Not valid
+			if (wasValid != 0) {		
+				setBackground( emptyColor );
+				repaint();
+				wasValid = 0;
+			}
+			
+			// TODO testen! hat sich das bewährt?
+			// Directly store the change in the Translation Object
+			// trans.remove( langCode );
+			trans.put( langCode, trimmedText );
+			
+		} else {
+			// valid
+			if (wasValid != 1) {
+				setBackground(Color.white);
+				repaint();
+				wasValid = 1;
+			}
+			// Directly store the change in the Translation Object
+			trans.put( langCode, trimmedText );
+		}
+		
+	}
+
+}

Added: trunk/src/skrueger/swing/resource/flags.jpg
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/swing/resource/flags.jpg
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/swing/resource/locales/SwingResourceBundle.properties
===================================================================
--- trunk/src/skrueger/swing/resource/locales/SwingResourceBundle.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/resource/locales/SwingResourceBundle.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1 @@
+translation_dialog_title=Please translate
\ No newline at end of file

Added: trunk/src/skrueger/swing/resource/locales/SwingResourceBundle_de.properties
===================================================================
--- trunk/src/skrueger/swing/resource/locales/SwingResourceBundle_de.properties	2009-02-24 22:34:32 UTC (rev 1)
+++ trunk/src/skrueger/swing/resource/locales/SwingResourceBundle_de.properties	2009-02-24 22:43:52 UTC (rev 2)
@@ -0,0 +1 @@
+translation_dialog_title=Bitte übersetzen
\ No newline at end of file

Added: trunk/src/skrueger/swing/small/cancel.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/swing/small/cancel.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/skrueger/swing/small/ok.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/skrueger/swing/small/ok.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream



More information about the Schmitzm-commits mailing list