/plugins/RubyPlugin/tags/release-0_9_4/src/org/jedit/ruby/parser/RubyParser.java

# · Java · 233 lines · 154 code · 46 blank · 33 comment · 15 complexity · f71f9a550af05af12757e7e7352f739a MD5 · raw file

  1. /*
  2. * RubyParser.java - Parses ruby file
  3. *
  4. * Copyright 2005 Robert McKinnon
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. */
  20. package org.jedit.ruby.parser;
  21. import org.gjt.sp.jedit.View;
  22. import org.gjt.sp.jedit.Buffer;
  23. import org.jedit.ruby.RubyPlugin;
  24. import org.jedit.ruby.ast.*;
  25. import org.jedit.ruby.ast.Error;
  26. import org.jruby.lexer.yacc.ISourcePosition;
  27. import org.jruby.common.IRubyWarnings;
  28. import java.util.ArrayList;
  29. import java.util.HashMap;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.io.File;
  33. /**
  34. * <p>Parses ruby file.</p>
  35. * <p><i>
  36. * Not thread safe.</i>
  37. * Should only be accessed by one thread at a time.
  38. * </p>
  39. *
  40. * @author robmckinnon at users.sourceforge.net
  41. */
  42. public final class RubyParser {
  43. private static final Member[] EMPTY_MEMBER_ARRAY = new Member[0];
  44. private static final RubyParser instance = new RubyParser();
  45. private final RubyParser.LogWarningListener logListener;
  46. private final MemberMatcher methodMatcher;
  47. private final Map<File, Long> fileToLastModified;
  48. private final Map<File, String> fileToOldText;
  49. private final Map<File, Member[]> fileToMembers;
  50. private final Map<File, RubyMembers> fileToLastGoodMembers;
  51. private final Map<File, List<Problem>> fileToProblems;
  52. private RubyParser() {
  53. logListener = new LogWarningListener();
  54. methodMatcher = new MemberMatcher.MethodMatcher();
  55. fileToLastModified = new HashMap<File, Long>();
  56. fileToOldText = new HashMap<File, String>();
  57. fileToMembers = new HashMap<File, Member[]>();
  58. fileToLastGoodMembers = new HashMap<File, RubyMembers>();
  59. fileToProblems = new HashMap<File, List<Problem>>();
  60. }
  61. public static RubyMembers getMembers(View view) {
  62. String text = view.getTextArea().getText();
  63. String filePath = view.getBuffer().getPath();
  64. return getMembers(text, filePath);
  65. }
  66. public static RubyMembers getMembers(String text, String filePath) {
  67. return getMembers(text, filePath, null, false);
  68. }
  69. public static RubyMembers getMembers(String text, String filePath, WarningListener listener, boolean forceReparse) {
  70. return instance.createMembers(text, filePath, listener, forceReparse);
  71. }
  72. public static List<Member> getMembersAsList(String text, String filePath, WarningListener listener) {
  73. return instance.createMembersAsList(text, filePath, listener);
  74. }
  75. public static boolean hasLastGoodMembers(Buffer buffer) {
  76. return instance.hasLastGoodMembers(buffer.getPath());
  77. }
  78. public static RubyMembers getLastGoodMembers(Buffer buffer) {
  79. return instance.getLastGoodMembers(buffer.getPath());
  80. }
  81. private synchronized boolean hasLastGoodMembers(String path) {
  82. return fileToLastGoodMembers.containsKey(new File(path));
  83. }
  84. private synchronized RubyMembers getLastGoodMembers(String path) {
  85. return fileToLastGoodMembers.get(new File(path));
  86. }
  87. private synchronized RubyMembers createMembers(String text, String path, WarningListener listener, boolean forceReparse) {
  88. Member[] members;
  89. List<Problem> problems;
  90. File file = new File(path);
  91. if(!forceReparse
  92. && fileToMembers.containsKey(file)
  93. && fileToLastModified.get(file) == file.lastModified()
  94. && fileToOldText.get(file).equals(text)) {
  95. members = fileToMembers.get(file);
  96. problems = fileToProblems.get(file);
  97. } else {
  98. List<Member> memberList = createMembersAsList(text, path, listener);
  99. problems = logListener.getProblems();
  100. members = memberList != null ? memberList.toArray(EMPTY_MEMBER_ARRAY) : null;
  101. fileToMembers.put(file, members);
  102. if (members != null) {
  103. fileToLastGoodMembers.put(file, new RubyMembers(members, null, text.length()));
  104. } else if (fileToLastGoodMembers.containsKey(file)) {
  105. RubyMembers lastGoodMembers = fileToLastGoodMembers.get(file);
  106. lastGoodMembers.setProblems(problems);
  107. }
  108. fileToLastModified.put(file, file.lastModified());
  109. fileToOldText.put(file, text);
  110. fileToProblems.put(file, problems);
  111. }
  112. return new RubyMembers(members, problems, text.length());
  113. }
  114. private synchronized List<Member> createMembersAsList(String text, String filePath, WarningListener listener) {
  115. LineCounter lineCounter = new LineCounter(text);
  116. List<Member> methods = createMembers(text, filePath, lineCounter, methodMatcher);
  117. List<WarningListener> listeners = getListeners(listener);
  118. return JRubyParser.getMembers(text, methods, listeners, filePath, lineCounter);
  119. }
  120. private List<WarningListener> getListeners(WarningListener listener) {
  121. logListener.clear();
  122. List<WarningListener> listeners = new ArrayList<WarningListener>();
  123. listeners.add(logListener);
  124. if(listener != null) {
  125. listeners.add(listener);
  126. }
  127. return listeners;
  128. }
  129. private static List<Member> createMembers(String text, String filePath, LineCounter lineCounter, MemberMatcher matcher) {
  130. List<MemberMatcher.Match> matches = matcher.getMatches(text, lineCounter);
  131. List<Member> members = new ArrayList<Member>();
  132. for(MemberMatcher.Match match : matches) {
  133. String name = match.value().trim();
  134. int startOffset = match.startOffset();
  135. // int startOuterOffset = match.startOuterOffset;
  136. String params = match.params();
  137. members.add(matcher.createMember(name, filePath, startOffset, params, text));
  138. }
  139. return members;
  140. }
  141. /**
  142. * Interface defining methods called back
  143. * with parsing warnings.
  144. */
  145. public static interface WarningListener extends IRubyWarnings {
  146. void error(ISourcePosition position, String message);
  147. void clear();
  148. }
  149. private static final class LogWarningListener implements WarningListener {
  150. private final List<Problem> problems = new ArrayList<Problem>();
  151. public final List<Problem> getProblems() {
  152. return problems;
  153. }
  154. public boolean isVerbose() {
  155. return false;
  156. }
  157. public final void warn(ISourcePosition position, String message) {
  158. problems.add(new Warning(message, getLine(position)));
  159. log(position, message);
  160. }
  161. public final void warn(String message) {
  162. problems.add(new Warning(message, 0));
  163. RubyPlugin.log("warn: " + message, getClass());
  164. }
  165. public final void warning(ISourcePosition position, String message) {
  166. problems.add(new Warning(message, getLine(position)));
  167. log(position, message);
  168. }
  169. public final void warning(String message) {
  170. RubyPlugin.log("warn: " + message, getClass());
  171. problems.add(new Warning(message, 0));
  172. }
  173. public final void error(ISourcePosition position, String message) {
  174. RubyPlugin.log("error: " + position.getFile() + " " + position.getEndLine() + " " + message, getClass());
  175. problems.add(new Error(message, getLine(position)));
  176. }
  177. public final void clear() {
  178. problems.clear();
  179. }
  180. private void log(ISourcePosition position, String message) {
  181. RubyPlugin.log("warn: " + position.getFile() + " " + position.getEndLine() + " " + message, getClass());
  182. }
  183. private int getLine(ISourcePosition position) {
  184. return position == null ? 0 : position.getEndLine();
  185. }
  186. }
  187. }