processing.mode.java.pdex.TextTransform Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-mode Show documentation
Show all versions of java-mode Show documentation
Processing is a programming language, development environment, and online community.
This Java Mode package contains the Java mode for Processing IDE.
package processing.mode.java.pdex;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
import processing.core.PApplet;
public class TextTransform {
private static final Comparator INPUT_OFFSET_COMP =
(o1, o2) -> Integer.compare(o1.fromOffset, o2.fromOffset);
private static final Comparator OUTPUT_OFFSET_COMP =
(o1, o2) -> Integer.compare(o1.toOffset, o2.toOffset);
private CharSequence input;
private List edits = new ArrayList<>();
private List inMap = new ArrayList<>();
private List outMap = new ArrayList<>();
private boolean built;
private int builtForLength;
TextTransform(CharSequence input) {
this.input = input;
}
public void add(Edit edit) {
edits.add(edit);
built = false;
}
public void addAll(Collection edits) {
this.edits.addAll(edits);
built = false;
}
public String apply() {
final int inLength = input.length();
final StringBuilder output = new StringBuilder(inLength);
buildIfNeeded(inLength);
outMap.stream()
// Filter out Delete edits
.filter(outEdit -> outEdit.toLength > 0)
.forEach(outEdit -> {
if (outEdit.outputText != null) {
// Insert or Replace edit
output.append(outEdit.outputText);
} else {
// Move edit
output.append(input, outEdit.fromOffset, outEdit.fromOffset + outEdit.fromLength);
}
});
return output.toString();
}
public OffsetMapper getMapper() {
int inLength = input.length();
buildIfNeeded(inLength);
return new SimpleOffsetMapper(inMap, outMap);
}
private void buildIfNeeded(int inLength) {
if (built && inLength == builtForLength) return;
// Make copies of Edits to preserve original edits
List inEdits = edits.stream().map(Edit::new).collect(Collectors.toList());
List outEdits = new ArrayList<>(inEdits);
// Edits sorted by input offsets
Collections.sort(inEdits, INPUT_OFFSET_COMP);
// Edits sorted by output offsets
Collections.sort(outEdits, OUTPUT_OFFSET_COMP);
// TODO: add some validation
// Input
ListIterator inIt = inEdits.listIterator();
Edit inEdit = inIt.hasNext() ? inIt.next() : null;
int inEditOff = inEdit == null ? inLength : inEdit.fromOffset;
// Output
ListIterator outIt = outEdits.listIterator();
Edit outEdit = outIt.hasNext() ? outIt.next() : null;
int outEditOff = outEdit == null ? inLength : outEdit.toOffset;
int inOffset = 0;
int outOffset = 0;
inMap.clear();
outMap.clear();
// Walk through the input, apply changes, create mapping
while (inOffset < inLength || inEdit != null || outEdit != null) {
{ // Create mapping for unmodified portion of the input
int nextEditOffset = Math.min(inEditOff, outEditOff);
{ // Insert move block to have mapping for unmodified portions too
int length = nextEditOffset - inOffset;
if (length > 0) {
Edit ch = Edit.move(inOffset, length, outOffset);
inMap.add(ch);
outMap.add(ch);
}
}
// Move offsets accordingly
outOffset += nextEditOffset - inOffset;
inOffset = nextEditOffset;
}
// Process encountered input edits
while (inEdit != null && inOffset >= inEditOff) {
inOffset += inEdit.fromLength;
if (inEdit.fromLength > 0) inMap.add(inEdit);
inEdit = inIt.hasNext() ? inIt.next() : null;
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
}
// Process encountered output edits
while (outEdit != null && inOffset >= outEditOff) {
outEdit.toOffset = outOffset;
if (outEdit.toLength > 0) outMap.add(outEdit);
outOffset += outEdit.toLength;
outEdit = outIt.hasNext() ? outIt.next() : null;
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
}
}
built = true;
builtForLength = inLength;
}
@Override
public String toString() {
return "SourceMapping{" +
"edits=" + edits +
'}';
}
protected static class Edit {
static Edit insert(int offset, String text) {
return new Edit(offset, 0, offset, text.length(), text);
}
static Edit replace(int offset, int length, String text) {
return new Edit(offset, length, offset, text.length(), text);
}
static Edit move(int fromOffset, int length, int toOffset) {
Edit result = new Edit(fromOffset, length, toOffset, length, null);
result.toOffset = toOffset;
return result;
}
static Edit delete(int position, int length) {
return new Edit(position, length, position, 0, null);
}
Edit(Edit edit) {
this.fromOffset = edit.fromOffset;
this.fromLength = edit.fromLength;
this.toOffset = edit.toOffset;
this.toLength = edit.toLength;
this.outputText = edit.outputText;
}
Edit(int fromOffset, int fromLength, int toOffset, int toLength, String text) {
this.fromOffset = fromOffset;
this.fromLength = fromLength;
this.toOffset = toOffset;
this.toLength = toLength;
this.outputText = text;
}
private final int fromOffset;
private final int fromLength;
private int toOffset;
private final int toLength;
private final String outputText;
@Override
public String toString() {
return "Edit{" +
"from=" + fromOffset + ":" + fromLength +
", to=" + toOffset + ":" + toLength +
((outputText != null) ? (", text='" + outputText + '\'') : "") +
'}';
}
}
protected interface OffsetMapper {
int getInputOffset(int outputOffset);
int getOutputOffset(int inputOffset);
OffsetMapper thenMapping(OffsetMapper mapper);
OffsetMapper EMPTY_MAPPER = CompositeOffsetMapper.of();
}
private static class SimpleOffsetMapper implements OffsetMapper {
private List inMap = new ArrayList<>();
private List outMap = new ArrayList<>();
private int outputOffsetOfInputStart;
private int inputOffsetOfOutputStart;
private SimpleOffsetMapper(List inMap, List outMap) {
this.inMap.addAll(inMap);
this.outMap.addAll(outMap);
Edit inStart = null;
for (Edit in : this.inMap) {
inStart = in;
if (in.fromLength > 0) break;
}
outputOffsetOfInputStart = inStart == null ? 0 : inStart.toOffset;
Edit outStart = null;
for (Edit out : this.inMap) {
outStart = out;
if (out.toLength > 0) break;
}
inputOffsetOfOutputStart = outStart == null ? 0 : outStart.fromOffset;
}
@Override
public int getInputOffset(int outputOffset) {
if (outputOffset < outputOffsetOfInputStart) return -1;
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
if (i < 0) {
i = -(i + 1);
i -= 1;
}
i = PApplet.constrain(i, 0, outMap.size()-1);
Edit edit = outMap.get(i);
int diff = outputOffset - edit.toOffset;
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
}
@Override
public int getOutputOffset(int inputOffset) {
if (inputOffset < inputOffsetOfOutputStart) return -1;
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
if (i < 0) {
i = -(i + 1);
i -= 1;
}
i = PApplet.constrain(i, 0, inMap.size()-1);
Edit edit = inMap.get(i);
int diff = inputOffset - edit.fromOffset;
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
}
@Override
public OffsetMapper thenMapping(OffsetMapper mapper) {
return CompositeOffsetMapper.of(this, mapper);
}
}
private static class CompositeOffsetMapper implements OffsetMapper {
private List mappers = new ArrayList<>();
public static CompositeOffsetMapper of(OffsetMapper... inMappers) {
CompositeOffsetMapper composite = new CompositeOffsetMapper();
// Add mappers one by one, unwrap if Composite
for (OffsetMapper mapper : inMappers) {
if (mapper instanceof CompositeOffsetMapper) {
composite.mappers.addAll(((CompositeOffsetMapper) mapper).mappers);
} else {
composite.mappers.add(mapper);
}
}
return composite;
}
@Override
public int getInputOffset(int outputOffset) {
for (int i = mappers.size() - 1; i >= 0; i--) {
outputOffset = mappers.get(i).getInputOffset(outputOffset);
}
return outputOffset;
}
@Override
public int getOutputOffset(int inputOffset) {
for (OffsetMapper mapper : mappers) {
inputOffset = mapper.getOutputOffset(inputOffset);
}
return inputOffset;
}
@Override
public OffsetMapper thenMapping(OffsetMapper mapper) {
return CompositeOffsetMapper.of(this, mapper);
}
}
}