001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.lang.psi;
018    
019    import com.intellij.lang.ASTNode;
020    import com.intellij.openapi.project.Project;
021    import com.intellij.openapi.util.Pair;
022    import com.intellij.psi.PsiElement;
023    import com.intellij.psi.PsiFileFactory;
024    import com.intellij.psi.util.PsiTreeUtil;
025    import com.intellij.util.LocalTimeCounter;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.annotations.Nullable;
028    import org.jetbrains.jet.lang.resolve.ImportPath;
029    import org.jetbrains.jet.lang.resolve.name.Name;
030    import org.jetbrains.jet.lexer.JetKeywordToken;
031    import org.jetbrains.jet.lexer.JetTokens;
032    import org.jetbrains.jet.plugin.JetFileType;
033    
034    import java.util.Collections;
035    import java.util.List;
036    
037    public class JetPsiFactory {
038    
039        @NotNull
040        public static ASTNode createValNode(Project project) {
041            JetProperty property = createProperty(project, "val x = 1");
042            return property.getValOrVarNode();
043        }
044    
045        @NotNull
046        public static ASTNode createVarNode(Project project) {
047            JetProperty property = createProperty(project, "var x = 1");
048            return property.getValOrVarNode();
049        }
050    
051        @NotNull
052        public static ASTNode createValOrVarNode(Project project, String text) {
053            return createParameterList(project, "(" + text + " int x)").getParameters().get(0).getValOrVarNode();
054        }
055    
056        @NotNull
057        public static JetExpression createExpression(Project project, String text) {
058            JetProperty property = createProperty(project, "val x = " + text);
059            return property.getInitializer();
060        }
061    
062        @NotNull
063        public static JetValueArgumentList createCallArguments(Project project, String text) {
064            JetProperty property = createProperty(project, "val x = foo" + text);
065            JetExpression initializer = property.getInitializer();
066            JetCallExpression callExpression = (JetCallExpression) initializer;
067            return callExpression.getValueArgumentList();
068        }
069    
070        @NotNull
071        public static JetTypeArgumentList createTypeArguments(Project project, String text) {
072            JetProperty property = createProperty(project, "val x = foo" + text + "()");
073            JetExpression initializer = property.getInitializer();
074            JetCallExpression callExpression = (JetCallExpression) initializer;
075            return callExpression.getTypeArgumentList();
076        }
077    
078        @NotNull
079        public static JetTypeReference createType(Project project, String type) {
080            JetProperty property = createProperty(project, "val x : " + type);
081            return property.getTypeRef();
082        }
083    
084        @NotNull
085        public static PsiElement createStar(Project project) {
086            PsiElement star = createType(project, "List<*>").findElementAt(5);
087            assert star != null;
088            return star;
089        }
090    
091        @NotNull
092        public static PsiElement createComma(Project project) {
093            PsiElement comma = createType(project, "T<X, Y>").findElementAt(3);
094            assert comma != null;
095            return comma;
096        }
097    
098        @NotNull
099        public static PsiElement createColon(Project project) {
100            JetProperty property = createProperty(project, "val x: Int");
101            PsiElement colon = property.findElementAt(5);
102            assert colon != null;
103            return colon;
104        }
105    
106        @NotNull
107        public static PsiElement createEQ(Project project) {
108            PsiElement eq = createFunction(project, "fun foo() = foo").getEqualsToken();
109            assert eq != null;
110            return eq;
111        }
112    
113        @NotNull
114        public static PsiElement createSemicolon(Project project) {
115            JetProperty property = createProperty(project, "val x: Int;");
116            PsiElement semicolon = property.findElementAt(10);
117            assert semicolon != null;
118            return semicolon;
119        }
120    
121        //the pair contains the first and the last elements of a range
122        @NotNull
123        public static Pair<PsiElement, PsiElement> createWhitespaceAndArrow(Project project) {
124            JetFunctionType functionType = (JetFunctionType) createType(project, "() -> Int").getTypeElement();
125            assert functionType != null;
126            return Pair.create(functionType.findElementAt(2), functionType.findElementAt(3));
127        }
128    
129        @NotNull
130        public static PsiElement createWhiteSpace(Project project) {
131            return createWhiteSpace(project, " ");
132        }
133    
134        @NotNull
135        private static PsiElement createWhiteSpace(Project project, String text) {
136            JetProperty property = createProperty(project, "val" + text + "x");
137            return property.findElementAt(3);
138        }
139    
140        @NotNull
141        public static PsiElement createNewLine(Project project) {
142            return createWhiteSpace(project, "\n");
143        }
144    
145        @NotNull
146        public static JetClass createClass(Project project, String text) {
147            return createDeclaration(project, text, JetClass.class);
148        }
149    
150        @NotNull
151        public static JetFile createFile(Project project, String text) {
152            return createFile(project, "dummy.kt", text);
153        }
154    
155        @NotNull
156        public static JetFile createFile(Project project, String fileName, String text) {
157            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
158                                                                                    LocalTimeCounter.currentTime(), false);
159        }
160    
161        @NotNull
162        public static JetFile createPhysicalFile(Project project, String fileName, String text) {
163            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
164                                                                                    LocalTimeCounter.currentTime(), true);
165        }
166    
167        @NotNull
168        public static JetProperty createProperty(Project project, String name, String type, boolean isVar, @Nullable String initializer) {
169            String text = (isVar ? "var " : "val ") + name + (type != null ? ":" + type : "") + (initializer == null ? "" : " = " + initializer);
170            return createProperty(project, text);
171        }
172    
173        @NotNull
174        public static JetProperty createProperty(Project project, String name, String type, boolean isVar) {
175            return createProperty(project, name, type, isVar, null);
176        }
177    
178        @NotNull
179        public static JetProperty createProperty(Project project, String text) {
180            return createDeclaration(project, text, JetProperty.class);
181        }
182    
183        @NotNull
184        public static <T> T createDeclaration(Project project, String text, Class<T> clazz) {
185            JetFile file = createFile(project, text);
186            List<JetDeclaration> dcls = file.getDeclarations();
187            assert dcls.size() == 1 : dcls.size() + " declarations in " + text;
188            @SuppressWarnings("unchecked")
189            T result = (T) dcls.get(0);
190            return result;
191        }
192    
193        @NotNull
194        public static PsiElement createNameIdentifier(Project project, String name) {
195            return createProperty(project, name, null, false).getNameIdentifier();
196        }
197    
198        @NotNull
199        public static JetSimpleNameExpression createSimpleName(Project project, String name) {
200            return (JetSimpleNameExpression) createProperty(project, name, null, false, name).getInitializer();
201        }
202    
203        @NotNull
204        public static PsiElement createIdentifier(Project project, String name) {
205            return createSimpleName(project, name).getIdentifier();
206        }
207    
208        @NotNull
209        public static JetNamedFunction createFunction(Project project, String funDecl) {
210            return createDeclaration(project, funDecl, JetNamedFunction.class);
211        }
212    
213        @NotNull
214        public static JetModifierList createModifierList(Project project, JetKeywordToken modifier) {
215            return createModifierList(project, modifier.getValue());
216        }
217    
218        @NotNull
219        public static JetModifierList createModifierList(Project project, String text) {
220            JetProperty property = createProperty(project, text + " val x");
221            return property.getModifierList();
222        }
223    
224        @NotNull
225        public static JetAnnotation createAnnotation(Project project, String text) {
226            JetProperty property = createProperty(project, text + " val x");
227            JetModifierList modifierList = property.getModifierList();
228            assert modifierList != null;
229            return modifierList.getAnnotations().get(0);
230        }
231    
232        @NotNull
233        public static JetModifierList createConstructorModifierList(Project project, JetKeywordToken modifier) {
234            JetClass aClass = createClass(project, "class C " + modifier.getValue() + " (){}");
235            return aClass.getPrimaryConstructorModifierList();
236        }
237    
238        @NotNull
239        public static JetExpression createEmptyBody(Project project) {
240            JetNamedFunction function = createFunction(project, "fun foo() {}");
241            return function.getBodyExpression();
242        }
243    
244        @NotNull
245        public static JetClassBody createEmptyClassBody(Project project) {
246            JetClass aClass = createClass(project, "class A(){}");
247            return aClass.getBody();
248        }
249    
250        @NotNull
251        public static JetParameter createParameter(Project project, String name, String type) {
252            JetNamedFunction function = createFunction(project, "fun foo(" + name + " : " + type + ") {}");
253            return function.getValueParameters().get(0);
254        }
255    
256        @NotNull
257        public static JetParameterList createParameterList(Project project, String text) {
258            JetNamedFunction function = createFunction(project, "fun foo" + text + "{}");
259            return function.getValueParameterList();
260        }
261    
262        @NotNull
263        public static JetWhenEntry createWhenEntry(@NotNull Project project, @NotNull String entryText) {
264            JetNamedFunction function = createFunction(project, "fun foo() { when(12) { " + entryText + " } }");
265            JetWhenEntry whenEntry = PsiTreeUtil.findChildOfType(function, JetWhenEntry.class);
266    
267            assert whenEntry != null : "Couldn't generate when entry";
268            assert entryText.equals(whenEntry.getText()) : "Generate when entry text differs from the given text";
269    
270            return whenEntry;
271        }
272    
273        @NotNull
274        public static JetStringTemplateEntryWithExpression createBlockStringTemplateEntry(@NotNull Project project, @NotNull JetExpression expression) {
275            JetStringTemplateExpression stringTemplateExpression = (JetStringTemplateExpression) createExpression(project,
276                                                                                                     "\"${" + expression.getText() + "}\"");
277            return (JetStringTemplateEntryWithExpression) stringTemplateExpression.getEntries()[0];
278        }
279    
280        @NotNull
281        public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
282            return createImportDirective(project, new ImportPath(path));
283        }
284    
285        @NotNull
286        public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) {
287            if (importPath.fqnPart().isRoot()) {
288                throw new IllegalArgumentException("import path must not be empty");
289            }
290    
291            StringBuilder importDirectiveBuilder = new StringBuilder("import ");
292            importDirectiveBuilder.append(importPath.getPathStr());
293    
294            Name alias = importPath.getAlias();
295            if (alias != null) {
296                importDirectiveBuilder.append(" as ").append(alias.asString());
297            }
298    
299            JetFile file = createFile(project, importDirectiveBuilder.toString());
300            return file.getImportDirectives().iterator().next();
301        }
302    
303        @NotNull
304        public static JetImportList createImportDirectiveWithImportList(Project project, @NotNull ImportPath importPath) {
305            JetImportDirective importDirective = createImportDirective(project, importPath);
306            return (JetImportList) importDirective.getParent();
307        }
308    
309        @NotNull
310        public static PsiElement createPrimaryConstructor(Project project) {
311            JetClass aClass = createClass(project, "class A()");
312            return aClass.findElementAt(7).getParent();
313        }
314    
315        @NotNull
316        public static JetSimpleNameExpression createClassLabel(Project project, @NotNull String labelName) {
317            JetThisExpression expression = (JetThisExpression) createExpression(project, "this@" + labelName);
318            return expression.getTargetLabel();
319        }
320    
321        @NotNull
322        public static JetExpression createFieldIdentifier(Project project, @NotNull String fieldName) {
323            return createExpression(project, "$" + fieldName);
324        }
325    
326        @NotNull
327        public static JetBinaryExpression createBinaryExpression(Project project, @NotNull String lhs, @NotNull String op, @NotNull String rhs) {
328            return (JetBinaryExpression) createExpression(project, lhs + " " + op + " " + rhs);
329        }
330    
331        @NotNull
332        public static JetBinaryExpression createBinaryExpression(Project project, @Nullable JetExpression lhs, @NotNull String op, @Nullable JetExpression rhs) {
333            return createBinaryExpression(project, JetPsiUtil.getText(lhs), op, JetPsiUtil.getText(rhs));
334        }
335    
336        @NotNull
337        public static JetTypeCodeFragment createTypeCodeFragment(Project project, String text, PsiElement context) {
338            return new JetTypeCodeFragmentImpl(project, "fragment.kt", text, context);
339        }
340    
341        @NotNull
342        public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
343            return new JetExpressionCodeFragmentImpl(project, "fragment.kt", text, context);
344        }
345    
346        @NotNull
347        public static JetReturnExpression createReturn(Project project, @NotNull String text) {
348            return (JetReturnExpression) createExpression(project, "return " + text);
349        }
350    
351        @NotNull
352        public static JetReturnExpression createReturn(Project project, @Nullable JetExpression expression) {
353            return createReturn(project, JetPsiUtil.getText(expression));
354        }
355    
356        @NotNull
357        public static JetIfExpression createIf(Project project,
358                @Nullable JetExpression condition, @Nullable JetExpression thenExpr, @Nullable JetExpression elseExpr) {
359            return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr));
360        }
361    
362        @NotNull
363        public static JetValueArgument createArgumentWithName(
364                @NotNull Project project,
365                @NotNull String name,
366                @NotNull JetExpression argumentExpression
367        ) {
368            return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0);
369        }
370    
371        public static class IfChainBuilder {
372            private final StringBuilder sb = new StringBuilder();
373            private boolean first = true;
374            private boolean frozen = false;
375    
376            public IfChainBuilder() {
377            }
378    
379            @NotNull
380            public IfChainBuilder ifBranch(@NotNull String conditionText, @NotNull String expressionText) {
381                if (first) {
382                    first = false;
383                } else {
384                    sb.append("else ");
385                }
386    
387                sb.append("if (").append(conditionText).append(") ").append(expressionText).append("\n");
388                return this;
389            }
390    
391            @NotNull
392            public IfChainBuilder ifBranch(@NotNull JetExpression condition, @NotNull JetExpression expression) {
393                return ifBranch(condition.getText(), expression.getText());
394            }
395    
396            @NotNull
397            public IfChainBuilder elseBranch(@NotNull String expressionText) {
398                sb.append("else ").append(expressionText);
399                return this;
400            }
401    
402            @NotNull
403            public IfChainBuilder elseBranch(@Nullable JetExpression expression) {
404                return elseBranch(JetPsiUtil.getText(expression));
405            }
406    
407            @NotNull
408            public JetIfExpression toExpression(Project project) {
409                if (!frozen) {
410                    frozen = true;
411                }
412                return (JetIfExpression) createExpression(project, sb.toString());
413            }
414        }
415    
416        public static class WhenBuilder {
417            private final StringBuilder sb = new StringBuilder("when ");
418            private boolean frozen = false;
419            private boolean inCondition = false;
420    
421            public WhenBuilder() {
422                this((String)null);
423            }
424    
425            public WhenBuilder(@Nullable String subjectText) {
426                if (subjectText != null) {
427                    sb.append("(").append(subjectText).append(") ");
428                }
429                sb.append("{\n");
430            }
431    
432            public WhenBuilder(@Nullable JetExpression subject) {
433                this(subject != null ? subject.getText() : null);
434            }
435    
436            @NotNull
437            public WhenBuilder condition(@NotNull String text) {
438                assert !frozen;
439    
440                if (!inCondition) {
441                    inCondition = true;
442                } else {
443                    sb.append(", ");
444                }
445                sb.append(text);
446    
447                return this;
448            }
449    
450            @NotNull
451            public WhenBuilder condition(@Nullable JetExpression expression) {
452                return condition(JetPsiUtil.getText(expression));
453            }
454    
455            @NotNull
456            public WhenBuilder pattern(@NotNull String typeReferenceText, boolean negated) {
457                return condition((negated ? "!is" : "is") + " " + typeReferenceText);
458            }
459    
460            @NotNull
461            public WhenBuilder pattern(@Nullable JetTypeReference typeReference, boolean negated) {
462                return pattern(JetPsiUtil.getText(typeReference), negated);
463            }
464    
465            @NotNull
466            public WhenBuilder range(@NotNull String argumentText, boolean negated) {
467                return condition((negated ? "!in" : "in") + " " + argumentText);
468            }
469    
470            @NotNull
471            public WhenBuilder range(@Nullable JetExpression argument, boolean negated) {
472                return range(JetPsiUtil.getText(argument), negated);
473            }
474    
475            @NotNull
476            public WhenBuilder branchExpression(@NotNull String expressionText) {
477                assert !frozen;
478                assert inCondition;
479    
480                inCondition = false;
481                sb.append(" -> ").append(expressionText).append("\n");
482    
483                return this;
484            }
485    
486            @NotNull
487            public WhenBuilder branchExpression(@Nullable JetExpression expression) {
488                return branchExpression(JetPsiUtil.getText(expression));
489            }
490    
491            @NotNull
492            public WhenBuilder entry(@NotNull String entryText) {
493                assert !frozen;
494                assert !inCondition;
495    
496                sb.append(entryText).append("\n");
497    
498                return this;
499            }
500    
501            @NotNull
502            public WhenBuilder entry(@Nullable JetWhenEntry whenEntry) {
503                return entry(JetPsiUtil.getText(whenEntry));
504            }
505    
506            @NotNull
507            public WhenBuilder elseEntry(@NotNull String text) {
508                return entry("else -> " + text);
509            }
510    
511            @NotNull
512            public WhenBuilder elseEntry(@Nullable JetExpression expression) {
513                return elseEntry(JetPsiUtil.getText(expression));
514            }
515    
516            @NotNull
517            public JetWhenExpression toExpression(Project project) {
518                if (!frozen) {
519                    sb.append("}");
520                    frozen = true;
521                }
522                return (JetWhenExpression) createExpression(project, sb.toString());
523            }
524        }
525    
526        @NotNull
527        public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
528            JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
529            return func.getBodyExpression();
530        }
531    
532        @NotNull
533        public static JetClassObject createEmptyClassObject(Project project) {
534            JetClass klass = createClass(project, "class foo { class object { } }");
535            return klass.getClassObject();
536        }
537    
538        @NotNull
539        public static JetBlockExpression wrapInABlock(@NotNull JetExpression expression) {
540            if (expression instanceof JetBlockExpression) {
541                return (JetBlockExpression) expression;
542            }
543            return BlockWrapper.create(expression);
544        }
545    
546        private static class BlockWrapper extends JetBlockExpression implements JetPsiUtil.JetExpressionWrapper {
547            private final JetExpression expression;
548    
549            public static BlockWrapper create(@NotNull JetExpression expressionToWrap) {
550                JetNamedFunction function = createFunction(expressionToWrap.getProject(), "fun f() { " + expressionToWrap.getText() + "}");
551                JetBlockExpression block = (JetBlockExpression) function.getBodyExpression();
552                assert block != null;
553                return new BlockWrapper(block, expressionToWrap);
554            }
555    
556            private BlockWrapper(@NotNull JetBlockExpression fakeBlockExpression, @NotNull JetExpression expressionToWrap) {
557                super(fakeBlockExpression.getNode());
558                this.expression = expressionToWrap;
559            }
560    
561            @NotNull
562            @Override
563            public List<JetElement> getStatements() {
564                return Collections.<JetElement>singletonList(expression);
565            }
566    
567            @Override
568            public JetExpression getBaseExpression() {
569                return expression;
570            }
571        }
572    }