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