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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.modules.ruby.hints;
041:
042: import java.util.Collections;
043: import java.util.List;
044: import java.util.Set;
045: import java.util.prefs.Preferences;
046: import javax.swing.JComponent;
047: import javax.swing.text.BadLocationException;
048: import org.jruby.ast.HashNode;
049: import org.jruby.ast.ListNode;
050: import org.jruby.ast.Node;
051: import org.jruby.ast.NodeTypes;
052: import org.netbeans.modules.gsf.api.CompilationInfo;
053: import org.netbeans.modules.gsf.api.OffsetRange;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.modules.ruby.hints.spi.AstRule;
056: import org.netbeans.modules.ruby.hints.spi.Description;
057: import org.netbeans.modules.ruby.hints.spi.EditList;
058: import org.netbeans.modules.ruby.hints.spi.Fix;
059: import org.netbeans.modules.ruby.hints.spi.HintSeverity;
060: import org.netbeans.modules.ruby.hints.spi.PreviewableFix;
061: import org.netbeans.modules.ruby.hints.spi.RuleContext;
062: import org.netbeans.modules.ruby.lexer.LexUtilities;
063: import org.openide.util.Exceptions;
064: import org.openide.util.NbBundle;
065:
066: /**
067: * Convert {a,b,c,d,...} to {a=>b,c=>d,...} as required by Ruby 1.9.
068: *
069: * @author Tor Norbye
070: */
071: public class HashListConvert implements AstRule {
072:
073: public Set<Integer> getKinds() {
074: return Collections.singleton(NodeTypes.HASHNODE);
075: }
076:
077: public void run(RuleContext context, List<Description> result) {
078: Node node = context.node;
079: CompilationInfo info = context.compilationInfo;
080:
081: HashNode hash = (HashNode) node;
082: ListNode listNode = hash.getListNode();
083: if (listNode == null) {
084: return;
085: }
086:
087: if (listNode.size() < 2) {
088: return;
089: }
090:
091: int commaOffset = getCommaOffset(context, listNode, 0);
092: if (commaOffset == -1) {
093: return;
094: }
095:
096: OffsetRange range = new OffsetRange(commaOffset,
097: commaOffset + 1);
098: String displayName = NbBundle.getMessage(HashListConvert.class,
099: "HashListConvertGutter");
100: List<Fix> fixes = Collections.<Fix> singletonList(new HashFix(
101: context, listNode));
102: Description desc = new Description(this , displayName, info
103: .getFileObject(), range, fixes, 140);
104: result.add(desc);
105: }
106:
107: private static int getCommaOffset(RuleContext context,
108: ListNode listNode, int pair) {
109: int prevEnd = listNode.get(2 * pair).getPosition()
110: .getEndOffset();
111: int nextStart = listNode.get(2 * pair + 1).getPosition()
112: .getStartOffset();
113: OffsetRange lexRange = LexUtilities.getLexerOffsets(
114: context.compilationInfo, new OffsetRange(prevEnd,
115: nextStart));
116: if (lexRange == OffsetRange.NONE) {
117: return -1;
118: }
119:
120: try {
121: String s = context.doc.getText(lexRange.getStart(),
122: lexRange.getLength());
123: int index = s.indexOf(',');
124:
125: // TODO - look out for comments here! Use lexical tokens rather than just document text
126: // (and watch out for RHTML discontiguous sections
127:
128: if (index == -1 || s.indexOf("=>") != -1) {
129: return -1;
130: }
131: return lexRange.getStart() + index;
132: } catch (BadLocationException ex) {
133: return -1;
134: }
135: }
136:
137: public String getId() {
138: return "HashListConvert"; // NOI18N
139: }
140:
141: public String getDescription() {
142: return NbBundle.getMessage(HashListConvert.class,
143: "HashListConvertDesc");
144: }
145:
146: public boolean getDefaultEnabled() {
147: return true;
148: }
149:
150: public JComponent getCustomizer(Preferences node) {
151: return null;
152: }
153:
154: public boolean appliesTo(CompilationInfo info) {
155: return true;
156: }
157:
158: public String getDisplayName() {
159: return NbBundle.getMessage(HashListConvert.class,
160: "HashListConvert");
161: }
162:
163: public boolean showInTasklist() {
164: return true;
165: }
166:
167: public HintSeverity getDefaultSeverity() {
168: return HintSeverity.WARNING;
169: }
170:
171: private static class HashFix implements PreviewableFix {
172: private RuleContext context;
173: private ListNode listNode;
174:
175: public HashFix(RuleContext context, ListNode listNode) {
176: this .context = context;
177: this .listNode = listNode;
178: }
179:
180: public String getDescription() {
181: return NbBundle.getMessage(Deprecations.class,
182: "HashListConvertFix");
183: }
184:
185: public void implement() throws Exception {
186: getEditList().apply();
187: }
188:
189: public EditList getEditList() throws Exception {
190: BaseDocument doc = context.doc;
191: EditList list = new EditList(doc);
192: for (int i = 0, n = listNode.size() / 2; i < n; i++) {
193: int offset = getCommaOffset(context, listNode, i);
194: if (offset == -1) {
195: continue;
196: }
197: String s = doc.getText(offset, 3);
198: StringBuilder sb = new StringBuilder();
199: if (!Character.isWhitespace(doc.getText(offset - 1, 1)
200: .charAt(0))) {
201: sb.append(' ');
202: }
203: sb.append("=>");
204: if (offset < doc.getLength() - 2) {
205: if (!Character.isWhitespace(doc.getText(offset + 1,
206: 1).charAt(0))) {
207: sb.append(' ');
208: }
209: }
210: list.replace(offset, 1, sb.toString(), false, 0);
211: }
212:
213: return list;
214: }
215:
216: public boolean isSafe() {
217: return true;
218: }
219:
220: public boolean isInteractive() {
221: return false;
222: }
223:
224: public boolean canPreview() {
225: return true;
226: }
227: }
228: }
|