Skip to main content
summaryrefslogtreecommitdiffstats
blob: 0f6cfd917d4b5cfa4de975b94d7e605b5e88f084 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*******************************************************************************
 * 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.core.internal.utility.jdt;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jpt.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(" ")) {
						// 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?
						}
						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, "");
	}

	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;
	}

}

Back to the top