001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.math.geometry;
019    
020    import java.text.FieldPosition;
021    import java.text.NumberFormat;
022    import java.text.ParseException;
023    import java.text.ParsePosition;
024    import java.util.Locale;
025    
026    import org.apache.commons.math.MathRuntimeException;
027    import org.apache.commons.math.util.CompositeFormat;
028    
029    /**
030     * Formats a 3D vector in components list format "{x; y; z}".
031     * <p>The prefix and suffix "{" and "}" and the separator "; " can be replaced by
032     * any user-defined strings. The number format for components can be configured.</p>
033     * <p>White space is ignored at parse time, even if it is in the prefix, suffix
034     * or separator specifications. So even if the default separator does include a space
035     * character that is used at format time, both input string "{1;1;1}" and
036     * " { 1 ; 1 ; 1 } " will be parsed without error and the same vector will be
037     * returned. In the second case, however, the parse position after parsing will be
038     * just after the closing curly brace, i.e. just before the trailing space.</p>
039     *
040     * @version $Revision: 772119 $ $Date: 2009-05-06 05:43:28 -0400 (Wed, 06 May 2009) $
041     */
042    public class Vector3DFormat extends CompositeFormat {
043    
044        /** Serializable version identifier */
045        private static final long serialVersionUID = -5447606608652576301L;
046    
047        /** The default prefix: "{". */
048        private static final String DEFAULT_PREFIX = "{";
049    
050        /** The default suffix: "}". */
051        private static final String DEFAULT_SUFFIX = "}";
052    
053        /** The default separator: ", ". */
054        private static final String DEFAULT_SEPARATOR = "; ";
055    
056        /** Prefix. */
057        private final String prefix;
058    
059        /** Suffix. */
060        private final String suffix;
061    
062        /** Separator. */
063        private final String separator;
064    
065        /** Trimmed prefix. */
066        private final String trimmedPrefix;
067    
068        /** Trimmed suffix. */
069        private final String trimmedSuffix;
070    
071        /** Trimmed separator. */
072        private final String trimmedSeparator;
073    
074        /** The format used for components. */
075        private NumberFormat format;
076    
077        /**
078         * Create an instance with default settings.
079         * <p>The instance uses the default prefix, suffix and separator:
080         * "{", "}", and "; " and the default number format for components.</p>
081         */
082        public Vector3DFormat() {
083            this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, getDefaultNumberFormat());
084        }
085    
086        /**
087         * Create an instance with a custom number format for components.
088         * @param format the custom format for components.
089         */
090        public Vector3DFormat(final NumberFormat format) {
091            this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
092        }
093    
094        /**
095         * Create an instance with custom prefix, suffix and separator.
096         * @param prefix prefix to use instead of the default "{"
097         * @param suffix suffix to use instead of the default "}"
098         * @param separator separator to use instead of the default "; "
099         */
100        public Vector3DFormat(final String prefix, final String suffix,
101                              final String separator) {
102            this(prefix, suffix, separator, getDefaultNumberFormat());
103        }
104    
105        /**
106         * Create an instance with custom prefix, suffix, separator and format
107         * for components.
108         * @param prefix prefix to use instead of the default "{"
109         * @param suffix suffix to use instead of the default "}"
110         * @param separator separator to use instead of the default "; "
111         * @param format the custom format for components.
112         */
113        public Vector3DFormat(final String prefix, final String suffix,
114                              final String separator, final NumberFormat format) {
115            this.prefix      = prefix;
116            this.suffix      = suffix;
117            this.separator   = separator;
118            trimmedPrefix    = prefix.trim();
119            trimmedSuffix    = suffix.trim();
120            trimmedSeparator = separator.trim();
121            this.format      = format;
122        }
123    
124        /**
125         * Get the set of locales for which 3D vectors formats are available.
126         * <p>This is the same set as the {@link NumberFormat} set.</p>
127         * @return available 3D vector format locales.
128         */
129        public static Locale[] getAvailableLocales() {
130            return NumberFormat.getAvailableLocales();
131        }
132    
133        /**
134         * Get the format prefix.
135         * @return format prefix.
136         */
137        public String getPrefix() {
138            return prefix;
139        }
140    
141        /**
142         * Get the format suffix.
143         * @return format suffix.
144         */
145        public String getSuffix() {
146            return suffix;
147        }
148    
149        /**
150         * Get the format separator between components.
151         * @return format separator.
152         */
153        public String getSeparator() {
154            return separator;
155        }
156    
157        /**
158         * Get the components format.
159         * @return components format.
160         */
161        public NumberFormat getFormat() {
162            return format;
163        }
164    
165        /**
166         * Returns the default 3D vector format for the current locale.
167         * @return the default 3D vector format.
168         */
169        public static Vector3DFormat getInstance() {
170            return getInstance(Locale.getDefault());
171        }
172    
173        /**
174         * Returns the default 3D vector format for the given locale.
175         * @param locale the specific locale used by the format.
176         * @return the 3D vector format specific to the given locale.
177         */
178        public static Vector3DFormat getInstance(final Locale locale) {
179            return new Vector3DFormat(getDefaultNumberFormat(locale));
180        }
181    
182        /**
183         * This static method calls {@link #format(Object)} on a default instance of
184         * Vector3DFormat.
185         *
186         * @param v Vector3D object to format
187         * @return A formatted vector
188         */
189        public static String formatVector3D(Vector3D v) {
190            return getInstance().format(v);
191        }
192    
193        /**
194         * Formats a {@link Vector3D} object to produce a string.
195         * @param vector the object to format.
196         * @param toAppendTo where the text is to be appended
197         * @param pos On input: an alignment field, if desired. On output: the
198         *            offsets of the alignment field
199         * @return the value passed in as toAppendTo.
200         */
201        public StringBuffer format(Vector3D vector, StringBuffer toAppendTo,
202                                   FieldPosition pos) {
203    
204            pos.setBeginIndex(0);
205            pos.setEndIndex(0);
206    
207            // format prefix
208            toAppendTo.append(prefix);
209    
210            // format components
211            formatDouble(vector.getX(), format, toAppendTo, pos);
212            toAppendTo.append(separator);
213            formatDouble(vector.getY(), format, toAppendTo, pos);
214            toAppendTo.append(separator);
215            formatDouble(vector.getZ(), format, toAppendTo, pos);
216    
217            // format suffix
218            toAppendTo.append(suffix);
219    
220            return toAppendTo;
221    
222        }
223    
224        /**
225         * Formats a object to produce a string.
226         * <p><code>obj</code> must be a  {@link Vector3D} object. Any other type of
227         * object will result in an {@link IllegalArgumentException} being thrown.</p>
228         * @param obj the object to format.
229         * @param toAppendTo where the text is to be appended
230         * @param pos On input: an alignment field, if desired. On output: the
231         *            offsets of the alignment field
232         * @return the value passed in as toAppendTo.
233         * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
234         * @throws IllegalArgumentException is <code>obj</code> is not a valid type.
235         */
236        @Override
237        public StringBuffer format(Object obj, StringBuffer toAppendTo,
238                                   FieldPosition pos) {
239    
240            if (obj instanceof Vector3D) {
241                return format( (Vector3D)obj, toAppendTo, pos);
242            }
243    
244            throw MathRuntimeException.createIllegalArgumentException("cannot format a {0} instance as a 3D vector",
245                                                                      obj.getClass().getName());
246    
247        }
248    
249        /**
250         * Parses a string to produce a {@link Vector3D} object.
251         * @param source the string to parse
252         * @return the parsed {@link Vector3D} object.
253         * @exception ParseException if the beginning of the specified string
254         *            cannot be parsed.
255         */
256        public Vector3D parse(String source) throws ParseException {
257            ParsePosition parsePosition = new ParsePosition(0);
258            Vector3D result = parse(source, parsePosition);
259            if (parsePosition.getIndex() == 0) {
260                throw MathRuntimeException.createParseException(
261                        parsePosition.getErrorIndex(),
262                        "unparseable 3D vector: \"{0}\"", source);
263            }
264            return result;
265        }
266    
267        /**
268         * Parses a string to produce a {@link Vector3D} object.
269         * @param source the string to parse
270         * @param pos input/ouput parsing parameter.
271         * @return the parsed {@link Vector3D} object.
272         */
273        public Vector3D parse(String source, ParsePosition pos) {
274            int initialIndex = pos.getIndex();
275    
276            // parse prefix
277            parseAndIgnoreWhitespace(source, pos);
278            if (!parseFixedstring(source, trimmedPrefix, pos)) {
279                return null;
280            }
281    
282            // parse X component
283            parseAndIgnoreWhitespace(source, pos);
284            Number x = parseNumber(source, format, pos);
285            if (x == null) {
286                // invalid abscissa
287                // set index back to initial, error index should already be set
288                pos.setIndex(initialIndex);
289                return null;
290            }
291    
292            // parse Y component
293            parseAndIgnoreWhitespace(source, pos);
294            if (!parseFixedstring(source, trimmedSeparator, pos)) {
295                return null;
296            }
297            parseAndIgnoreWhitespace(source, pos);
298            Number y = parseNumber(source, format, pos);
299            if (y == null) {
300                // invalid ordinate
301                // set index back to initial, error index should already be set
302                pos.setIndex(initialIndex);
303                return null;
304            }
305    
306            // parse Z component
307            parseAndIgnoreWhitespace(source, pos);
308            if (!parseFixedstring(source, trimmedSeparator, pos)) {
309                return null;
310            }
311            parseAndIgnoreWhitespace(source, pos);
312            Number z = parseNumber(source, format, pos);
313            if (z == null) {
314                // invalid height
315                // set index back to initial, error index should already be set
316                pos.setIndex(initialIndex);
317                return null;
318            }
319    
320            // parse suffix
321            parseAndIgnoreWhitespace(source, pos);
322            if (!parseFixedstring(source, trimmedSuffix, pos)) {
323                return null;
324            }
325    
326            return new Vector3D(x.doubleValue(), y.doubleValue(), z.doubleValue());
327    
328        }
329    
330        /**
331         * Parses a string to produce a object.
332         * @param source the string to parse
333         * @param pos input/ouput parsing parameter.
334         * @return the parsed object.
335         * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
336         */
337        @Override
338        public Object parseObject(String source, ParsePosition pos) {
339            return parse(source, pos);
340        }
341    
342    }