001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/graphics/sld/Stroke.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.graphics.sld;
037    
038    import static java.awt.Color.decode;
039    import static org.deegree.framework.log.LoggerFactory.getLogger;
040    
041    import java.awt.Color;
042    import java.util.HashMap;
043    import java.util.StringTokenizer;
044    
045    import org.deegree.framework.log.ILogger;
046    import org.deegree.framework.util.ColorUtils;
047    import org.deegree.framework.xml.Marshallable;
048    import org.deegree.model.feature.Feature;
049    import org.deegree.model.filterencoding.Expression;
050    import org.deegree.model.filterencoding.FilterEvaluationException;
051    
052    /**
053     * A Stroke allows a string of line segments (or any linear geometry) to be rendered. There are three basic types of
054     * strokes: solid Color, GraphicFill (stipple), and repeated GraphicStroke. A repeated graphic is plotted linearly and
055     * has its graphic symbol bended around the curves of the line string. The default is a solid black line (Color
056     * "#000000").
057     * <p>
058     * The supported CSS-Parameter names are:
059     * <ul>
060     * <li>stroke (color)
061     * <li>stroke-opacity
062     * <li>stroke-width
063     * <li>stroke-linejoin
064     * <li>stroke-linecap
065     * <li>stroke-dasharray
066     * <li>stroke-dashoffset
067     * <p>
068     * 
069     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
070     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
071     * @version $Revision: 29792 $ $Date: 2011-02-23 14:04:07 +0100 (Wed, 23 Feb 2011) $
072     */
073    
074    public class Stroke extends Drawing implements Marshallable {
075    
076        private static final ILogger LOG = getLogger( Stroke.class );
077    
078        /**
079         * Wraps the java.awt.BasicStroke.JOIN_MITER constant (why)
080         */
081        public static final int LJ_MITRE = java.awt.BasicStroke.JOIN_MITER;
082    
083        /**
084         * Wraps the java.awt.BasicStroke.JOIN_ROUND constant (why)
085         */
086        public static final int LJ_ROUND = java.awt.BasicStroke.JOIN_ROUND;
087    
088        /**
089         * Wraps the java.awt.BasicStroke.JOIN_BEVEL constant (why)
090         */
091        public static final int LJ_BEVEL = java.awt.BasicStroke.JOIN_BEVEL;
092    
093        /**
094         * Wraps the java.awt.BasicStroke.CAP_BUTT constant (why)
095         */
096        public static final int LC_BUTT = java.awt.BasicStroke.CAP_BUTT;
097    
098        /**
099         * Wraps the java.awt.BasicStroke.CAP_ROUND constant (why)
100         */
101        public static final int LC_ROUND = java.awt.BasicStroke.CAP_ROUND;
102    
103        /**
104         * Wraps the java.awt.BasicStroke.CAP_SQUARE constant (why)
105         */
106        public static final int LC_SQUARE = java.awt.BasicStroke.CAP_SQUARE;
107    
108        // default values
109        /**
110         * Default color is black.
111         */
112        public static final Color COLOR_DEFAULT = Color.BLACK;
113    
114        /**
115         * Default opacity is 1
116         */
117        public static final double OPACITY_DEFAULT = 1.0;
118    
119        /**
120         * Default with is 1
121         */
122        public static final double WIDTH_DEFAULT = 1.0;
123    
124        /**
125         * Default Line join is mitre
126         */
127        public static final int LJ_DEFAULT = LJ_MITRE;
128    
129        /**
130         * default line end is butt
131         */
132        public static final int LC_DEFAULT = LC_BUTT;
133    
134        private GraphicStroke graphicStroke = null;
135    
136        private Color color = null;
137    
138        private double smplOpacity = -1;
139    
140        private double smplWidth = -1;
141    
142        private int smplLineJoin = -1;
143    
144        private int smplLineCap = -1;
145    
146        private float[] smplDashArray = null;
147    
148        private float smplDashOffset = -1;
149    
150        /**
151         * Constructs a new <tt>Stroke<tt>.
152         */
153        protected Stroke() {
154            super( new HashMap<String, Object>(), null );
155        }
156    
157        /**
158         * Constructs a new <tt>Stroke<tt>. <p>
159         * 
160         * @param cssParams
161         *            keys are <tt>Strings<tt> (see above), values are <tt>CssParameters</tt>
162         * @param graphicStroke
163         * @param graphicFill
164         */
165        public Stroke( HashMap<String, Object> cssParams, GraphicStroke graphicStroke, GraphicFill graphicFill ) {
166            super( cssParams, graphicFill );
167            this.graphicStroke = graphicStroke;
168            try {
169                extractSimpleColor();
170            } catch ( Exception e ) {
171                LOG.logWarning( "The color could not be parsed as string." );
172                LOG.logDebug( "Stack trace:", e );
173            }
174            try {
175                extractSimpleOpacity();
176            } catch ( Exception e ) {
177                LOG.logWarning( "The opacity could not be parsed as string." );
178                LOG.logDebug( "Stack trace:", e );
179            }
180            try {
181                extractSimpleWidth();
182            } catch ( Exception e ) {
183                LOG.logWarning( "The width could not be parsed as string." );
184                LOG.logDebug( "Stack trace:", e );
185            }
186            try {
187                extractSimpleLineJoin();
188            } catch ( Exception e ) {
189                LOG.logWarning( "The linejoin could not be parsed as string." );
190                LOG.logDebug( "Stack trace:", e );
191            }
192            try {
193                extractSimpleLineCap();
194            } catch ( Exception e ) {
195                LOG.logWarning( "The linecap could not be parsed as string." );
196                LOG.logDebug( "Stack trace:", e );
197            }
198            try {
199                extractSimpleDasharray();
200            } catch ( Exception e ) {
201                LOG.logWarning( "The dasharray could not be parsed as string." );
202                LOG.logDebug( "Stack trace:", e );
203            }
204            try {
205                extractSimpleDashOffset();
206            } catch ( Exception e ) {
207                LOG.logWarning( "The dashoffset could not be parsed as string." );
208                LOG.logDebug( "Stack trace:", e );
209            }
210        }
211    
212        /**
213         * extracts the color of the stroke if it is simple (nor Expression) to avoid new calculation for each call of
214         * getStroke(Feature feature)
215         */
216        private void extractSimpleColor()
217                                throws FilterEvaluationException {
218            CssParameter cssParam = (CssParameter) cssParams.get( "stroke" );
219            if ( cssParam != null ) {
220                Object[] o = cssParam.getValue().getComponents();
221                for ( int i = 0; i < o.length; i++ ) {
222                    if ( o[i] instanceof Expression ) {
223                        color = null;
224                        break;
225                    }
226                    try {
227                        // trimming the String to avoid parsing errors with newlines
228                        String s = o[i].toString().trim();
229                        if ( s.equals( "" ) ) {
230                            color = decode( s );
231                        }
232                    } catch ( NumberFormatException e ) {
233                        throw new FilterEvaluationException( "Given value ('" + o[i] + "') for CSS-Parameter 'stroke' "
234                                                             + "does not denote a valid color!" );
235                    }
236                }
237            }
238        }
239    
240        /**
241         * returns true if the passed CssParameter contain a simple value
242         */
243        private boolean isSimple( CssParameter cssParam ) {
244            boolean simple = true;
245            Object[] o = cssParam.getValue().getComponents();
246            for ( int i = 0; i < o.length; i++ ) {
247                if ( o[i] instanceof Expression ) {
248                    simple = false;
249                    break;
250                }
251            }
252            return simple;
253        }
254    
255        /**
256         * extracts the opacity of the stroke if it is simple (no Expression) to avoid new calculation for each call of
257         * getStroke(Feature feature)
258         */
259        private void extractSimpleOpacity()
260                                throws FilterEvaluationException {
261            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-opacity" );
262            if ( cssParam != null ) {
263                if ( isSimple( cssParam ) ) {
264                    Object[] o = cssParam.getValue().getComponents();
265                    try {
266                        smplOpacity = Double.parseDouble( (String) o[0] );
267                    } catch ( NumberFormatException e ) {
268                        throw new FilterEvaluationException( "Given value for parameter 'stroke-opacity' ('" + o[0]
269                                                             + "') has invalid format!" );
270                    }
271    
272                    if ( ( smplOpacity < 0.0 ) || ( smplOpacity > 1.0 ) ) {
273                        throw new FilterEvaluationException( "Value for parameter 'stroke-opacity' (given: '" + o[0]
274                                                             + "') must be between 0.0 and 1.0!" );
275                    }
276                }
277            }
278        }
279    
280        /**
281         * extracts the width of the stroke if it is simple (no Expression) to avoid new calculation for each call of
282         * getStroke(Feature feature)
283         */
284        private void extractSimpleWidth()
285                                throws FilterEvaluationException {
286            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-width" );
287            if ( cssParam != null ) {
288                if ( isSimple( cssParam ) ) {
289                    Object[] o = cssParam.getValue().getComponents();
290                    try {
291                        smplWidth = Double.parseDouble( (String) o[0] );
292                    } catch ( NumberFormatException e ) {
293                        throw new FilterEvaluationException( "Given value for parameter 'stroke-width' ('" + o[0]
294                                                             + "') has invalid format!" );
295                    }
296                    if ( smplWidth < 0.0 ) {
297                        throw new FilterEvaluationException( "Value for parameter 'stroke-width' (given: '" + smplWidth
298                                                             + "') must be > 0.0!" );
299                    }
300                }
301            }
302        }
303    
304        /**
305         * extracts the line join of the stroke if it is simple (no Expression) to avoid new calculation for each call of
306         * getStroke(Feature feature)
307         */
308        private void extractSimpleLineJoin()
309                                throws FilterEvaluationException {
310            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linejoin" );
311            if ( cssParam != null ) {
312                if ( isSimple( cssParam ) ) {
313                    Object[] o = cssParam.getValue().getComponents();
314                    String value = (String) o[0];
315                    if ( value.equals( "mitre" ) ) {
316                        smplLineJoin = Stroke.LJ_MITRE;
317                    } else if ( value.equals( "round" ) ) {
318                        smplLineJoin = Stroke.LJ_ROUND;
319                    } else if ( value.equals( "bevel" ) ) {
320                        smplLineJoin = Stroke.LJ_BEVEL;
321                    } else {
322                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linejoin' ('" + value
323                                                             + "') is unsupported. Supported values are: "
324                                                             + "'mitre', 'round' or 'bevel'!" );
325                    }
326                }
327            }
328        }
329    
330        /**
331         * extracts the line cap of the stroke if it is simple (no Expression) to avoid new calculation for each call of
332         * getStroke(Feature feature)
333         */
334        private void extractSimpleLineCap()
335                                throws FilterEvaluationException {
336            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linecap" );
337            if ( cssParam != null ) {
338                if ( isSimple( cssParam ) ) {
339                    Object[] o = cssParam.getValue().getComponents();
340                    String value = (String) o[0];
341                    if ( value.equals( "butt" ) ) {
342                        smplLineCap = Stroke.LC_BUTT;
343                    } else if ( value.equals( "round" ) ) {
344                        smplLineCap = Stroke.LC_ROUND;
345                    } else if ( value.equals( "square" ) ) {
346                        smplLineCap = Stroke.LC_SQUARE;
347                    } else {
348                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linecap' ('" + value
349                                                             + "') is unsupported. Supported values are: "
350                                                             + "'butt', 'round' or 'square'!" );
351                    }
352                }
353            }
354        }
355    
356        /**
357         * extracts the dasharray of the stroke if it is simple (no Expression) to avoid new calculation for each call of
358         * getStroke(Feature feature)
359         */
360        private void extractSimpleDasharray()
361                                throws FilterEvaluationException {
362            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dasharray" );
363            if ( cssParam != null ) {
364                if ( isSimple( cssParam ) ) {
365                    Object[] o = cssParam.getValue().getComponents();
366                    String value = (String) o[0];
367                    StringTokenizer st = new StringTokenizer( value, ",; " );
368                    int count = st.countTokens();
369                    float[] dashArray;
370    
371                    if ( ( count % 2 ) == 0 ) {
372                        dashArray = new float[count];
373                    } else {
374                        dashArray = new float[count * 2];
375                    }
376    
377                    int k = 0;
378                    while ( st.hasMoreTokens() ) {
379                        String s = st.nextToken();
380                        try {
381                            dashArray[k++] = Float.parseFloat( s );
382                        } catch ( NumberFormatException e ) {
383                            throw new FilterEvaluationException( "List of values for parameter 'stroke-dashoffset' "
384                                                                 + "contains an invalid token: '" + s + "'!" );
385                        }
386                    }
387    
388                    // odd number of values -> the pattern must be repeated twice
389                    if ( ( count % 2 ) == 1 ) {
390                        int j = 0;
391                        while ( k < ( ( count * 2 ) - 1 ) ) {
392                            dashArray[k++] = dashArray[j++];
393                        }
394                    }
395                    smplDashArray = dashArray;
396                }
397            }
398        }
399    
400        /**
401         * extracts the dash offset of the stroke if it is simple (no Expression) to avoid new calculation for each call of
402         * getStroke(Feature feature)
403         */
404        private void extractSimpleDashOffset()
405                                throws FilterEvaluationException {
406            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dashoffset" );
407            if ( cssParam != null ) {
408                if ( isSimple( cssParam ) ) {
409                    Object[] o = cssParam.getValue().getComponents();
410                    String value = (String) o[0];
411                    try {
412                        smplDashOffset = Float.parseFloat( value );
413                    } catch ( NumberFormatException e ) {
414                        throw new FilterEvaluationException( "Given value for parameter 'stroke-dashoffset' ('" + value
415                                                             + "') has invalid format!" );
416                    }
417                }
418            }
419        }
420    
421        /**
422         * The GraphicStroke element both indicates that a repeated-linear-graphic stroke type will be used.
423         * <p>
424         * 
425         * @return the underlying <tt>GraphicStroke</tt> instance (may be null)
426         * 
427         */
428        public GraphicStroke getGraphicStroke() {
429            return graphicStroke;
430        }
431    
432        /**
433         * The GraphicStroke element both indicates that a repeated-linear-graphic stroke type will be used.
434         * 
435         * @param graphicStroke
436         *            the graphicStroke element
437         *            <p>
438         * 
439         */
440        public void setGraphicStroke( GraphicStroke graphicStroke ) {
441            this.graphicStroke = graphicStroke;
442        }
443    
444        /**
445         * The stroke CssParameter element gives the solid color that will be used for a stroke. The color value is
446         * RGB-encoded using two hexadecimal digits per primary-color component, in the order Red, Green, Blue, prefixed
447         * with a hash (#) sign. The hexadecimal digits between A and F may be in either uppercase or lowercase. For
448         * example, full red is encoded as #ff0000 (with no quotation marks). The default color is defined to be black
449         * (#000000) in the context of the LineSymbolizer, if the stroke CssParameter element is absent.
450         * <p>
451         * 
452         * @param feature
453         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
454         * @return the (evaluated) value of the parameter
455         * @throws FilterEvaluationException
456         *             if the evaluation fails
457         */
458        public Color getStroke( Feature feature )
459                                throws FilterEvaluationException {
460            Color awtColor = COLOR_DEFAULT;
461    
462            if ( color == null ) {
463                // evaluate color depending on the passed feature's properties
464                CssParameter cssParam = (CssParameter) cssParams.get( "stroke" );
465    
466                if ( cssParam != null ) {
467                    String s = cssParam.getValue( feature );
468    
469                    if ( !s.equalsIgnoreCase( "random" ) ) {
470                        try {
471                            awtColor = Color.decode( s );
472                        } catch ( NumberFormatException e ) {
473                            throw new FilterEvaluationException( "Given value ('" + s + "') for CSS-Parameter 'stroke' "
474                                                                 + "does not denote a valid color!" );
475                        }
476                    } else {
477                        awtColor = ColorUtils.getRandomColor( false );
478                    }
479                }
480            } else {
481                awtColor = color;
482            }
483    
484            return awtColor;
485        }
486    
487        /**
488         * @see org.deegree.graphics.sld.Stroke#getStroke(Feature) <p>
489         * @param stroke
490         *            the stroke to be set
491         */
492        public void setStroke( Color stroke ) {
493            this.color = stroke;
494            CssParameter strokeColor = StyleFactory.createCssParameter( "stroke", ColorUtils.toHexCode( "#", stroke ) );
495            cssParams.put( "stroke", strokeColor );
496        }
497    
498        /**
499         * The stroke-opacity CssParameter element specifies the level of translucency to use when rendering the stroke. The
500         * value is encoded as a floating-point value (float) between 0.0 and 1.0 with 0.0 representing completely
501         * transparent and 1.0 representing completely opaque, with a linear scale of translucency for intermediate values.
502         * For example, 0.65 would represent 65% opacity. The default value is 1.0 (opaque).
503         * <p>
504         * 
505         * @param feature
506         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
507         * @return the (evaluated) value of the parameter
508         * @throws FilterEvaluationException
509         *             if the evaluation fails
510         */
511        public double getOpacity( Feature feature )
512                                throws FilterEvaluationException {
513            double opacity = OPACITY_DEFAULT;
514    
515            if ( smplOpacity < 0 ) {
516                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-opacity" );
517    
518                if ( cssParam != null ) {
519                    // evaluate opacity depending on the passed feature's properties
520                    String value = cssParam.getValue( feature );
521    
522                    if ( !value.equalsIgnoreCase( "random" ) ) {
523                        try {
524                            opacity = Double.parseDouble( value );
525                        } catch ( NumberFormatException e ) {
526                            throw new FilterEvaluationException( "Given value for parameter 'stroke-opacity' ('" + value
527                                                                 + "') has invalid format!" );
528                        }
529                    } else {
530                        opacity = 0.5 + Double.parseDouble( value ) / 2d;
531                    }
532    
533                    if ( ( opacity < 0.0 ) || ( opacity > 1.0 ) ) {
534                        throw new FilterEvaluationException( "Value for parameter 'stroke-opacity' (given: '" + value
535                                                             + "') must be between 0.0 and 1.0!" );
536                    }
537                }
538            } else {
539                opacity = smplOpacity;
540            }
541    
542            return opacity;
543        }
544    
545        /**
546         * @see org.deegree.graphics.sld.Stroke#getOpacity(Feature) <p>
547         * @param opacity
548         *            the opacity to be set for the stroke
549         */
550        public void setOpacity( double opacity ) {
551            if ( opacity > 1 ) {
552                opacity = 1;
553            } else if ( opacity < 0 ) {
554                opacity = 0;
555            }
556            this.smplOpacity = opacity;
557            CssParameter strokeOp = StyleFactory.createCssParameter( "stroke-opacity", "" + opacity );
558            cssParams.put( "stroke-opacity", strokeOp );
559        }
560    
561        /**
562         * The stroke-width CssParameter element gives the absolute width (thickness) of a stroke in pixels encoded as a
563         * float. (Arguably, more units could be provided for encoding sizes, such as millimeters or typesetter's points.)
564         * The default is 1.0. Fractional numbers are allowed (with a system-dependent interpretation) but negative numbers
565         * are not.
566         * <p>
567         * 
568         * @param feature
569         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
570         * @return the (evaluated) value of the parameter
571         * @throws FilterEvaluationException
572         *             if the evaluation fails
573         */
574        public double getWidth( Feature feature )
575                                throws FilterEvaluationException {
576            double width = WIDTH_DEFAULT;
577    
578            if ( smplWidth < 0 ) {
579                // evaluate smplWidth depending on the passed feature's properties
580                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-width" );
581    
582                if ( cssParam != null && feature != null ) {
583                    String value = cssParam.getValue( feature );
584    
585                    try {
586                        width = Double.parseDouble( value );
587                    } catch ( NumberFormatException e ) {
588                        throw new FilterEvaluationException( "Given value for parameter 'stroke-width' ('" + value
589                                                             + "') has invalid format!" );
590                    }
591    
592                    if ( width <= 0.0 ) {
593                        throw new FilterEvaluationException( "Value for parameter 'stroke-width' (given: '" + value
594                                                             + "') must be greater than 0!" );
595                    }
596                }
597            } else {
598                width = smplWidth;
599            }
600    
601            return width;
602        }
603    
604        /**
605         * @see org.deegree.graphics.sld.Stroke#getWidth(Feature) <p>
606         * @param width
607         *            the width to be set for the stroke
608         */
609        public void setWidth( double width ) {
610            if ( width <= 0 )
611                width = 1;
612            this.smplWidth = width;
613            CssParameter strokeWi = StyleFactory.createCssParameter( "stroke-width", "" + width );
614            cssParams.put( "stroke-width", strokeWi );
615        }
616    
617        /**
618         * The stroke-linejoin CssParameter element encode enumerated values telling how line strings should be joined
619         * (between line segments). The values are represented as content strings. The allowed values for line join are
620         * mitre, round, and bevel.
621         * <p>
622         * 
623         * @param feature
624         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
625         * @return the (evaluated) value of the parameter
626         * @throws FilterEvaluationException
627         *             if the evaluation fails
628         */
629        public int getLineJoin( Feature feature )
630                                throws FilterEvaluationException {
631            int lineJoin = LJ_DEFAULT;
632    
633            if ( smplLineJoin < 0 ) {
634                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linejoin" );
635    
636                if ( cssParam != null ) {
637                    String value = cssParam.getValue( feature );
638    
639                    if ( value.equals( "mitre" ) ) {
640                        lineJoin = Stroke.LJ_MITRE;
641                    } else if ( value.equals( "round" ) ) {
642                        lineJoin = Stroke.LJ_ROUND;
643                    } else if ( value.equals( "bevel" ) ) {
644                        lineJoin = Stroke.LJ_BEVEL;
645                    } else {
646                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linejoin' ('" + value
647                                                             + "') is unsupported. Supported values are: "
648                                                             + "'mitre', 'round' or 'bevel'!" );
649                    }
650                }
651            } else {
652                lineJoin = smplLineJoin;
653            }
654    
655            return lineJoin;
656        }
657    
658        /**
659         * @see org.deegree.graphics.sld.Stroke#getLineJoin(Feature) <p>
660         * @param lineJoin
661         *            the lineJoin to be set for the stroke
662         */
663        public void setLineJoin( int lineJoin ) {
664            String join = null;
665            if ( lineJoin == Stroke.LJ_MITRE ) {
666                join = "mitre";
667            } else if ( lineJoin == Stroke.LJ_ROUND ) {
668                join = "round";
669            } else if ( lineJoin == Stroke.LJ_BEVEL ) {
670                join = "bevel";
671            } else {
672                // default
673                lineJoin = Stroke.LJ_BEVEL;
674                join = "bevel";
675            }
676            smplLineJoin = lineJoin;
677            CssParameter strokeLJ = StyleFactory.createCssParameter( "stroke-linejoin", join );
678            cssParams.put( "stroke-linejoin", strokeLJ );
679        }
680    
681        /**
682         * Thestroke-linecap CssParameter element encode enumerated values telling how line strings should be capped (at the
683         * two ends of the line string). The values are represented as content strings. The allowed values for line cap are
684         * butt, round, and square. The default values are system-dependent.
685         * <p>
686         * 
687         * @param feature
688         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
689         * @return the (evaluated) value of the parameter
690         * @throws FilterEvaluationException
691         *             if the evaluation fails
692         */
693        public int getLineCap( Feature feature )
694                                throws FilterEvaluationException {
695            int lineCap = LC_DEFAULT;
696    
697            if ( smplLineCap < 0 ) {
698    
699                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linecap" );
700    
701                if ( cssParam != null ) {
702                    String value = cssParam.getValue( feature );
703    
704                    if ( value.equals( "butt" ) ) {
705                        lineCap = Stroke.LC_BUTT;
706                    } else if ( value.equals( "round" ) ) {
707                        lineCap = Stroke.LC_ROUND;
708                    } else if ( value.equals( "square" ) ) {
709                        lineCap = Stroke.LC_SQUARE;
710                    } else {
711                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linecap' ('" + value
712                                                             + "') is unsupported. Supported values are: "
713                                                             + "'butt', 'round' or 'square'!" );
714                    }
715                }
716            } else {
717                lineCap = smplLineCap;
718            }
719    
720            return lineCap;
721        }
722    
723        /**
724         * @see org.deegree.graphics.sld.Stroke#getLineCap(Feature) <p>
725         * @param lineCap
726         *            lineCap to be set for the stroke
727         */
728        public void setLineCap( int lineCap ) {
729            String cap = null;
730            if ( lineCap == Stroke.LC_BUTT ) {
731                cap = "butt";
732            } else if ( lineCap == Stroke.LC_ROUND ) {
733                cap = "round";
734            } else if ( lineCap == Stroke.LC_SQUARE ) {
735                cap = "square";
736            } else {
737                // default;
738                cap = "round";
739                lineCap = Stroke.LC_SQUARE;
740            }
741            smplLineCap = lineCap;
742            CssParameter strokeCap = StyleFactory.createCssParameter( "stroke-linecap", cap );
743            cssParams.put( "stroke-linecap", strokeCap );
744        }
745    
746        /**
747         * Evaluates the 'stroke-dasharray' parameter as defined in OGC 02-070. The stroke-dasharray CssParameter element
748         * encodes a dash pattern as a series of space separated floats. The first number gives the length in pixels of dash
749         * to draw, the second gives the amount of space to leave, and this pattern repeats. If an odd number of values is
750         * given, then the pattern is expanded by repeating it twice to give an even number of values. Decimal values have a
751         * system-dependent interpretation (usually depending on whether antialiasing is being used). The default is to draw
752         * an unbroken line.
753         * <p>
754         * 
755         * @param feature
756         *            the encoded pattern
757         * @throws FilterEvaluationException
758         *             if the eevaluation fails or the encoded pattern is erroneous
759         * @return the decoded pattern as an array of float-values (null if the parameter was not specified)
760         */
761        public float[] getDashArray( Feature feature )
762                                throws FilterEvaluationException {
763            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dasharray" );
764    
765            float[] dashArray = null;
766            if ( smplDashArray == null ) {
767                if ( cssParam == null ) {
768                    return null;
769                }
770    
771                String value = cssParam.getValue( feature );
772    
773                StringTokenizer st = new StringTokenizer( value, ",; " );
774                int count = st.countTokens();
775    
776                if ( ( count % 2 ) == 0 ) {
777                    dashArray = new float[count];
778                } else {
779                    dashArray = new float[count * 2];
780                }
781    
782                int i = 0;
783                while ( st.hasMoreTokens() ) {
784                    String s = st.nextToken();
785                    try {
786                        dashArray[i++] = Float.parseFloat( s );
787                    } catch ( NumberFormatException e ) {
788                        throw new FilterEvaluationException( "List of values for parameter 'stroke-dashoffset' "
789                                                             + "contains an invalid token: '" + s + "'!" );
790                    }
791                }
792    
793                // odd number of values -> the pattern must be repeated twice
794                if ( ( count % 2 ) == 1 ) {
795                    int j = 0;
796                    while ( i < ( ( count * 2 ) - 1 ) ) {
797                        dashArray[i++] = dashArray[j++];
798                    }
799                }
800            } else {
801                dashArray = smplDashArray;
802            }
803    
804            return dashArray;
805        }
806    
807        /**
808         * @see org.deegree.graphics.sld.Stroke#getDashArray(Feature) <p>
809         * @param dashArray
810         *            the dashArray to be set for the Stroke
811         */
812        public void setDashArray( float[] dashArray ) {
813            if ( dashArray != null ) {
814                String s = "";
815                for ( int i = 0; i < dashArray.length - 1; i++ ) {
816                    s = s + dashArray[i] + ",";
817                }
818                s = s + dashArray[dashArray.length - 1];
819                smplDashArray = dashArray;
820                CssParameter strokeDash = StyleFactory.createCssParameter( "stroke-dasharray", s );
821                cssParams.put( "stroke-dasharray", strokeDash );
822            }
823        }
824    
825        /**
826         * The stroke-dashoffset CssParameter element specifies the distance as a float into the stroke-dasharray pattern at
827         * which to start drawing.
828         * <p>
829         * 
830         * @param feature
831         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
832         * @return the (evaluated) value of the parameter
833         * @throws FilterEvaluationException
834         *             if the evaluation fails
835         */
836        public float getDashOffset( Feature feature )
837                                throws FilterEvaluationException {
838            float dashOffset = 0;
839    
840            if ( smplDashOffset < 0 ) {
841                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dashoffset" );
842                if ( cssParam != null ) {
843                    String value = cssParam.getValue( feature );
844    
845                    try {
846                        dashOffset = Float.parseFloat( value );
847                    } catch ( NumberFormatException e ) {
848                        throw new FilterEvaluationException( "Given value for parameter 'stroke-dashoffset' ('" + value
849                                                             + "') has invalid format!" );
850                    }
851                }
852            } else {
853                dashOffset = smplDashOffset;
854            }
855    
856            return dashOffset;
857        }
858    
859        /**
860         * The stroke-dashoffset CssParameter element specifies the distance as a float into the stroke-dasharray pattern at
861         * which to start drawing.
862         * <p>
863         * 
864         * @param dashOffset
865         *            the dashOffset to be set for the Stroke
866         */
867        public void setDashOffset( float dashOffset ) {
868            if ( dashOffset < 0 )
869                dashOffset = 0;
870            smplDashOffset = dashOffset;
871            CssParameter strokeDashOff = StyleFactory.createCssParameter( "stroke-dashoffset", "" + dashOffset );
872            cssParams.put( "stroke-dashoffset", strokeDashOff );
873        }
874    
875        /**
876         * exports the content of the Stroke as XML formated String
877         * 
878         * @return xml representation of the Stroke
879         */
880        public String exportAsXML() {
881    
882            StringBuffer sb = new StringBuffer( 1000 );
883            sb.append( "<Stroke>" );
884    
885            if ( graphicFill != null ) {
886                sb.append( ( (Marshallable) graphicFill ).exportAsXML() );
887            } else if ( graphicStroke != null ) {
888                sb.append( ( (Marshallable) graphicStroke ).exportAsXML() );
889            }
890            for ( Object o : cssParams.values() ) {
891                sb.append( ( (Marshallable) o ).exportAsXML() );
892            }
893    
894            sb.append( "</Stroke>" );
895    
896            return sb.toString();
897        }
898    
899    }