001 /* 002 Copyright (C) 2003 Renaud Pawlak <renaud@aopsys.com> 003 004 This program is free software; you can redistribute it and/or modify 005 it under the terms of the GNU Lesser General Public License as 006 published by the Free Software Foundation; either version 2 of the 007 License, or (at your option) any later version. 008 009 This program is distributed in the hope that it will be useful, but 010 WITHOUT ANY WARRANTY; without even the implied warranty of 011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 Lesser General Public License for more details. 013 014 You should have received a copy of the GNU Lesser General Public 015 License along with this program; if not, write to the Free Software 016 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 017 USA */ 018 019 package org.objectweb.jac.ide.swing; 020 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.Vector; 025 import org.apache.log4j.Logger; 026 import org.objectweb.jac.aspects.gui.swing.CompletionEngine; 027 import org.objectweb.jac.aspects.gui.swing.SHEditor; 028 import org.objectweb.jac.core.rtti.AbstractMethodItem; 029 import org.objectweb.jac.core.rtti.ClassItem; 030 import org.objectweb.jac.core.rtti.ClassRepository; 031 import org.objectweb.jac.ide.Class; 032 import org.objectweb.jac.ide.CodeGeneration; 033 import org.objectweb.jac.ide.Field; 034 import org.objectweb.jac.ide.Method; 035 import org.objectweb.jac.ide.Parameter; 036 import org.objectweb.jac.ide.RelationRole; 037 import org.objectweb.jac.ide.Role; 038 import org.objectweb.jac.ide.Type; 039 040 /** 041 * This class implements a completion engine for method bodies Java 042 * editors of the UMLAF IDE. */ 043 044 public class MethodBodyCompletionEngine extends CompletionEngine { 045 static final Logger logger = Logger.getLogger("completion"); 046 static final Logger loggerPerf = Logger.getLogger("perf"); 047 048 Method method; 049 SHEditor editor; 050 051 /** 052 * Creates a completion engine for a given method. */ 053 054 public MethodBodyCompletionEngine(Method method, SHEditor editor) { 055 this.method = method; 056 addBaseWords(buildThisWords()); 057 logger.info("baseWords = "+baseWords); 058 this.editor = editor; 059 } 060 061 /** 062 * Get the contextual possible choices. 063 * 064 * <p>Supported contextual completions are like 065 * <code>class_typed_symbol.{methods}</code>. In the long term, any 066 * typed expression should be supported even if it is not that 067 * important in clean developments (because of the Demeter's 068 * Law!!). 069 * 070 * @param text the editor's full text 071 * @param position the cursor position 072 * @param writtenText the already written text */ 073 074 public List getContextualChoices(String text, int position, 075 String writtenText) { 076 long start = System.currentTimeMillis(); 077 078 List result; 079 int pos = position-1; 080 if (pos>0 && text.charAt(pos)=='.') { 081 // Here we should be able to guess types of more complex 082 // lines that symbols (see getSymbolType). This would need a 083 // real parser. 084 while (pos>0 && !editor.isDivider(text.charAt(--pos))) { 085 } 086 String symbol = text.substring(pos+1,position-1); 087 logger.info("found parent symbol "+symbol); 088 Type t = getSymbolType(symbol); 089 if (t!=null) { 090 result = buildTypeWords(t); 091 } else { 092 result = new Vector(); 093 } 094 } else { 095 result = getBaseWords(); 096 } 097 loggerPerf.info("getContextualChoices("+writtenText+"): "+ 098 (System.currentTimeMillis()-start)+"ms"); 099 100 logger.info( "getContextualChoices("+writtenText+") -> "+result); 101 return result; 102 } 103 104 /** 105 * Builds the list of method and field names that are directly 106 * accessible within the class of the method and the parameters of 107 * the method, as well as the names of the types of those fields. 108 */ 109 public List buildThisWords() { 110 List ret = buildClassWords(method.getParent()); 111 Iterator it = method.getParameters().iterator(); 112 while(it.hasNext()) { 113 Parameter p = (Parameter)it.next(); 114 String name = p.getGenerationName(); 115 if (!ret.contains(name)) { 116 ret.add(name); 117 } 118 } 119 return ret; 120 } 121 122 /** 123 * Get all the accessible words in the context of a given type. 124 * 125 * <p>If type is an internal IDE class, all the method plus the 126 * generated methods (getters, setters, adders, removers, clearers) 127 * are returned. If type is an external library class, all the 128 * public methods are returned. 129 * 130 * @see #buildClassWords(Class) */ 131 132 public List buildTypeWords(Type type) { 133 logger.debug("buildTypeWords("+type.getFullName()+")"); 134 if (type==null) { 135 return new Vector(); 136 } else if (type instanceof Class) { 137 return buildClassWords((Class)type); 138 } else { 139 logger.debug("found external type"); 140 List ret = new Vector(); 141 ClassItem c = ClassRepository.get().getClass(type.getFullName()); 142 Iterator it = c.getAllMethods().iterator(); 143 while(it.hasNext()) { 144 String name = ((AbstractMethodItem)it.next()).getName(); 145 logger.debug("external method candidate: "+name); 146 if (!ret.contains(name)) { 147 ret.add(name); 148 } 149 } 150 return ret; 151 } 152 } 153 154 /** 155 * Get all the accessible words in the context of a given IDE 156 * class (fields,methods). 157 * 158 * @param c the class for which to get accessible words 159 */ 160 public List buildClassWords(Class c) { 161 if (c==null) 162 return new Vector(); 163 if (classWords.containsKey(c)) { 164 return (List)classWords.get(c); 165 } 166 logger.debug("buildClassWords("+c.getFullName()+")"); 167 List ret = new Vector(); 168 Iterator it = c.getMethods().iterator(); 169 while (it.hasNext()) { 170 String name = ((Method)it.next()).getGenerationName(); 171 if (!ret.contains(name)) { 172 ret.add(name); 173 } 174 } 175 it = c.getFields().iterator(); 176 while (it.hasNext()) { 177 Field field = (Field)it.next(); 178 String name = field.getGenerationName(); 179 if (!ret.contains(name)) { 180 ret.add(name); 181 } 182 Type type = field.getType(); 183 if (type instanceof Class) { 184 name = type.getGenerationName(); 185 if (!ret.contains(name)) 186 ret.add(name); 187 } 188 // add also the setter 189 String tmpName = CodeGeneration.getSetterName(name); 190 if (!ret.contains(tmpName)) { 191 ret.add(tmpName); 192 } 193 // and the getter 194 tmpName = CodeGeneration.getGetterName(name); 195 if (!ret.contains(tmpName)) { 196 ret.add(tmpName); 197 } 198 199 } 200 201 List rs = c.getLinks(); 202 for (int i=0; i<rs.size(); i++) { 203 Role role = (Role)rs.get(i); 204 if (role instanceof RelationRole) { 205 RelationRole end = (RelationRole)role; 206 207 logger.debug(" role "+end.getGenerationName()); 208 String name = end.getGenerationName(); 209 if (!ret.contains(name)) { 210 ret.add(name); 211 } 212 name = ((Class)end.getEnd()).getGenerationName(); 213 logger.debug(" Classname of "+end.getGenerationName()+": "+name); 214 if (!ret.contains(name)) 215 ret.add(name); 216 217 String tmpName = CodeGeneration.getSetterName(name); 218 if (!ret.contains(tmpName)) { 219 ret.add(tmpName); 220 } 221 tmpName = CodeGeneration.getGetterName(name); 222 if (!ret.contains(tmpName)) { 223 ret.add(tmpName); 224 } 225 if (end.isMultiple()) { 226 tmpName = CodeGeneration.getAdderName(name); 227 if (!ret.contains(tmpName)) { 228 ret.add(tmpName); 229 } 230 tmpName = CodeGeneration.getRemoverName(name); 231 if (!ret.contains(tmpName)) { 232 ret.add(tmpName); 233 } 234 tmpName = CodeGeneration.getClearerName(name); 235 if (!ret.contains(tmpName)) { 236 ret.add(tmpName); 237 } 238 } 239 } 240 } 241 242 classWords.put(c,ret); 243 return ret; 244 } 245 246 HashMap classWords = new HashMap(); 247 248 /** 249 * Returns the type of a symbol. Any kind of expression should be 250 * supported one day... 251 * @param name the symbol's name 252 */ 253 Type getSymbolType(String name) { 254 if (symbolTypes.containsKey(name)) { 255 return (Type)symbolTypes.get(name); 256 } 257 Type type = null; 258 Iterator it = method.getParameters().iterator(); 259 while (it.hasNext()) { 260 Parameter p = (Parameter)it.next(); 261 if (p.getGenerationName().equals(name)) { 262 type = p.getType(); 263 symbolTypes.put(name,type); 264 return type; 265 } 266 } 267 Class c = method.getParent(); 268 if (c!=null) { 269 it = c.getFields().iterator(); 270 while(it.hasNext()) { 271 Field f = (Field)it.next(); 272 if (f.getGenerationName().equals(name)) { 273 type = f.getType(); 274 symbolTypes.put(name,type); 275 return type; 276 } 277 } 278 } 279 symbolTypes.put(name,null); 280 return null; 281 } 282 283 HashMap symbolTypes = new HashMap(); 284 285 /** 286 * Returns true if <code>c=='('</code>. */ 287 288 public boolean isAutomaticCompletionChar(char c) { 289 logger.debug("isAutomaticCompletionChar("+c+")"); 290 return c=='('; 291 } 292 293 /** 294 * Help the programmer to write useful control structure such as 295 * <code>for</code> or <code>while</code>. */ 296 297 public void runAutomaticCompletion(SHEditor editor, 298 String text, 299 int position, 300 char c) { 301 String word = getPreviousWord(text,position); 302 logger.debug("getAutomaticCompletion("+word+")"); 303 if(c=='(') { 304 if(word.equals("for")) { 305 editor.insertString(position,";;) {"); 306 editor.insertReturn(); 307 editor.insertReturn(); 308 editor.insertCloseCBracket(); 309 } else if(word.equals("while")) { 310 editor.insertString(position,") {"); 311 editor.insertReturn(); 312 editor.insertReturn(); 313 editor.insertCloseCBracket(); 314 } 315 } 316 } 317 318 String getPreviousWord(String text, int position) { 319 int pos2 = position-1; 320 int pos1 = pos2; 321 while (pos1>0 && !editor.isDivider(text.charAt(--pos1))) { 322 } 323 return text.substring(pos1,pos2).trim(); 324 } 325 326 } 327