/*******************************************************************************
* Copyright 2005-2007, CHISEL Group, University of Victoria, Victoria, BC, Canada
* and IBM Corporation. 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:
* The Chisel Group, University of Victoria
*******************************************************************************/
package net.sourceforge.tagsea.parsed.javabang;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.tagsea.core.ITag;
import net.sourceforge.tagsea.parsed.parser.DefaultMutableParsedWaypointDescripor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
/**
* Creates a mutable waypoint descriptor for bang waypoints.
* @author Del Myers
*
*/
public class MutableBangWaypointDescriptor extends DefaultMutableParsedWaypointDescripor {
private int offset;
private HashMap<String, Set<IRegion>> tagRegionMap;
private Document document;
private boolean dirty;
private String message;
private int line;
/**
* Uses the given text to generate a bang waypoint descriptor. The regions are the regions in
* which the tags exist, starting from the beginning of the text. offset is the offset in the original
* document.
* @param text
* @param tags
*/
public MutableBangWaypointDescriptor(int offset, String text, List<IRegion> tags, int line) {
this.offset = offset;
this.line = line;
this.document = new Document(text);
//read the regions and generate a tag region map.
this.tagRegionMap = new HashMap<String, Set<IRegion>>();
for (IRegion tagRegion : tags) {
String tagWord = text.substring(tagRegion.getOffset(), tagRegion.getOffset()+tagRegion.getLength());
String realTag = tagWord.substring(1);
Set<IRegion> regions = tagRegionMap.get(realTag);
if (regions == null) {
regions = new HashSet<IRegion>();
tagRegionMap.put(realTag, regions);
}
regions.add(tagRegion);
}
this.dirty = true;
}
/**
* Returns the most up-to-date regions, relative to the beginning of the waypoint, where tags exist.
* No order is guaranteed.
* @return
*/
public Collection<IRegion> getAllTagLocations() {
HashSet<IRegion> regionSet = new HashSet<IRegion>();
for (String tag : tagRegionMap.keySet()) {
regionSet.addAll(tagRegionMap.get(tag));
}
return regionSet;
}
public String getText() {
return document.get();
}
public void removeTag(String tag) throws UnsupportedOperationException {
Set<IRegion> tagRegions = tagRegionMap.get(tag);
if (tagRegions != null) {
MultiTextEdit edit = new MultiTextEdit();
for(IRegion region : tagRegions) {
edit.addChild(new ReplaceEdit(region.getOffset(), region.getLength(), tag));
}
try {
edit.apply(this.document);
} catch (MalformedTreeException e) {
} catch (BadLocationException e) {
}
tagRegions.remove(tag);
dirty = true;
}
}
public void replaceTag(String oldTag, String newTag)
throws UnsupportedOperationException {
Set<IRegion> oldRegions = tagRegionMap.get(oldTag);
if (oldRegions == null) return;
List<IRegion> regionList = new ArrayList<IRegion>(oldRegions);
Collections.sort(regionList, new Comparator<IRegion>(){
public int compare(IRegion o1, IRegion o2) {
return o1.getOffset() - o2.getOffset();
}
});
int sizeDiff = oldTag.length() - newTag.length();
int adjust = 0;
Set<IRegion> newRegions = new HashSet<IRegion>();
for (IRegion region : regionList) {
try {
int offset = region.getOffset() - adjust;
this.document.replace(offset, region.getLength(), "`" + newTag);
int length = newTag.length() + 1;
newRegions.add(new Region(offset, length));
adjust += sizeDiff;
} catch (BadLocationException e) {
}
}
Set<IRegion> newTagSet = tagRegionMap.get(newTag);
if (newTagSet == null) {
newTagSet = new HashSet<IRegion>();
tagRegionMap.put(newTag, newTagSet);
}
newTagSet.addAll(newRegions);
this.dirty = true;
}
public String getAuthor() {
return "";
}
public int getCharEnd() {
return offset+document.getLength();
}
public int getCharStart() {
return offset;
}
@Override
public int getLine() {
return line;
}
public Date getDate() {
return null;
}
public String getDetail() {
return "";
}
public String getMessage() {
if (dirty) {
this.message = recalculateMessage();
}
return this.message;
}
private boolean isMultiLine() {
String documentText = document.get();
return (documentText.charAt(0) == '/' && documentText.charAt(1) == '*');
}
/**
* @return
*/
private String recalculateMessage() {
int start = 2;
int end = this.document.getLength();
if (isMultiLine()) {
//read back from the end.
end -= 3;
while (end > start) {
try {
char c = this.document.getChar(end);
if (c != '*' && c != '/' && !(Character.isWhitespace(c))) {
break;
}
end--;
} catch (BadLocationException e) {
break;
}
}
end++;
}
char c;
String word = "";
StringBuilder message = new StringBuilder();
if (start < end) {
try {
String text = this.document.get(start, end - start);
String[] lines = text.split("(\\r+)|(\\n+)");
for (String line : lines) {
line = line.replaceAll("\\s+", " ");
line = line.replaceAll("^(\\s*)\\*+(\\s*)", " ");
line = line.trim();
if ("".equals(line)) {
continue;
}
for (int i = 0; i < line.length(); i++) {
c = line.charAt(i);
if (Character.isWhitespace(c)) {
if (!"".equals(word)) {
message.append(' ');
if (BangCommentParser.isTag(word)) {
word = word.substring(1);
}
message.append(word);
word = "";
}
} else {
word += c;
}
}
if (!"".equals(word)) {
if (BangCommentParser.isTag(word)) {
word = word.substring(1);
}
message.append(" " + word);
word = "";
}
message.append(' ');
}
} catch (BadLocationException e) {
return "";
}
}
return message.toString();
}
public String[] getTags() {
return tagRegionMap.keySet().toArray(new String[tagRegionMap.size()]);
}
@Override
public void addTag(String tag) throws UnsupportedOperationException {
//never add the default tag.
if (ITag.DEFAULT.equals(tag)) return;
//don't do anything if it isn't a valid tag or it already exists
if (tag == null || !BangCommentParser.isTag('`' + tag) || tagRegionMap.keySet().contains(tag)) return;
int end = document.getLength()-1;
if (isMultiLine()) {
try {
while (Character.isWhitespace(document.getChar(end)) && end > 0) {
end--;
}
} catch (BadLocationException e) {
JavaBangPlugin.getDefault().log(e);
}
end-=2;
}
try {
while (Character.isWhitespace(document.getChar(end)) && end > 0) {
end--;
}
} catch (BadLocationException e) {
JavaBangPlugin.getDefault().log(e);
}
end++;
try {
document.replace(end, 0, " `" + tag);
} catch (BadLocationException e) {
JavaBangPlugin.getDefault().log(e);
}
Set<IRegion> tagRegionSet = new HashSet<IRegion>();
tagRegionSet.add(new Region(end, tag.length()+2));
tagRegionMap.put(tag, tagRegionSet);
}
}