groovy.lang.GString Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.lang;
import org.apache.groovy.io.StringBuilderWriter;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.StringGroovyMethods;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.regex.Pattern;
/**
* Represents a String which contains embedded values such as "hello there
* ${user} how are you?" which can be evaluated lazily. Advanced users can
* iterate over the text and values to perform special processing, such as for
* performing SQL operations, the values can be substituted for ? and the
* actual value objects can be bound to a JDBC statement. The lovely name of
* this class was suggested by Jules Gosnell and was such a good idea, I
* couldn't resist :)
*
* @author James Strachan
*/
public abstract class GString extends GroovyObjectSupport implements Comparable, CharSequence, Writable, Buildable, Serializable {
private static final long serialVersionUID = -2638020355892246323L;
private static final String MKP = "mkp";
private static final String YIELD = "yield";
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* A GString containing a single empty String and no values.
*/
public static final GString EMPTY = new GString(EMPTY_OBJECT_ARRAY) {
private static final long serialVersionUID = -7676746462783374250L;
@Override
public String[] getStrings() {
return new String[]{ "" };
}
};
private final Object[] values;
public GString(Object values) {
this.values = (Object[]) values;
}
public GString(Object[] values) {
this.values = values;
}
// will be static in an instance
public abstract String[] getStrings();
/**
* Overloaded to implement duck typing for Strings
* so that any method that can't be evaluated on this
* object will be forwarded to the toString() object instead.
*/
@Override
public Object invokeMethod(String name, Object args) {
try {
return super.invokeMethod(name, args);
}
catch (MissingMethodException e) {
// lets try invoke the method on the real String
return InvokerHelper.invokeMethod(toString(), name, args);
}
}
public Object[] getValues() {
return values;
}
public GString plus(GString that) {
Object[] values = getValues();
return new GStringImpl(appendValues(values, that.getValues()), appendStrings(getStrings(), that.getStrings(), values.length));
}
private String[] appendStrings(String[] strings, String[] thatStrings, int valuesLength) {
int stringsLength = strings.length;
boolean isStringsLonger = stringsLength > valuesLength;
int thatStringsLength = isStringsLonger ? thatStrings.length - 1 : thatStrings.length;
String[] newStrings = new String[stringsLength + thatStringsLength];
System.arraycopy(strings, 0, newStrings, 0, stringsLength);
if (isStringsLonger) {
// merge onto end of previous GString to avoid an empty bridging value
System.arraycopy(thatStrings, 1, newStrings, stringsLength, thatStringsLength);
int lastIndexOfStrings = stringsLength - 1;
newStrings[lastIndexOfStrings] = strings[lastIndexOfStrings] + thatStrings[0];
} else {
System.arraycopy(thatStrings, 0, newStrings, stringsLength, thatStringsLength);
}
return newStrings;
}
private Object[] appendValues(Object[] values, Object[] thatValues) {
int valuesLength = values.length;
int thatValuesLength = thatValues.length;
Object[] newValues = new Object[valuesLength + thatValuesLength];
System.arraycopy(values, 0, newValues, 0, valuesLength);
System.arraycopy(thatValues, 0, newValues, valuesLength, thatValuesLength);
return newValues;
}
public GString plus(String that) {
return plus(new GStringImpl(EMPTY_OBJECT_ARRAY, new String[] { that }));
}
public int getValueCount() {
return values.length;
}
public Object getValue(int idx) {
return values[idx];
}
@Override
public String toString() {
Writer buffer = new StringBuilderWriter(calcInitialCapacity());
try {
writeTo(buffer);
}
catch (IOException e) {
throw new StringWriterIOException(e);
}
return buffer.toString();
}
private int calcInitialCapacity() {
String[] strings = getStrings();
int initialCapacity = 0;
for (String string : strings) {
initialCapacity += string.length();
}
initialCapacity += values.length * Math.max(initialCapacity / strings.length, 8);
return Math.max((int) (initialCapacity * 1.2), 16);
}
@Override
public Writer writeTo(Writer out) throws IOException {
String[] s = getStrings();
int numberOfValues = values.length;
for (int i = 0, size = s.length; i < size; i++) {
out.write(s[i]);
if (i < numberOfValues) {
final Object value = values[i];
if (value instanceof Closure) {
final Closure c = (Closure) value;
int maximumNumberOfParameters = c.getMaximumNumberOfParameters();
if (maximumNumberOfParameters == 0) {
InvokerHelper.write(out, c.call());
} else if (maximumNumberOfParameters == 1) {
c.call(out);
} else {
throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking "
+ maximumNumberOfParameters + " parameters");
}
} else {
InvokerHelper.write(out, value);
}
}
}
return out;
}
/* (non-Javadoc)
* @see groovy.lang.Buildable#build(groovy.lang.GroovyObject)
*/
@Override
public void build(final GroovyObject builder) {
final String[] s = getStrings();
final int numberOfValues = values.length;
for (int i = 0, size = s.length; i < size; i++) {
builder.getProperty(MKP);
builder.invokeMethod(YIELD, new Object[]{ s[i] });
if (i < numberOfValues) {
builder.getProperty(MKP);
builder.invokeMethod(YIELD, new Object[]{ values[i] });
}
}
}
@Override
public int hashCode() {
return 37 + toString().hashCode();
}
@Override
public boolean equals(Object that) {
if (that instanceof GString) {
return equals((GString) that);
}
return false;
}
public boolean equals(GString that) {
return toString().equals(that.toString());
}
@Override
public int compareTo(Object that) {
return toString().compareTo(that.toString());
}
@Override
public char charAt(int index) {
return toString().charAt(index);
}
@Override
public int length() {
return toString().length();
}
@Override
public CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
/**
* Turns a String into a regular expression pattern
*
* @return the regular expression pattern
*/
public Pattern negate() {
return StringGroovyMethods.bitwiseNegate(toString());
}
public byte[] getBytes() {
return toString().getBytes();
}
public byte[] getBytes(String charset) throws UnsupportedEncodingException {
return toString().getBytes(charset);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy