001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/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: 18328 $ $Date: 2009-07-06 16:00:35 +0200 (Mo, 06. Jul 2009) $
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                    try {
470                        awtColor = Color.decode( s );
471                    } catch ( NumberFormatException e ) {
472                        throw new FilterEvaluationException( "Given value ('" + s + "') for CSS-Parameter 'stroke' "
473                                                             + "does not denote a valid color!" );
474                    }
475                }
476            } else {
477                awtColor = color;
478            }
479    
480            return awtColor;
481        }
482    
483        /**
484         * @see org.deegree.graphics.sld.Stroke#getStroke(Feature) <p>
485         * @param stroke
486         *            the stroke to be set
487         */
488        public void setStroke( Color stroke ) {
489            this.color = stroke;
490            CssParameter strokeColor = StyleFactory.createCssParameter( "stroke", ColorUtils.toHexCode( "#", stroke ) );
491            cssParams.put( "stroke", strokeColor );
492        }
493    
494        /**
495         * The stroke-opacity CssParameter element specifies the level of translucency to use when rendering the stroke. The
496         * value is encoded as a floating-point value (float) between 0.0 and 1.0 with 0.0 representing completely
497         * transparent and 1.0 representing completely opaque, with a linear scale of translucency for intermediate values.
498         * For example, 0.65 would represent 65% opacity. The default value is 1.0 (opaque).
499         * <p>
500         *
501         * @param feature
502         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
503         * @return the (evaluated) value of the parameter
504         * @throws FilterEvaluationException
505         *             if the evaluation fails
506         */
507        public double getOpacity( Feature feature )
508                                throws FilterEvaluationException {
509            double opacity = OPACITY_DEFAULT;
510    
511            if ( smplOpacity < 0 ) {
512                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-opacity" );
513    
514                if ( cssParam != null ) {
515                    // evaluate opacity depending on the passed feature's properties
516                    String value = cssParam.getValue( feature );
517    
518                    try {
519                        opacity = Double.parseDouble( value );
520                    } catch ( NumberFormatException e ) {
521                        throw new FilterEvaluationException( "Given value for parameter 'stroke-opacity' ('" + value
522                                                             + "') has invalid format!" );
523                    }
524    
525                    if ( ( opacity < 0.0 ) || ( opacity > 1.0 ) ) {
526                        throw new FilterEvaluationException( "Value for parameter 'stroke-opacity' (given: '" + value
527                                                             + "') must be between 0.0 and 1.0!" );
528                    }
529                }
530            } else {
531                opacity = smplOpacity;
532            }
533    
534            return opacity;
535        }
536    
537        /**
538         * @see org.deegree.graphics.sld.Stroke#getOpacity(Feature) <p>
539         * @param opacity
540         *            the opacity to be set for the stroke
541         */
542        public void setOpacity( double opacity ) {
543            if ( opacity > 1 ) {
544                opacity = 1;
545            } else if ( opacity < 0 ) {
546                opacity = 0;
547            }
548            this.smplOpacity = opacity;
549            CssParameter strokeOp = StyleFactory.createCssParameter( "stroke-opacity", "" + opacity );
550            cssParams.put( "stroke-opacity", strokeOp );
551        }
552    
553        /**
554         * The stroke-width CssParameter element gives the absolute width (thickness) of a stroke in pixels encoded as a
555         * float. (Arguably, more units could be provided for encoding sizes, such as millimeters or typesetter's points.)
556         * The default is 1.0. Fractional numbers are allowed (with a system-dependent interpretation) but negative numbers
557         * are not.
558         * <p>
559         *
560         * @param feature
561         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
562         * @return the (evaluated) value of the parameter
563         * @throws FilterEvaluationException
564         *             if the evaluation fails
565         */
566        public double getWidth( Feature feature )
567                                throws FilterEvaluationException {
568            double width = WIDTH_DEFAULT;
569    
570            if ( smplWidth < 0 ) {
571                // evaluate smplWidth depending on the passed feature's properties
572                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-width" );
573    
574                if ( cssParam != null ) {
575                    String value = cssParam.getValue( feature );
576    
577                    try {
578                        width = Double.parseDouble( value );
579                    } catch ( NumberFormatException e ) {
580                        throw new FilterEvaluationException( "Given value for parameter 'stroke-width' ('" + value
581                                                             + "') has invalid format!" );
582                    }
583    
584                    if ( width <= 0.0 ) {
585                        throw new FilterEvaluationException( "Value for parameter 'stroke-width' (given: '" + value
586                                                             + "') must be greater than 0!" );
587                    }
588                }
589            } else {
590                width = smplWidth;
591            }
592    
593            return width;
594        }
595    
596        /**
597         * @see org.deegree.graphics.sld.Stroke#getWidth(Feature) <p>
598         * @param width
599         *            the width to be set for the stroke
600         */
601        public void setWidth( double width ) {
602            if ( width <= 0 )
603                width = 1;
604            this.smplWidth = width;
605            CssParameter strokeWi = StyleFactory.createCssParameter( "stroke-width", "" + width );
606            cssParams.put( "stroke-width", strokeWi );
607        }
608        
609        /**
610         * The stroke-linejoin CssParameter element encode enumerated values telling how line strings should be joined
611         * (between line segments). The values are represented as content strings. The allowed values for line join are
612         * mitre, round, and bevel.
613         * <p>
614         *
615         * @param feature
616         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
617         * @return the (evaluated) value of the parameter
618         * @throws FilterEvaluationException
619         *             if the evaluation fails
620         */
621        public int getLineJoin( Feature feature )
622                                throws FilterEvaluationException {
623            int lineJoin = LJ_DEFAULT;
624    
625            if ( smplLineJoin < 0 ) {
626                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linejoin" );
627    
628                if ( cssParam != null ) {
629                    String value = cssParam.getValue( feature );
630    
631                    if ( value.equals( "mitre" ) ) {
632                        lineJoin = Stroke.LJ_MITRE;
633                    } else if ( value.equals( "round" ) ) {
634                        lineJoin = Stroke.LJ_ROUND;
635                    } else if ( value.equals( "bevel" ) ) {
636                        lineJoin = Stroke.LJ_BEVEL;
637                    } else {
638                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linejoin' ('" + value
639                                                             + "') is unsupported. Supported values are: "
640                                                             + "'mitre', 'round' or 'bevel'!" );
641                    }
642                }
643            } else {
644                lineJoin = smplLineJoin;
645            }
646    
647            return lineJoin;
648        }
649    
650        /**
651         * @see org.deegree.graphics.sld.Stroke#getLineJoin(Feature) <p>
652         * @param lineJoin
653         *            the lineJoin to be set for the stroke
654         */
655        public void setLineJoin( int lineJoin ) {
656            String join = null;
657            if ( lineJoin == Stroke.LJ_MITRE ) {
658                join = "mitre";
659            } else if ( lineJoin == Stroke.LJ_ROUND ) {
660                join = "round";
661            } else if ( lineJoin == Stroke.LJ_BEVEL ) {
662                join = "bevel";
663            } else {
664                // default
665                lineJoin = Stroke.LJ_BEVEL;
666                join = "bevel";
667            }
668            smplLineJoin = lineJoin;
669            CssParameter strokeLJ = StyleFactory.createCssParameter( "stroke-linejoin", join );
670            cssParams.put( "stroke-linejoin", strokeLJ );
671        }
672    
673        /**
674         * Thestroke-linecap CssParameter element encode enumerated values telling how line strings should be capped (at the
675         * two ends of the line string). The values are represented as content strings. The allowed values for line cap are
676         * butt, round, and square. The default values are system-dependent.
677         * <p>
678         *
679         * @param feature
680         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
681         * @return the (evaluated) value of the parameter
682         * @throws FilterEvaluationException
683         *             if the evaluation fails
684         */
685        public int getLineCap( Feature feature )
686                                throws FilterEvaluationException {
687            int lineCap = LC_DEFAULT;
688    
689            if ( smplLineCap < 0 ) {
690    
691                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-linecap" );
692    
693                if ( cssParam != null ) {
694                    String value = cssParam.getValue( feature );
695    
696                    if ( value.equals( "butt" ) ) {
697                        lineCap = Stroke.LC_BUTT;
698                    } else if ( value.equals( "round" ) ) {
699                        lineCap = Stroke.LC_ROUND;
700                    } else if ( value.equals( "square" ) ) {
701                        lineCap = Stroke.LC_SQUARE;
702                    } else {
703                        throw new FilterEvaluationException( "Given value for parameter 'stroke-linecap' ('" + value
704                                                             + "') is unsupported. Supported values are: "
705                                                             + "'butt', 'round' or 'square'!" );
706                    }
707                }
708            } else {
709                lineCap = smplLineCap;
710            }
711    
712            return lineCap;
713        }
714    
715        /**
716         * @see org.deegree.graphics.sld.Stroke#getLineCap(Feature) <p>
717         * @param lineCap
718         *            lineCap to be set for the stroke
719         */
720        public void setLineCap( int lineCap ) {
721            String cap = null;
722            if ( lineCap == Stroke.LC_BUTT ) {
723                cap = "butt";
724            } else if ( lineCap == Stroke.LC_ROUND ) {
725                cap = "round";
726            } else if ( lineCap == Stroke.LC_SQUARE ) {
727                cap = "square";
728            } else {
729                // default;
730                cap = "round";
731                lineCap = Stroke.LC_SQUARE;
732            }
733            smplLineCap = lineCap;
734            CssParameter strokeCap = StyleFactory.createCssParameter( "stroke-linecap", cap );
735            cssParams.put( "stroke-linecap", strokeCap );
736        }
737    
738        /**
739         * Evaluates the 'stroke-dasharray' parameter as defined in OGC 02-070. The stroke-dasharray CssParameter element
740         * encodes a dash pattern as a series of space separated floats. The first number gives the length in pixels of dash
741         * to draw, the second gives the amount of space to leave, and this pattern repeats. If an odd number of values is
742         * given, then the pattern is expanded by repeating it twice to give an even number of values. Decimal values have a
743         * system-dependent interpretation (usually depending on whether antialiasing is being used). The default is to draw
744         * an unbroken line.
745         * <p>
746         *
747         * @param feature
748         *            the encoded pattern
749         * @throws FilterEvaluationException
750         *             if the eevaluation fails or the encoded pattern is erroneous
751         * @return the decoded pattern as an array of float-values (null if the parameter was not specified)
752         */
753        public float[] getDashArray( Feature feature )
754                                throws FilterEvaluationException {
755            CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dasharray" );
756    
757            float[] dashArray = null;
758            if ( smplDashArray == null ) {
759                if ( cssParam == null ) {
760                    return null;
761                }
762    
763                String value = cssParam.getValue( feature );
764    
765                StringTokenizer st = new StringTokenizer( value, ",; " );
766                int count = st.countTokens();
767    
768                if ( ( count % 2 ) == 0 ) {
769                    dashArray = new float[count];
770                } else {
771                    dashArray = new float[count * 2];
772                }
773    
774                int i = 0;
775                while ( st.hasMoreTokens() ) {
776                    String s = st.nextToken();
777                    try {
778                        dashArray[i++] = Float.parseFloat( s );
779                    } catch ( NumberFormatException e ) {
780                        throw new FilterEvaluationException( "List of values for parameter 'stroke-dashoffset' "
781                                                             + "contains an invalid token: '" + s + "'!" );
782                    }
783                }
784    
785                // odd number of values -> the pattern must be repeated twice
786                if ( ( count % 2 ) == 1 ) {
787                    int j = 0;
788                    while ( i < ( ( count * 2 ) - 1 ) ) {
789                        dashArray[i++] = dashArray[j++];
790                    }
791                }
792            } else {
793                dashArray = smplDashArray;
794            }
795    
796            return dashArray;
797        }
798    
799        /**
800         * @see org.deegree.graphics.sld.Stroke#getDashArray(Feature) <p>
801         * @param dashArray
802         *            the dashArray to be set for the Stroke
803         */
804        public void setDashArray( float[] dashArray ) {
805            if ( dashArray != null ) {
806                String s = "";
807                for ( int i = 0; i < dashArray.length - 1; i++ ) {
808                    s = s + dashArray[i] + ",";
809                }
810                s = s + dashArray[dashArray.length - 1];
811                smplDashArray = dashArray;
812                CssParameter strokeDash = StyleFactory.createCssParameter( "stroke-dasharray", s );
813                cssParams.put( "stroke-dasharray", strokeDash );
814            }
815        }
816    
817        /**
818         * The stroke-dashoffset CssParameter element specifies the distance as a float into the stroke-dasharray pattern at
819         * which to start drawing.
820         * <p>
821         *
822         * @param feature
823         *            specifies the <tt>Feature</tt> to be used for evaluation of the underlying 'sld:ParameterValueType'
824         * @return the (evaluated) value of the parameter
825         * @throws FilterEvaluationException
826         *             if the evaluation fails
827         */
828        public float getDashOffset( Feature feature )
829                                throws FilterEvaluationException {
830            float dashOffset = 0;
831    
832            if ( smplDashOffset < 0 ) {
833                CssParameter cssParam = (CssParameter) cssParams.get( "stroke-dashoffset" );
834                if ( cssParam != null ) {
835                    String value = cssParam.getValue( feature );
836    
837                    try {
838                        dashOffset = Float.parseFloat( value );
839                    } catch ( NumberFormatException e ) {
840                        throw new FilterEvaluationException( "Given value for parameter 'stroke-dashoffset' ('" + value
841                                                             + "') has invalid format!" );
842                    }
843                }
844            } else {
845                dashOffset = smplDashOffset;
846            }
847    
848            return dashOffset;
849        }
850    
851        /**
852         * The stroke-dashoffset CssParameter element specifies the distance as a float into the stroke-dasharray pattern at
853         * which to start drawing.
854         * <p>
855         *
856         * @param dashOffset
857         *            the dashOffset to be set for the Stroke
858         */
859        public void setDashOffset( float dashOffset ) {
860            if ( dashOffset < 0 )
861                dashOffset = 0;
862            smplDashOffset = dashOffset;
863            CssParameter strokeDashOff = StyleFactory.createCssParameter( "stroke-dashoffset", "" + dashOffset );
864            cssParams.put( "stroke-dashoffset", strokeDashOff );
865        }
866    
867        /**
868         * exports the content of the Stroke as XML formated String
869         *
870         * @return xml representation of the Stroke
871         */
872        public String exportAsXML() {
873    
874            StringBuffer sb = new StringBuffer( 1000 );
875            sb.append( "<Stroke>" );
876    
877            if ( graphicFill != null ) {
878                sb.append( ( (Marshallable) graphicFill ).exportAsXML() );
879            } else if ( graphicStroke != null ) {
880                sb.append( ( (Marshallable) graphicStroke ).exportAsXML() );
881            }
882            for ( Object o : cssParams.values() ) {
883                sb.append( ( (Marshallable) o ).exportAsXML() );
884            }
885    
886            sb.append( "</Stroke>" );
887    
888            return sb.toString();
889        }
890    
891    }