blob: 8a2ed8c73cad1c7d3f9071ebd5b4fd0a32cfd71e [file] [log] [blame]
/*
* 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 org.netbeans.modules.refactoring.java.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
import org.netbeans.lib.editor.util.CharSequenceUtilities;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.lib.editor.util.swing.MutablePositionRegion;
import org.netbeans.lib.editor.util.swing.PositionRegion;
import org.openide.util.Exceptions;
/**Copied from editor/codetemplates and adjusted for the needs of the instant rename.
*
* Maintain the same text in the selected regions of the text document.
*
* @author Miloslav Metelka
*/
public final class SyncDocumentRegion {
private Document doc;
private String oldVal;
private List<? extends MutablePositionRegion> regions;
private List<? extends MutablePositionRegion> sortedRegions;
/**
* Construct synchronized document regions.
*
* @param doc document on which to operate.
* @param regions regions that should be kept synchronized.
* The first region is the master. All the regions need to have
* the initial position to have the backward bias.
*/
public SyncDocumentRegion(Document doc, List<? extends MutablePositionRegion> regions) {
this.doc = doc;
this.regions = regions;
// Check bounds correctness and whether they are sorted
boolean regionsSortPerformed = PositionRegion.isRegionsSorted(regions);
if (regionsSortPerformed) {
sortedRegions = regions;
} else {
sortedRegions = new ArrayList<>(regions);
Collections.sort(sortedRegions, PositionRegion.getComparator());
}
this.oldVal = getFirstRegionText();
}
public void updateRegions(List<MutablePositionRegion> regions) {
boolean sorted = PositionRegion.isRegionsSorted(regions);
List<MutablePositionRegion> sortedRegions;
List<MutablePositionRegion> toRestore = new LinkedList<>();
List<MutablePositionRegion> toSync = new LinkedList<>();
if(sorted) {
sortedRegions = regions;
} else {
sortedRegions = new ArrayList<>(regions);
Collections.sort(sortedRegions, PositionRegion.getComparator());
}
int j = 0;
for (int i = 0; i < this.regions.size();) {
MutablePositionRegion r1 = this.regions.get(i);
// if old before new, remove
if(j >= sortedRegions.size()) {
toRestore.add(r1);
i++;
} else {
MutablePositionRegion r2 = sortedRegions.get(j);
if(r1.getStartOffset() == r2.getStartOffset()) { // if same pos, skip both
i++;
j++;
} else if(r1.getStartOffset() < r2.getStartOffset()) { // if old before new, remove
toRestore.add(r1);
i++;
} else { // if old after new, skip new
toSync.add(r2);
j++;
}
}
}
while(j < sortedRegions.size()) {
toSync.add(sortedRegions.get(j));
j++;
}
restore(toRestore);
sync(toSync);
this.regions = regions;
this.sortedRegions = sortedRegions;
}
public int getRegionCount() {
return regions.size();
}
public MutablePositionRegion getRegion(int regionIndex) {
return regions.get(regionIndex);
}
public int getFirstRegionStartOffset() {
return getRegion(0).getStartOffset();
}
public int getFirstRegionEndOffset() {
return getRegion(0).getEndOffset();
}
public int getFirstRegionLength() {
return getFirstRegionEndOffset() - getFirstRegionStartOffset();
}
/**
* Get region in a sorted list of the regions.
*
* @param regionIndex of the region.
* @return region in a sorted list of the regions.
*/
public MutablePositionRegion getSortedRegion(int regionIndex) {
return sortedRegions.get(regionIndex);
}
/**
* Propagate text of the first region into all other regions.
*/
public void sync() {
String firstRegionText = getFirstRegionText();
if (firstRegionText != null) {
int regionCount = getRegionCount();
for (int i = 1; i < regionCount; i++) {
MutablePositionRegion region = getRegion(i);
int offset = region.getStartOffset();
int length = region.getEndOffset() - offset;
try {
if (!CharSequenceUtilities.textEquals(firstRegionText, DocumentUtilities.getText(doc, offset, length))) {
if (firstRegionText.length() > 0) {
doc.insertString(offset, firstRegionText, null);
}
doc.remove(offset + firstRegionText.length(), length);
}
} catch (BadLocationException e) {
Exceptions.printStackTrace(e);
}
}
}
}
private void sync(List<MutablePositionRegion> toSync) {
String firstRegionText = getFirstRegionText();
for (MutablePositionRegion region : toSync) {
int offset = region.getStartOffset();
int length = region.getEndOffset() - offset;
try {
final CharSequence old = DocumentUtilities.getText(doc, offset, length);
if (!CharSequenceUtilities.textEquals(firstRegionText, old)) {
int res = -1;
for (int k = 0; k < Math.min(old.length(), firstRegionText.length()); k++) {
if (old.charAt(k) == firstRegionText.charAt(k)) {
res = k;
} else {
break;
}
}
String insert = firstRegionText.substring(res + 1);
CharSequence remove = old.subSequence(res + 1, old.length());
if (insert.length() > 0) {
doc.insertString(offset + res + 1, insert, null);
}
if (remove.length() > 0) {
doc.remove(offset + res + 1 + insert.length(), remove.length());
}
}
} catch (BadLocationException e) {
Exceptions.printStackTrace(e);
}
}
}
private void restore(List<MutablePositionRegion> toRestore) {
for (MutablePositionRegion region : toRestore) {
int offset = region.getStartOffset();
int length = region.getEndOffset() - offset;
try {
if (!CharSequenceUtilities.textEquals(oldVal, DocumentUtilities.getText(doc, offset, length))) {
if (oldVal.length() > 0) {
doc.insertString(offset, oldVal, null);
}
doc.remove(offset + oldVal.length(), length);
}
} catch (BadLocationException e) {
Exceptions.printStackTrace(e);
}
}
}
private String getFirstRegionText() {
return getRegionText(0);
}
private String getRegionText(int regionIndex) {
try {
MutablePositionRegion region = getRegion(regionIndex);
int offset = region.getStartOffset();
int length = region.getEndOffset() - offset;
return doc.getText(offset, length);
} catch (BadLocationException e) {
Exceptions.printStackTrace(e);
return null;
}
}
}