blob: 97264a9d937f523b24dfb2ea32bb92c3b6fd0887 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2008 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.common.core.internal.utility.jdt;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jpt.common.core.utility.jdt.AnnotationEditFormatter;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
/**
* This implementation will clean up some of the nasty Eclipse annotation
* formatting (or lack thereof); e.g. arrays of annotations.
*/
public final class DefaultAnnotationEditFormatter
implements AnnotationEditFormatter
{
private static final DefaultAnnotationEditFormatter INSTANCE = new DefaultAnnotationEditFormatter();
/**
* Return the singleton.
*/
public static DefaultAnnotationEditFormatter instance() {
return INSTANCE;
}
/**
* Ensure single instance.
*/
private DefaultAnnotationEditFormatter() {
super();
}
/**
* TODO
*/
public void format(IDocument doc, TextEdit editTree) throws MalformedTreeException, BadLocationException {
TextEdit[] edits = editTree.getChildren();
int len = edits.length;
if (len == 0) {
return;
}
MultiTextEdit extraEdits = new MultiTextEdit();
for (int i = 0; i < len; i++) {
TextEdit edit1 = edits[i];
if ( ! (edit1 instanceof InsertEdit)) {
continue; // if the edit is not an insert, skip to the next edit
}
InsertEdit insert1 = (InsertEdit) edit1;
int j = i + 1;
if (j < len) {
TextEdit edit2 = edits[j];
if (edit2 instanceof InsertEdit) {
InsertEdit insert2 = (InsertEdit) edit2;
String text1 = insert1.getText();
String text2 = insert2.getText();
int offset1 = insert1.getOffset();
int offset2 = insert2.getOffset();
if (this.stringIsAnnotation(text1) && text2.equals(" ")) { //$NON-NLS-1$
// an annotation was inserted before something on the same line;
// replace the trailing space with a newline and appropriate indent
extraEdits.addChild(new ReplaceEdit(offset2, 1, this.buildCR(doc, offset2)));
i++; // jump the index past 'edit2'
continue; // go to the next edit
}
int comma1Length = this.commaLength(text1);
if ((comma1Length != 0) && this.stringIsAnnotation(text2)) {
// an annotation was inserted in an array initializer on the
// same line as the previous array element;
// replace the preceding space with a newline and appropriate indent
extraEdits.addChild(new ReplaceEdit(offset1 + comma1Length, text1.length() - comma1Length, this.buildCR(doc, offset1)));
i++; // jump the index past 'edit2'
continue; // go to the next edit
}
}
}
this.formatArrayInitializer(doc, insert1, extraEdits);
}
extraEdits.apply(doc, TextEdit.NONE);
}
/**
* If the insert edit is inserting an annotation containing an array of annotations as
* its value then format them nicely.
*/
private void formatArrayInitializer(IDocument doc, InsertEdit insertEdit, MultiTextEdit extraEdits) throws BadLocationException {
String s = insertEdit.getText();
if ( ! this.stringIsAnnotation(s)) {
return;
}
int len = s.length();
int pos = 1; // skip '@'
while (pos < len) {
char c = s.charAt(pos);
pos++; // bump to just past first '('
if (c == '(') {
break;
}
}
if (pos == len) {
return; // reached end of string
}
while (pos < len) {
char c = s.charAt(pos);
pos++; // bump to just past first '{'
if (c == '{') {
break;
}
if (c != ' ') {
return;
}
}
if (pos == len) {
return; // reached end of string
}
// now look for '@' not inside parentheses and put in
// line delimeter and indent string before each
int offset = insertEdit.getOffset();
String indent = null;
int parenDepth = 0;
while (pos < len) {
switch (s.charAt(pos)) {
case '(' :
parenDepth++;
break;
case ')' :
parenDepth--;
break;
case '@' :
if (parenDepth == 0) {
if (indent == null) {
indent = this.buildCR(doc, offset, "\t"); // TODO use tab preference? //$NON-NLS-1$
}
extraEdits.addChild(new InsertEdit(offset + pos, indent));
}
break;
case '}' :
if (parenDepth == 0) {
extraEdits.addChild(new InsertEdit(offset + pos, this.buildCR(doc, offset)));
}
break;
}
pos++;
}
}
/**
* Build a string containing a line delimeter and indenting characters
* matching the indent level of the line containing the character offset
* (i.e. the new line's indent matches the current line).
*/
private String buildCR(IDocument doc, int offset) throws BadLocationException {
return this.buildCR(doc, offset, ""); //$NON-NLS-1$
}
private String buildCR(IDocument doc, int offset, String suffix) throws BadLocationException {
int line = doc.getLineOfOffset(offset);
StringBuilder sb = new StringBuilder();
sb.append(doc.getLineDelimiter(line)); // use same CR as current line
int o = doc.getLineOffset(line); // match the whitespace of the current line
char c = doc.getChar(o++);
while ((c == ' ') || (c == '\t')) {
sb.append(c);
c = doc.getChar(o++);
}
sb.append(suffix);
return sb.toString();
}
/**
* Return whether the specified string is an annotation.
*/
private boolean stringIsAnnotation(String string) {
return (string.length() > 1) && string.charAt(0) == '@';
}
/**
* If the specified string is a single comma, possibly surrounded by
* spaces, return the length of the substring containing the
* initial spaces and the comma.
*/
private int commaLength(String string) {
boolean comma = false;
int len = string.length();
int result = 0;
for (int i = 0; i < len; i++) {
switch (string.charAt(i)) {
case ' ' :
if ( ! comma) {
result++; // space preceding comma
}
break;
case ',' :
if (comma) {
return 0; // second comma!
}
comma = true;
result++;
break;
default:
return 0; // non-comma, non-space char
}
}
return result;
}
}