/**
* Copyright (c) 2002-2012 IBM Corporation and others.
* 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:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.ecore.xmi.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.ListIterator;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMIPlugin;
/**
* A String Buffer that never reallocates
*/
public class StringSegment extends BasicEList<StringSegment.Element>
{
private static final long serialVersionUID = 1L;
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
protected final static int LIST_SIZE = 100;
protected static final int ELEMENT_SIZE = 1000;
protected static final int BUFFER_SIZE = 8192;
protected int segmentCapacity;
protected byte[] outputbytes;
protected char[] outputchars;
protected char[] buffer;
protected Element cursor;
protected int cursorIndex = 0;
protected String lineSeparator = LINE_SEPARATOR;
protected String temporaryFileName;
protected Writer temporaryFile;
protected int bufferPosition;
protected String firstString;
public StringSegment()
{
this(LIST_SIZE);
}
public StringSegment(int minimumCapacity)
{
this(minimumCapacity, ELEMENT_SIZE);
}
public StringSegment(int minimumCapacity, int segmentCapacity)
{
super(minimumCapacity);
add(cursor = new Element(this.segmentCapacity = segmentCapacity));
outputchars = new char [BUFFER_SIZE];
}
public StringSegment(String temporaryFileName)
{
this(LIST_SIZE, ELEMENT_SIZE);
setTemporaryFileName(temporaryFileName);
}
public void setTemporaryFileName(String tempFile)
{
temporaryFileName = tempFile;
if (temporaryFileName != null)
{
buffer = new char [BUFFER_SIZE];
}
else
{
buffer = null;
}
}
/**
* @since 2.9
*/
public void setLineSeparator(String lineSeparator)
{
this.lineSeparator = lineSeparator == null || lineSeparator.equals(Resource.OPTION_LINE_DELIMITER_UNSPECIFIED) ? LINE_SEPARATOR : lineSeparator;
}
public String getTemporaryFileName()
{
return temporaryFileName;
}
@Override
protected Object[] newData(int capacity)
{
return new Element [capacity];
}
public void reset()
{
bufferPosition = 0;
cursor = (Element)data[0];
cursorIndex = 0;
for (int i = 0; i < size; i++)
{
((Element)data[i]).size = 0;
}
}
public void add(String newString)
{
// System.err.println("add = ["+newString+"]");
// If there is a temporary file...
//
if (temporaryFile != null)
{
if (firstString == null)
{
firstString = newString;
}
int length = newString.length();
if (length + bufferPosition >= buffer.length)
{
try
{
temporaryFile.write(buffer, 0, bufferPosition);
}
catch (IOException exception)
{
XMIPlugin.INSTANCE.log(exception);
}
bufferPosition = 0;
if (length > buffer.length)
{
buffer = new char [length];
}
}
newString.getChars(0, length, buffer, bufferPosition);
bufferPosition += length;
return;
}
// This is the cheapest and most common case.
//
if (cursor.size < segmentCapacity)
{
cursor.add(newString);
return;
}
Element oldCursor = cursor;
int index = size - 1;
if (cursorIndex < index)
{
cursor = (Element)data[++cursorIndex];
if (cursor.size == 0)
{
cursor.add(newString);
return;
}
}
cursor = new Element(segmentCapacity);
cursor.add(newString);
// The first case is the most common case.
// It is slightly cheaper to call add without an index since an index will be range checked.
//
if (data[index] == oldCursor)
{
super.add(cursor);
cursorIndex = ++index;
}
else
{
// This case can only happen if we are reset to a mark and we've got lots of XMLNS attributes to write.
//
int counter = 0;
while (counter < index)
{
if (data[counter++] == oldCursor)
{
cursorIndex = counter;
super.add(cursorIndex, cursor);
break;
}
}
}
}
public void addLine()
{
add(lineSeparator);
}
public Object mark()
{
Element result = cursor;
if (cursor.size == 0)
{
result.add("");
}
int i = size - 1;
if (cursorIndex < i)
{
cursor = (Element)data[++cursorIndex];
}
else
{
cursorIndex++;
cursor = new Element(segmentCapacity);
super.add(cursor);
}
return result;
}
public void startFileBuffering()
{
if (temporaryFileName != null && temporaryFile == null)
{
try
{
temporaryFile = new OutputStreamWriter(new FileOutputStream(temporaryFileName), "UTF8");
}
catch (IOException exception)
{
// If we can't create one, too bad.
}
}
}
public void resetToMark(Object mark)
{
if (temporaryFile != null)
{
cursor.add("");
try
{
temporaryFile.write(buffer, 0, bufferPosition);
temporaryFile.close();
}
catch (IOException exception)
{
XMIPlugin.INSTANCE.log(exception);
}
temporaryFile = null;
}
cursor = (Element)mark;
for (int i = 0; i < data.length; i++)
{
if (data[i] == cursor)
{
cursorIndex = i;
return;
}
}
}
public int getLength()
{
Element[] elements = (Element[])data;
int length = 0;
for (int i = 0; i < size; ++i)
{
Element element = elements[i];
int segmentSize = element.size;
for (int j = 0; j < segmentSize; ++j)
{
String s = element.data[j];
length += s.length();
}
}
return length;
}
public int getChars(char[] destination, int position)
{
Element[] elements = (Element[])data;
for (int i = 0; i < size; ++i)
{
Element element = elements[i];
int segmentSize = element.size;
for (int j = 0; j < segmentSize; ++j)
{
String string = element.data[j];
int length = string.length();
string.getChars(0, length, destination, position);
position += length;
}
}
return position;
}
public void writeAscii(OutputStream os, int flushThreshold) throws IOException
{
if (outputbytes == null)
{
outputbytes = new byte [BUFFER_SIZE];
}
Element[] elements = (Element[])data;
int position = 0;
int count = 0;
for (int i = 0; i < size; ++i)
{
Element element = elements[i];
int segmentSize = element.size;
for (int j = 0; j < segmentSize; ++j)
{
String string = element.data[j];
int length = string.length();
if (length + position >= outputchars.length)
{
for (int x = 0; x < position; x++)
{
outputbytes[x] = (byte)(outputchars[x] & 0xFF);
}
os.write(outputbytes, 0, position);
position = 0;
if (length > outputchars.length)
{
outputchars = new char [length];
outputbytes = new byte [length];
}
}
string.getChars(0, length, outputchars, position);
position += length;
count += length;
if (count > flushThreshold)
{
os.flush();
count = 0;
}
}
}
for (int x = 0; x < position; x++)
{
outputbytes[x] = (byte)(outputchars[x] & 0xFF);
}
os.write(outputbytes, 0, position);
String temporaryFileName = this.temporaryFileName;
if (temporaryFileName != null)
{
InputStream inputStream = new FileInputStream(temporaryFileName);
for (int length = inputStream.read(outputbytes, 0, outputbytes.length); length > 0; length = inputStream.read(
outputbytes,
0,
outputbytes.length))
{
os.write(outputbytes, 0, length);
count += length;
if (count > flushThreshold)
{
os.flush();
count = 0;
}
}
inputStream.close();
new File(temporaryFileName).delete();
}
}
/**
* @deprecated since 2.2 - Instead use #write(Writer, int)
* @param os
* @param flushThreshold
* @throws IOException
*/
@Deprecated
public void write(OutputStreamWriter os, int flushThreshold) throws IOException
{
write((Writer)os, flushThreshold);
}
public void write(Writer os, int flushThreshold) throws IOException
{
Element[] elements = (Element[])data;
int position = 0;
int count = 0;
for (int i = 0; i < size; ++i)
{
Element element = elements[i];
int segmentSize = element.size;
for (int j = 0; j < segmentSize; ++j)
{
String string = element.data[j];
int length = string.length();
if (length + position >= outputchars.length)
{
os.write(outputchars, 0, position);
position = 0;
if (length > outputchars.length)
{
outputchars = new char [length];
}
}
string.getChars(0, length, outputchars, position);
position += length;
count += length;
if (count > flushThreshold)
{
os.flush();
count = 0;
}
}
}
os.write(outputchars, 0, position);
String temporaryFileName = this.temporaryFileName;
if (temporaryFileName != null)
{
InputStreamReader reader = new InputStreamReader(new FileInputStream(temporaryFileName), "UTF8");
for (int length = reader.read(outputchars, 0, outputchars.length); length > 0; length = reader.read(
outputchars,
0,
outputchars.length))
{
os.write(outputchars, 0, length);
count += length;
if (count > flushThreshold)
{
os.flush();
count = 0;
}
}
reader.close();
new File(temporaryFileName).delete();
}
}
protected static class Element
{
int size;
String[] data;
Element(int capacity)
{
data = new String [capacity];
}
void add(String newString)
{
data[size++] = newString;
}
}
@SuppressWarnings("unchecked")
@Override
public Iterator<Element> iterator()
{
// TODO This is really quite attrocious since there is code that will assume an iterator that returns strings!
return (ListIterator<Element>)(ListIterator<?>)new SegmentIterator();
}
@SuppressWarnings("unchecked")
@Override
public ListIterator<Element> listIterator()
{
// TODO This is really quite attrocious since there is code that will assume an iterator that returns strings!
return (ListIterator<Element>)(Iterator<?>)new SegmentIterator();
}
public Iterator<String> stringIterator()
{
return new SegmentIterator();
}
protected class SegmentIterator implements ListIterator<String>
{
protected int outerIndex = 0;
protected int innerIndex = 0;
SegmentIterator()
{
super();
}
public boolean hasNext()
{
return outerIndex < size - 1 || (outerIndex == size - 1 && innerIndex < ((Element)data[outerIndex]).size);
}
public boolean hasPrevious()
{
return outerIndex > 0 || innerIndex > 0;
}
public String next()
{
Element element = (Element)data[outerIndex];
if (innerIndex < element.size)
{
return element.data[innerIndex++];
}
else
{
innerIndex = 1;
return ((Element)data[++outerIndex]).data[0];
}
}
public String previous()
{
if (innerIndex > 0)
{
return ((Element)data[outerIndex]).data[--innerIndex];
}
else
{
Element element = (Element)data[--outerIndex];
innerIndex = element.size - 1;
return element.data[innerIndex];
}
}
public void add(String newElement)
{
throw new UnsupportedOperationException(SegmentIterator.class.toString());
}
public void remove()
{
throw new UnsupportedOperationException(SegmentIterator.class.toString());
}
public void set(String newElement)
{
throw new UnsupportedOperationException(SegmentIterator.class.toString());
}
public int nextIndex()
{
throw new UnsupportedOperationException(SegmentIterator.class.toString());
}
public int previousIndex()
{
throw new UnsupportedOperationException(SegmentIterator.class.toString());
}
}
}