001    /*
002     * $Id: GString.java 4098 2006-10-10 16:09:48Z blackdrag $
003     * 
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     * 
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     * 
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *  
033     */
034    package groovy.lang;
035    
036    import java.io.IOException;
037    import java.io.StringWriter;
038    import java.io.Writer;
039    import java.util.ArrayList;
040    import java.util.Arrays;
041    import java.util.List;
042    import java.util.regex.Pattern;
043    
044    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
045    import org.codehaus.groovy.runtime.InvokerHelper;
046    
047    /**
048     * Represents a String which contains embedded values such as "hello there
049     * ${user} how are you?" which can be evaluated lazily. Advanced users can
050     * iterate over the text and values to perform special processing, such as for
051     * performing SQL operations, the values can be substituted for ? and the
052     * actual value objects can be bound to a JDBC statement. The lovely name of
053     * this class was suggested by Jules Gosnell and was such a good idea, I
054     * couldn't resist :)
055     * 
056     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
057     * @version $Revision: 4098 $
058     */
059    public abstract class GString extends GroovyObjectSupport implements Comparable, CharSequence, Writable, Buildable {
060    
061        private Object[] values;
062    
063        public GString(Object values) {
064            this.values = (Object[]) values;
065        }
066    
067        public GString(Object[] values) {
068            this.values = values;
069        }
070    
071        // will be static in an instance
072        public abstract String[] getStrings();
073    
074        /**
075         * Overloaded to implement duck typing for Strings 
076         * so that any method that can't be evaluated on this
077         * object will be forwarded to the toString() object instead.
078         */
079        public Object invokeMethod(String name, Object args) {
080            try {
081                return super.invokeMethod(name, args);
082            }
083            catch (MissingMethodException e) {
084                // lets try invoke the method on the real String
085                return InvokerHelper.invokeMethod(toString(), name, args);
086            }
087        }
088    
089        public Object[] getValues() {
090            return values;
091        }
092    
093        public GString plus(GString that) {
094            List stringList = new ArrayList();
095            List valueList = new ArrayList();
096    
097            stringList.addAll(Arrays.asList(getStrings()));
098            valueList.addAll(Arrays.asList(getValues()));
099    
100            if (stringList.size() > valueList.size()) {
101                valueList.add("");
102            }
103    
104            stringList.addAll(Arrays.asList(that.getStrings()));
105            valueList.addAll(Arrays.asList(that.getValues()));
106    
107            final String[] newStrings = new String[stringList.size()];
108            stringList.toArray(newStrings);
109            Object[] newValues = valueList.toArray();
110    
111            return new GString(newValues) {
112                public String[] getStrings() {
113                    return newStrings;
114                }
115            };
116        }
117    
118        public GString plus(String that) {
119            String[] currentStrings = getStrings();
120            String[] newStrings = null;
121            Object[] newValues = null;
122    
123            newStrings = new String[currentStrings.length + 1];
124            newValues = new Object[getValues().length + 1];
125            int lastIndex = currentStrings.length;
126            System.arraycopy(currentStrings, 0, newStrings, 0, lastIndex);
127            System.arraycopy(getValues(), 0, newValues, 0, getValues().length);
128            newStrings[lastIndex] = that;
129            newValues[getValues().length] = "";
130    
131            final String[] finalStrings = newStrings;
132            return new GString(newValues) {
133    
134                public String[] getStrings() {
135                    return finalStrings;
136                }
137            };
138        }
139    
140        public int getValueCount() {
141            return values.length;
142        }
143    
144        public Object getValue(int idx) {
145            return values[idx];
146        }
147    
148        public String toString() {
149            StringWriter buffer = new StringWriter();
150            try {
151                writeTo(buffer);
152            }
153            catch (IOException e) {
154                throw new StringWriterIOException(e);
155            }
156            return buffer.toString();
157        }
158    
159        public Writer writeTo(Writer out) throws IOException {
160            String[] s = getStrings();
161            int numberOfValues = values.length;
162            for (int i = 0, size = s.length; i < size; i++) {
163                out.write(s[i]);
164                if (i < numberOfValues) {
165                    InvokerHelper.write(out, values[i]);
166                }
167            }
168            return out;
169        }
170    
171        /* (non-Javadoc)
172         * @see groovy.lang.Buildable#build(groovy.lang.GroovyObject)
173         */
174        public void build(final GroovyObject builder) {
175        final String[] s = getStrings();
176        final int numberOfValues = values.length;
177            
178            for (int i = 0, size = s.length; i < size; i++) {
179                builder.getProperty("mkp");
180                builder.invokeMethod("yield", new Object[]{s[i]});
181                if (i < numberOfValues) {
182                    builder.getProperty("mkp");
183                    builder.invokeMethod("yield", new Object[]{values[i]});
184                }
185            }
186        }
187    
188        public boolean equals(Object that) {
189            if (that instanceof GString) {
190                return equals((GString) that);
191            }
192            return false;
193        }
194    
195        public boolean equals(GString that) {
196            return toString().equals(that.toString());
197        }
198    
199        public int hashCode() {
200            return 37 + toString().hashCode();
201        }
202    
203        public int compareTo(Object that) {
204            return toString().compareTo(that.toString());
205        }
206    
207        public char charAt(int index) {
208            return toString().charAt(index);
209        }
210    
211        public int length() {
212            return toString().length();
213        }
214    
215        public CharSequence subSequence(int start, int end) {
216            return toString().subSequence(start, end);
217        }
218    
219        /**
220         * Turns a String into a regular expression pattern
221         *
222         * @return the regular expression pattern
223         */
224        public Pattern negate() {
225            return DefaultGroovyMethods.negate(toString());
226        }
227    }