001 /*
002 * $Id: GString.java,v 1.16 2005/11/04 08:33:57 tug Exp $
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.InvokerHelper;
045
046 /**
047 * Represents a String which contains embedded values such as "hello there
048 * ${user} how are you?" which can be evaluated lazily. Advanced users can
049 * iterate over the text and values to perform special processing, such as for
050 * performing SQL operations, the values can be substituted for ? and the
051 * actual value objects can be bound to a JDBC statement. The lovely name of
052 * this class was suggested by Jules Gosnell and was such a good idea, I
053 * couldn't resist :)
054 *
055 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
056 * @version $Revision: 1.16 $
057 */
058 public abstract class GString extends GroovyObjectSupport implements Comparable, CharSequence, Writable, Buildable {
059
060 private Object[] values;
061
062 public GString(Object values) {
063 this.values = (Object[]) values;
064 }
065
066 public GString(Object[] values) {
067 this.values = values;
068 }
069
070 // will be static in an instance
071 public abstract String[] getStrings();
072
073 /**
074 * Overloaded to implement duck typing for Strings
075 * so that any method that can't be evaluated on this
076 * object will be forwarded to the toString() object instead.
077 */
078 public Object invokeMethod(String name, Object args) {
079 try {
080 return super.invokeMethod(name, args);
081 }
082 catch (MissingMethodException e) {
083 // lets try invoke the method on the real String
084 return InvokerHelper.invokeMethod(toString(), name, args);
085 }
086 }
087
088 public Object[] getValues() {
089 return values;
090 }
091
092 public GString plus(GString that) {
093 List stringList = new ArrayList();
094 List valueList = new ArrayList();
095
096 stringList.addAll(Arrays.asList(getStrings()));
097 valueList.addAll(Arrays.asList(getValues()));
098
099 if (stringList.size() > valueList.size()) {
100 valueList.add("");
101 }
102
103 stringList.addAll(Arrays.asList(that.getStrings()));
104 valueList.addAll(Arrays.asList(that.getValues()));
105
106 final String[] newStrings = new String[stringList.size()];
107 stringList.toArray(newStrings);
108 Object[] newValues = valueList.toArray();
109
110 return new GString(newValues) {
111 public String[] getStrings() {
112 return newStrings;
113 }
114 };
115 }
116
117 public GString plus(String that) {
118 String[] currentStrings = getStrings();
119 String[] newStrings = null;
120 Object[] newValues = null;
121
122 newStrings = new String[currentStrings.length + 1];
123 newValues = new Object[getValues().length + 1];
124 int lastIndex = currentStrings.length;
125 System.arraycopy(currentStrings, 0, newStrings, 0, lastIndex);
126 System.arraycopy(getValues(), 0, newValues, 0, getValues().length);
127 newStrings[lastIndex] = that;
128 newValues[getValues().length] = "";
129
130 final String[] finalStrings = newStrings;
131 return new GString(newValues) {
132
133 public String[] getStrings() {
134 return finalStrings;
135 }
136 };
137 }
138
139 public int getValueCount() {
140 return values.length;
141 }
142
143 public Object getValue(int idx) {
144 return values[idx];
145 }
146
147 public String toString() {
148 StringWriter buffer = new StringWriter();
149 try {
150 writeTo(buffer);
151 }
152 catch (IOException e) {
153 throw new StringWriterIOException(e);
154 }
155 return buffer.toString();
156 }
157
158 public Writer writeTo(Writer out) throws IOException {
159 String[] s = getStrings();
160 int numberOfValues = values.length;
161 for (int i = 0, size = s.length; i < size; i++) {
162 out.write(s[i]);
163 if (i < numberOfValues) {
164 InvokerHelper.write(out, values[i]);
165 }
166 }
167 return out;
168 }
169
170 /* (non-Javadoc)
171 * @see groovy.lang.Buildable#build(groovy.lang.GroovyObject)
172 */
173 public void build(final GroovyObject builder) {
174 final String[] s = getStrings();
175 final int numberOfValues = values.length;
176
177 for (int i = 0, size = s.length; i < size; i++) {
178 builder.getProperty("mkp");
179 builder.invokeMethod("yield", new Object[]{s[i]});
180 if (i < numberOfValues) {
181 builder.getProperty("mkp");
182 builder.invokeMethod("yield", new Object[]{values[i]});
183 }
184 }
185 }
186
187 public boolean equals(Object that) {
188 if (that instanceof GString) {
189 return equals((GString) that);
190 }
191 return false;
192 }
193
194 public boolean equals(GString that) {
195 return toString().equals(that.toString());
196 }
197
198 public int hashCode() {
199 return 37 + toString().hashCode();
200 }
201
202 public int compareTo(Object that) {
203 return toString().compareTo(that.toString());
204 }
205
206 public char charAt(int index) {
207 return toString().charAt(index);
208 }
209
210 public int length() {
211 return toString().length();
212 }
213
214 public CharSequence subSequence(int start, int end) {
215 return toString().subSequence(start, end);
216 }
217
218 /**
219 * Turns a String into a regular expression pattern
220 *
221 * @return the regular expression pattern
222 */
223 public Pattern negate() {
224 return InvokerHelper.regexPattern(toString());
225 }
226 }