001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.editor.hints;
042:
043: import java.beans.PropertyChangeEvent;
044: import java.beans.PropertyChangeListener;
045: import java.beans.PropertyChangeSupport;
046: import java.io.IOException;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.List;
050: import java.util.logging.Level;
051: import java.util.logging.Logger;
052: import javax.swing.event.ChangeListener;
053: import javax.swing.text.BadLocationException;
054: import javax.swing.text.Document;
055: import javax.swing.text.Position;
056: import javax.swing.text.StyledDocument;
057: import org.netbeans.editor.BaseDocument;
058: import org.netbeans.editor.Utilities;
059: import org.netbeans.spi.editor.hints.ErrorDescription;
060: import org.netbeans.spi.editor.hints.Fix;
061: import org.netbeans.spi.editor.hints.LazyFixList;
062: import org.openide.ErrorManager;
063: import org.openide.cookies.EditorCookie;
064: import org.openide.filesystems.FileObject;
065: import org.openide.loaders.DataObject;
066: import org.openide.text.CloneableEditorSupport;
067: import org.openide.text.EditorSupport;
068: import org.openide.text.NbDocument;
069: import org.openide.text.PositionBounds;
070: import org.openide.text.PositionRef;
071:
072: /**
073: *
074: * @author Jan Lahoda
075: */
076: public final class HintsControllerImpl {
077:
078: private HintsControllerImpl() {
079: }
080:
081: public static void setErrors(Document doc, String layer,
082: Collection<? extends ErrorDescription> errors) {
083: DataObject od = (DataObject) doc
084: .getProperty(Document.StreamDescriptionProperty);
085:
086: if (od == null)
087: return;
088:
089: try {
090: setErrorsImpl(od.getPrimaryFile(), layer, errors);
091: } catch (IOException e) {
092: ErrorManager.getDefault().notify(e);
093: }
094: }
095:
096: public static void setErrors(FileObject file, String layer,
097: Collection<? extends ErrorDescription> errors) {
098: try {
099: setErrorsImpl(file, layer, errors);
100: } catch (IOException e) {
101: ErrorManager.getDefault().notify(e);
102: }
103: }
104:
105: private static void setErrorsImpl(FileObject file, String layer,
106: Collection<? extends ErrorDescription> errors)
107: throws IOException {
108: AnnotationHolder holder = AnnotationHolder.getInstance(file);
109:
110: if (holder != null) {
111: holder.setErrorDescriptions(layer, errors);
112: }
113: }
114:
115: private static void computeLineSpan(Document doc, int[] offsets)
116: throws BadLocationException {
117: String text = doc.getText(offsets[0], offsets[1] - offsets[0]);
118: int column = 0;
119: int length = text.length();
120:
121: while (column < text.length()
122: && Character.isWhitespace(text.charAt(column))) {
123: column++;
124: }
125:
126: while (length > 0
127: && Character.isWhitespace(text.charAt(length - 1)))
128: length--;
129:
130: offsets[1] = offsets[0] + length;
131: offsets[0] += column;
132:
133: if (offsets[1] < offsets[0]) {
134: //may happen on lines without non-whitespace characters
135: offsets[0] = offsets[1];
136: }
137: }
138:
139: static int[] computeLineSpan(Document doc, int lineNumber)
140: throws BadLocationException {
141: int lineStartOffset = NbDocument.findLineOffset(
142: (StyledDocument) doc, lineNumber - 1);
143: int lineEndOffset;
144:
145: if (doc instanceof BaseDocument) {
146: lineEndOffset = Utilities.getRowEnd((BaseDocument) doc,
147: lineStartOffset);
148: } else {
149: //XXX: performance:
150: String lineText = doc.getText(lineStartOffset, doc
151: .getLength()
152: - lineStartOffset);
153:
154: lineText = lineText.indexOf('\n') != (-1) ? lineText
155: .substring(0, lineText.indexOf('\n')) : lineText;
156: lineEndOffset = lineStartOffset + lineText.length();
157: }
158:
159: int[] span = new int[] { lineStartOffset, lineEndOffset };
160:
161: computeLineSpan(doc, span);
162:
163: return span;
164: }
165:
166: public static PositionBounds fullLine(Document doc, int lineNumber) {
167: DataObject file = (DataObject) doc
168: .getProperty(Document.StreamDescriptionProperty);
169:
170: if (file == null)
171: return null;
172:
173: try {
174: int[] span = computeLineSpan(doc, lineNumber);
175:
176: return linePart(file.getPrimaryFile(), span[0], span[1]);
177: } catch (BadLocationException e) {
178: ErrorManager.getDefault().notify(e);
179: return null;
180: }
181: }
182:
183: public static PositionBounds linePart(Document doc,
184: final Position start, final Position end) {
185: DataObject od = (DataObject) doc
186: .getProperty(Document.StreamDescriptionProperty);
187:
188: if (od == null)
189: return null;
190:
191: EditorCookie ec = od.getCookie(EditorCookie.class);
192:
193: if (ec instanceof CloneableEditorSupport) {
194: final CloneableEditorSupport ces = (CloneableEditorSupport) ec;
195:
196: final PositionRef[] refs = new PositionRef[2];
197:
198: doc.render(new Runnable() {
199: public void run() {
200: checkOffsetsAndLog(start.getOffset(), end
201: .getOffset());
202:
203: refs[0] = ces.createPositionRef(start.getOffset(),
204: Position.Bias.Forward);
205: refs[1] = ces.createPositionRef(end.getOffset(),
206: Position.Bias.Backward);
207: }
208: });
209:
210: return new PositionBounds(refs[0], refs[1]);
211: }
212:
213: if (ec instanceof EditorSupport) {
214: final EditorSupport es = (EditorSupport) ec;
215:
216: final PositionRef[] refs = new PositionRef[2];
217:
218: doc.render(new Runnable() {
219: public void run() {
220: checkOffsetsAndLog(start.getOffset(), end
221: .getOffset());
222:
223: refs[0] = es.createPositionRef(start.getOffset(),
224: Position.Bias.Forward);
225: refs[1] = es.createPositionRef(end.getOffset(),
226: Position.Bias.Backward);
227: }
228: });
229:
230: return new PositionBounds(refs[0], refs[1]);
231: }
232:
233: return null;
234: }
235:
236: public static PositionBounds linePart(FileObject file, int start,
237: int end) {
238: try {
239: DataObject od = DataObject.find(file);
240:
241: if (od == null)
242: return null;
243:
244: EditorCookie ec = od.getCookie(EditorCookie.class);
245:
246: if (!(ec instanceof CloneableEditorSupport)) {
247: return null;
248: }
249:
250: final CloneableEditorSupport ces = (CloneableEditorSupport) ec;
251:
252: checkOffsetsAndLog(start, end);
253:
254: return new PositionBounds(ces.createPositionRef(start,
255: Position.Bias.Forward), ces.createPositionRef(end,
256: Position.Bias.Backward));
257: } catch (IOException e) {
258: ErrorManager.getDefault().notify(
259: ErrorManager.INFORMATIONAL, e);
260: return null;
261: }
262: }
263:
264: private static void checkOffsetsAndLog(int start, int end) {
265: if (start <= end) {
266: return;
267: }
268:
269: Logger.getLogger(HintsControllerImpl.class.getName()).log(
270: Level.INFO,
271: "Incorrect span, please attach your messages.log to issue #112566. start="
272: + start + ", end=" + end, new Exception());
273: }
274:
275: private static List<ChangeListener> listeners = new ArrayList<ChangeListener>();
276:
277: public static synchronized void addChangeListener(ChangeListener l) {
278: listeners.add(l);
279: }
280:
281: public static synchronized void removeChangeListener(
282: ChangeListener l) {
283: listeners.remove(l);
284: }
285:
286: public static class CompoundLazyFixList implements LazyFixList,
287: PropertyChangeListener {
288:
289: private List<LazyFixList> delegates;
290:
291: private List<Fix> fixesCache;
292: private Boolean computedCache;
293: private Boolean probablyContainsFixesCache;
294:
295: private PropertyChangeSupport pcs;
296:
297: public CompoundLazyFixList(List<LazyFixList> delegates) {
298: this .delegates = delegates;
299: this .pcs = new PropertyChangeSupport(this );
300:
301: for (LazyFixList l : delegates) {
302: l.addPropertyChangeListener(this );
303: }
304: }
305:
306: public void addPropertyChangeListener(PropertyChangeListener l) {
307: pcs.addPropertyChangeListener(l);
308: }
309:
310: public void removePropertyChangeListener(
311: PropertyChangeListener l) {
312: pcs.removePropertyChangeListener(l);
313: }
314:
315: public synchronized boolean probablyContainsFixes() {
316: if (probablyContainsFixesCache == null) {
317: boolean result = false;
318:
319: for (LazyFixList l : delegates) {
320: result |= l.probablyContainsFixes();
321: }
322:
323: probablyContainsFixesCache = Boolean.valueOf(result);
324: }
325:
326: return probablyContainsFixesCache;
327: }
328:
329: public synchronized List<Fix> getFixes() {
330: if (fixesCache == null) {
331: fixesCache = new ArrayList<Fix>();
332:
333: for (LazyFixList l : delegates) {
334: fixesCache.addAll(l.getFixes());
335: }
336: }
337:
338: return fixesCache;
339: }
340:
341: public synchronized boolean isComputed() {
342: if (computedCache == null) {
343: boolean result = true;
344:
345: for (LazyFixList l : delegates) {
346: result &= l.isComputed();
347: }
348:
349: computedCache = Boolean.valueOf(result);
350: }
351:
352: return computedCache;
353: }
354:
355: public void propertyChange(PropertyChangeEvent evt) {
356: if (PROP_FIXES.equals(evt.getPropertyName())) {
357: synchronized (this) {
358: fixesCache = null;
359: }
360: pcs.firePropertyChange(PROP_FIXES, null, null);
361: return;
362: }
363:
364: if (PROP_COMPUTED.equals(evt.getPropertyName())) {
365: synchronized (this) {
366: computedCache = null;
367: }
368: pcs.firePropertyChange(PROP_COMPUTED, null, null);
369: }
370: }
371:
372: }
373:
374: }
|