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