/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * * Arnold Lankamp - interfaces and implementation * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.value.impl.primitive; import java.nio.CharBuffer; import org.rascalmpl.value.IString; import org.rascalmpl.value.IValue; import org.rascalmpl.value.impl.AbstractValue; import org.rascalmpl.value.type.Type; import org.rascalmpl.value.type.TypeFactory; import org.rascalmpl.value.visitors.IValueVisitor; /** * Implementation of IString. * * @author Arnold Lankamp */ /*package*/ class StringValue { private final static Type STRING_TYPE = TypeFactory.getInstance().stringType(); /*package*/ static IString newString(String value) { if (value ==null) value = ""; return newString(value, containsSurrogatePairs(value)); } /*package*/ static IString newString(String value, boolean fullUnicode) { if (value ==null) value = ""; if (fullUnicode) { return new FullUnicodeString(value); } return new SimpleUnicodeString(value); } private static boolean containsSurrogatePairs(String str) { if (str == null) { return false; } int len = str.length(); for (int i = 1; i < len; i++) { if (Character.isSurrogatePair(str.charAt(i - 1), str.charAt(i))) { return true; } } return false; } private static class FullUnicodeString extends AbstractValue implements IString { protected final String value; private FullUnicodeString(String value){ super(); this.value = value; } @Override public Type getType(){ return STRING_TYPE; } @Override public String getValue(){ return value; } @Override public IString concat(IString other){ StringBuilder buffer = new StringBuilder(); buffer.append(value); buffer.append(other.getValue()); return StringValue.newString(buffer.toString(), true); } @Override public int compare(IString other){ int result = value.compareTo(other.getValue()); if(result > 0) return 1; if(result < 0) return -1; return 0; } @Override public <T, E extends Throwable> T accept(IValueVisitor<T,E> v) throws E{ return v.visitString(this); } public int hashCode(){ return value.hashCode(); } public boolean equals(Object o){ if(o == null) return false; if(this == o) return true; if(o.getClass() == getClass()){ FullUnicodeString otherString = (FullUnicodeString) o; return value.equals(otherString.value); } return false; } @Override public boolean isEqual(IValue value){ return equals(value); } @Override public IString reverse() { StringBuilder b = new StringBuilder(value); return newString(b.reverse().toString(), true); } @Override public int length() { return value.codePointCount(0, value.length()); } private int codePointAt(java.lang.String str, int i) { return str.codePointAt(str.offsetByCodePoints(0,i)); } @Override public IString substring(int start, int end) { return newString(value.substring(value.offsetByCodePoints(0, start),value.offsetByCodePoints(0, end))); } @Override public IString substring(int start) { return newString(value.substring(value.offsetByCodePoints(0, start))); } @Override public int charAt(int index) { return codePointAt(value, index); } private int nextCP(CharBuffer cbuf){ int cp = Character.codePointAt(cbuf, 0); if(cbuf.position() < cbuf.capacity()){ cbuf.position(cbuf.position() + Character.charCount(cp)); } return cp; } private void skipCP(CharBuffer cbuf){ if(cbuf.hasRemaining()){ int cp = Character.codePointAt(cbuf, 0); cbuf.position(cbuf.position() + Character.charCount(cp)); } } @Override public IString replace(int first, int second, int end, IString repl) { StringBuilder buffer = new StringBuilder(); int valueLen = value.codePointCount(0, value.length()); CharBuffer valueBuf; int replLen = repl.length(); CharBuffer replBuf = CharBuffer.wrap(repl.getValue()); int increment = Math.abs(second - first); if(first <= end){ valueBuf = CharBuffer.wrap(value); int valueIndex = 0; // Before begin (from left to right) while(valueIndex < first){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex++; } int replIndex = 0; boolean wrapped = false; // Between begin and end while(valueIndex < end){ buffer.appendCodePoint(nextCP(replBuf)); replIndex++; if(replIndex == replLen){ replBuf.position(0); replIndex = 0; wrapped = true; } skipCP(valueBuf); valueIndex++; //skip the replaced element for(int j = 1; j < increment && valueIndex < end; j++){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex++; } } if(!wrapped){ while(replIndex < replLen){ buffer.appendCodePoint(nextCP(replBuf)); replIndex++; } } // After end while( valueIndex < valueLen){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex++; } } else { // Before begin (from right to left) // Place reversed value of fValue in valueBuffer for better sequential code point access // Also add code points to buffer in reverse order and reverse again at the end valueBuf = CharBuffer.wrap(new StringBuilder(value).reverse().toString()); int valueIndex = valueLen - 1; while(valueIndex > first){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex--; } // Between begin (right) and end (left) int replIndex = 0; boolean wrapped = false; while(valueIndex > end){ buffer.appendCodePoint(nextCP(replBuf)); replIndex++; if(replIndex == repl.length()){ replBuf.position(0); replIndex = 0; wrapped = true; } skipCP(valueBuf); valueIndex--; //skip the replaced element for(int j = 1; j < increment && valueIndex > end; j++){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex--; } } if(!wrapped){ while(replIndex < replLen){ buffer.appendCodePoint(nextCP(replBuf)); replIndex++; } } // Left of end while(valueIndex >= 0){ buffer.appendCodePoint(nextCP(valueBuf)); valueIndex--; } buffer.reverse(); } String res = buffer.toString(); return StringValue.newString(res); } } private static class SimpleUnicodeString extends FullUnicodeString { public SimpleUnicodeString(String value) { super(value); } @Override public boolean equals(Object o) { if(o == null) return false; if(this == o) return true; if(o.getClass() == getClass()){ SimpleUnicodeString otherString = (SimpleUnicodeString) o; return value.equals(otherString.value); } return false; } // Common operations which do not need to be slow @Override public int length() { return value.length(); } @Override public int charAt(int index) { return value.charAt(index); } @Override public IString substring(int start) { return newString(value.substring(start), false); } @Override public IString substring(int start, int end) { return newString(value.substring(start, end), false); } @Override public IString reverse() { return newString(new StringBuilder(value).reverse().toString(), false); } @Override public IString concat(IString other) { StringBuilder buffer = new StringBuilder(); buffer.append(value); buffer.append(other.getValue()); return StringValue.newString(buffer.toString(), other.getClass() != getClass()); } } }