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.psi.PsiElement;
022    import com.intellij.psi.PsiFileFactory;
023    import com.intellij.psi.util.PsiTreeUtil;
024    import com.intellij.util.LocalTimeCounter;
025    import kotlin.KotlinPackage;
026    import kotlin.Pair;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.jet.lang.resolve.ImportPath;
030    import org.jetbrains.jet.lang.resolve.name.Name;
031    import org.jetbrains.jet.lexer.JetKeywordToken;
032    import org.jetbrains.jet.plugin.JetFileType;
033    
034    import java.util.Collection;
035    import java.util.Collections;
036    import java.util.List;
037    
038    public class JetPsiFactory {
039    
040        @NotNull
041        public static ASTNode createValNode(Project project) {
042            JetProperty property = createProperty(project, "val x = 1");
043            return property.getValOrVarNode();
044        }
045    
046        @NotNull
047        public static ASTNode createVarNode(Project project) {
048            JetProperty property = createProperty(project, "var x = 1");
049            return property.getValOrVarNode();
050        }
051    
052        @NotNull
053        public static ASTNode createValOrVarNode(Project project, String text) {
054            return createParameterList(project, "(" + text + " int x)").getParameters().get(0).getValOrVarNode();
055        }
056    
057        @NotNull
058        public static JetExpression createExpression(Project project, String text) {
059            JetProperty property = createProperty(project, "val x = " + text);
060            return property.getInitializer();
061        }
062    
063        @NotNull
064        public static JetValueArgumentList createCallArguments(Project project, String text) {
065            JetProperty property = createProperty(project, "val x = foo" + text);
066            JetExpression initializer = property.getInitializer();
067            JetCallExpression callExpression = (JetCallExpression) initializer;
068            return callExpression.getValueArgumentList();
069        }
070    
071        @NotNull
072        public static JetTypeArgumentList createTypeArguments(Project project, String text) {
073            JetProperty property = createProperty(project, "val x = foo" + text + "()");
074            JetExpression initializer = property.getInitializer();
075            JetCallExpression callExpression = (JetCallExpression) initializer;
076            return callExpression.getTypeArgumentList();
077        }
078    
079        @NotNull
080        public static JetTypeReference createType(Project project, String type) {
081            JetProperty property = createProperty(project, "val x : " + type);
082            return property.getTypeRef();
083        }
084    
085        @NotNull
086        public static PsiElement createStar(Project project) {
087            PsiElement star = createType(project, "List<*>").findElementAt(5);
088            assert star != null;
089            return star;
090        }
091    
092        @NotNull
093        public static PsiElement createComma(Project project) {
094            PsiElement comma = createType(project, "T<X, Y>").findElementAt(3);
095            assert comma != null;
096            return comma;
097        }
098    
099        @NotNull
100        public static PsiElement createColon(Project project) {
101            JetProperty property = createProperty(project, "val x: Int");
102            PsiElement colon = property.findElementAt(5);
103            assert colon != null;
104            return colon;
105        }
106    
107        @NotNull
108        public static PsiElement createEQ(Project project) {
109            PsiElement eq = createFunction(project, "fun foo() = foo").getEqualsToken();
110            assert eq != null;
111            return eq;
112        }
113    
114        @NotNull
115        public static PsiElement createSemicolon(Project project) {
116            JetProperty property = createProperty(project, "val x: Int;");
117            PsiElement semicolon = property.findElementAt(10);
118            assert semicolon != null;
119            return semicolon;
120        }
121    
122        //the pair contains the first and the last elements of a range
123        @NotNull
124        public static Pair<PsiElement, PsiElement> createWhitespaceAndArrow(Project project) {
125            JetFunctionType functionType = (JetFunctionType) createType(project, "() -> Int").getTypeElement();
126            assert functionType != null;
127            return new Pair<PsiElement, PsiElement>(functionType.findElementAt(2), functionType.findElementAt(3));
128        }
129    
130        @NotNull
131        public static PsiElement createWhiteSpace(Project project) {
132            return createWhiteSpace(project, " ");
133        }
134    
135        @NotNull
136        public static PsiElement createWhiteSpace(Project project, String text) {
137            JetProperty property = createProperty(project, "val" + text + "x");
138            return property.findElementAt(3);
139        }
140    
141        @NotNull
142        public static PsiElement createNewLine(Project project) {
143            return createWhiteSpace(project, "\n");
144        }
145    
146        @NotNull
147        public static JetClass createClass(Project project, String text) {
148            return createDeclaration(project, text, JetClass.class);
149        }
150    
151        @NotNull
152        public static JetFile createFile(Project project, String text) {
153            return createFile(project, "dummy.kt", text);
154        }
155    
156        @NotNull
157        public static JetFile createFile(Project project, String fileName, String text) {
158            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
159                                                                                    LocalTimeCounter.currentTime(), false);
160        }
161    
162        @NotNull
163        public static JetFile createPhysicalFile(Project project, String fileName, String text) {
164            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
165                                                                                    LocalTimeCounter.currentTime(), true);
166        }
167    
168        @NotNull
169        public static JetProperty createProperty(Project project, String name, String type, boolean isVar, @Nullable String initializer) {
170            String text = (isVar ? "var " : "val ") + name + (type != null ? ":" + type : "") + (initializer == null ? "" : " = " + initializer);
171            return createProperty(project, text);
172        }
173    
174        @NotNull
175        public static JetProperty createProperty(Project project, String name, String type, boolean isVar) {
176            return createProperty(project, name, type, isVar, null);
177        }
178    
179        @NotNull
180        public static JetProperty createProperty(Project project, String text) {
181            return createDeclaration(project, text, JetProperty.class);
182        }
183    
184        @NotNull
185        public static <T> T createDeclaration(Project project, String text, Class<T> clazz) {
186            JetFile file = createFile(project, text);
187            List<JetDeclaration> dcls = file.getDeclarations();
188            assert dcls.size() == 1 : dcls.size() + " declarations in " + text;
189            @SuppressWarnings("unchecked")
190            T result = (T) dcls.get(0);
191            return result;
192        }
193    
194        @NotNull
195        public static PsiElement createNameIdentifier(Project project, String name) {
196            return createProperty(project, name, null, false).getNameIdentifier();
197        }
198    
199        @NotNull
200        public static JetSimpleNameExpression createSimpleName(Project project, String name) {
201            return (JetSimpleNameExpression) createProperty(project, name, null, false, name).getInitializer();
202        }
203    
204        @NotNull
205        public static PsiElement createIdentifier(Project project, String name) {
206            return createSimpleName(project, name).getIdentifier();
207        }
208    
209        @NotNull
210        public static JetNamedFunction createFunction(Project project, String funDecl) {
211            return createDeclaration(project, funDecl, JetNamedFunction.class);
212        }
213    
214        @NotNull
215        public static JetModifierList createModifierList(Project project, JetKeywordToken modifier) {
216            return createModifierList(project, modifier.getValue());
217        }
218    
219        @NotNull
220        public static JetModifierList createModifierList(Project project, String text) {
221            JetProperty property = createProperty(project, text + " val x");
222            return property.getModifierList();
223        }
224    
225        @NotNull
226        public static JetAnnotation createAnnotation(Project project, String text) {
227            JetProperty property = createProperty(project, text + " val x");
228            JetModifierList modifierList = property.getModifierList();
229            assert modifierList != null;
230            return modifierList.getAnnotations().get(0);
231        }
232    
233        @NotNull
234        public static JetModifierList createConstructorModifierList(Project project, JetKeywordToken modifier) {
235            JetClass aClass = createClass(project, "class C " + modifier.getValue() + " (){}");
236            return aClass.getPrimaryConstructorModifierList();
237        }
238    
239        @NotNull
240        public static JetExpression createEmptyBody(Project project) {
241            JetNamedFunction function = createFunction(project, "fun foo() {}");
242            return function.getBodyExpression();
243        }
244    
245        @NotNull
246        public static JetClassBody createEmptyClassBody(Project project) {
247            JetClass aClass = createClass(project, "class A(){}");
248            return aClass.getBody();
249        }
250    
251        @NotNull
252        public static JetParameter createParameter(Project project, String name, String type) {
253            JetNamedFunction function = createFunction(project, "fun foo(" + name + " : " + type + ") {}");
254            return function.getValueParameters().get(0);
255        }
256    
257        @NotNull
258        public static JetParameterList createParameterList(Project project, String text) {
259            JetNamedFunction function = createFunction(project, "fun foo" + text + "{}");
260            return function.getValueParameterList();
261        }
262    
263        @NotNull
264        public static JetWhenEntry createWhenEntry(@NotNull Project project, @NotNull String entryText) {
265            JetNamedFunction function = createFunction(project, "fun foo() { when(12) { " + entryText + " } }");
266            JetWhenEntry whenEntry = PsiTreeUtil.findChildOfType(function, JetWhenEntry.class);
267    
268            assert whenEntry != null : "Couldn't generate when entry";
269            assert entryText.equals(whenEntry.getText()) : "Generate when entry text differs from the given text";
270    
271            return whenEntry;
272        }
273    
274        @NotNull
275        public static JetStringTemplateEntryWithExpression createBlockStringTemplateEntry(@NotNull Project project, @NotNull JetExpression expression) {
276            JetStringTemplateExpression stringTemplateExpression = (JetStringTemplateExpression) createExpression(project,
277                                                                                                     "\"${" + expression.getText() + "}\"");
278            return (JetStringTemplateEntryWithExpression) stringTemplateExpression.getEntries()[0];
279        }
280    
281        @NotNull
282        public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
283            return createImportDirective(project, new ImportPath(path));
284        }
285    
286        @NotNull
287        public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) {
288            if (importPath.fqnPart().isRoot()) {
289                throw new IllegalArgumentException("import path must not be empty");
290            }
291    
292            StringBuilder importDirectiveBuilder = new StringBuilder("import ");
293            importDirectiveBuilder.append(importPath.getPathStr());
294    
295            Name alias = importPath.getAlias();
296            if (alias != null) {
297                importDirectiveBuilder.append(" as ").append(alias.asString());
298            }
299    
300            JetFile file = createFile(project, importDirectiveBuilder.toString());
301            return file.getImportDirectives().iterator().next();
302        }
303    
304        @NotNull
305        public static JetImportList createImportDirectiveWithImportList(Project project, @NotNull ImportPath importPath) {
306            JetImportDirective importDirective = createImportDirective(project, importPath);
307            return (JetImportList) importDirective.getParent();
308        }
309    
310        @NotNull
311        public static PsiElement createPrimaryConstructor(Project project) {
312            JetClass aClass = createClass(project, "class A()");
313            return aClass.findElementAt(7).getParent();
314        }
315    
316        @NotNull
317        public static JetSimpleNameExpression createClassLabel(Project project, @NotNull String labelName) {
318            JetThisExpression expression = (JetThisExpression) createExpression(project, "this@" + labelName);
319            return expression.getTargetLabel();
320        }
321    
322        @NotNull
323        public static JetExpression createFieldIdentifier(Project project, @NotNull String fieldName) {
324            return createExpression(project, "$" + fieldName);
325        }
326    
327        @NotNull
328        public static JetBinaryExpression createBinaryExpression(Project project, @NotNull String lhs, @NotNull String op, @NotNull String rhs) {
329            return (JetBinaryExpression) createExpression(project, lhs + " " + op + " " + rhs);
330        }
331    
332        @NotNull
333        public static JetBinaryExpression createBinaryExpression(Project project, @Nullable JetExpression lhs, @NotNull String op, @Nullable JetExpression rhs) {
334            return createBinaryExpression(project, JetPsiUtil.getText(lhs), op, JetPsiUtil.getText(rhs));
335        }
336    
337        @NotNull
338        public static JetTypeCodeFragment createTypeCodeFragment(Project project, String text, PsiElement context) {
339            return new JetTypeCodeFragment(project, "fragment.kt", text, context);
340        }
341    
342        @NotNull
343        public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
344            return new JetExpressionCodeFragment(project, "fragment.kt", text, context);
345        }
346    
347        @NotNull
348        public static JetBlockCodeFragment createBlockCodeFragment(Project project, String text, PsiElement context) {
349            return new JetBlockCodeFragment(project, "fragment.kt", text, context);
350        }
351    
352        @NotNull
353        public static JetReturnExpression createReturn(Project project, @NotNull String text) {
354            return (JetReturnExpression) createExpression(project, "return " + text);
355        }
356    
357        @NotNull
358        public static JetReturnExpression createReturn(Project project, @Nullable JetExpression expression) {
359            return createReturn(project, JetPsiUtil.getText(expression));
360        }
361    
362        @NotNull
363        public static JetIfExpression createIf(Project project,
364                @Nullable JetExpression condition, @Nullable JetExpression thenExpr, @Nullable JetExpression elseExpr) {
365            return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr));
366        }
367    
368        @NotNull
369        public static JetValueArgument createArgumentWithName(
370                @NotNull Project project,
371                @NotNull String name,
372                @NotNull JetExpression argumentExpression
373        ) {
374            return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0);
375        }
376    
377        public static class IfChainBuilder {
378            private final StringBuilder sb = new StringBuilder();
379            private boolean first = true;
380            private boolean frozen = false;
381    
382            public IfChainBuilder() {
383            }
384    
385            @NotNull
386            public IfChainBuilder ifBranch(@NotNull String conditionText, @NotNull String expressionText) {
387                if (first) {
388                    first = false;
389                } else {
390                    sb.append("else ");
391                }
392    
393                sb.append("if (").append(conditionText).append(") ").append(expressionText).append("\n");
394                return this;
395            }
396    
397            @NotNull
398            public IfChainBuilder ifBranch(@NotNull JetExpression condition, @NotNull JetExpression expression) {
399                return ifBranch(condition.getText(), expression.getText());
400            }
401    
402            @NotNull
403            public IfChainBuilder elseBranch(@NotNull String expressionText) {
404                sb.append("else ").append(expressionText);
405                return this;
406            }
407    
408            @NotNull
409            public IfChainBuilder elseBranch(@Nullable JetExpression expression) {
410                return elseBranch(JetPsiUtil.getText(expression));
411            }
412    
413            @NotNull
414            public JetIfExpression toExpression(Project project) {
415                if (!frozen) {
416                    frozen = true;
417                }
418                return (JetIfExpression) createExpression(project, sb.toString());
419            }
420        }
421    
422        public static class WhenBuilder {
423            private final StringBuilder sb = new StringBuilder("when ");
424            private boolean frozen = false;
425            private boolean inCondition = false;
426    
427            public WhenBuilder() {
428                this((String)null);
429            }
430    
431            public WhenBuilder(@Nullable String subjectText) {
432                if (subjectText != null) {
433                    sb.append("(").append(subjectText).append(") ");
434                }
435                sb.append("{\n");
436            }
437    
438            public WhenBuilder(@Nullable JetExpression subject) {
439                this(subject != null ? subject.getText() : null);
440            }
441    
442            @NotNull
443            public WhenBuilder condition(@NotNull String text) {
444                assert !frozen;
445    
446                if (!inCondition) {
447                    inCondition = true;
448                } else {
449                    sb.append(", ");
450                }
451                sb.append(text);
452    
453                return this;
454            }
455    
456            @NotNull
457            public WhenBuilder condition(@Nullable JetExpression expression) {
458                return condition(JetPsiUtil.getText(expression));
459            }
460    
461            @NotNull
462            public WhenBuilder pattern(@NotNull String typeReferenceText, boolean negated) {
463                return condition((negated ? "!is" : "is") + " " + typeReferenceText);
464            }
465    
466            @NotNull
467            public WhenBuilder pattern(@Nullable JetTypeReference typeReference, boolean negated) {
468                return pattern(JetPsiUtil.getText(typeReference), negated);
469            }
470    
471            @NotNull
472            public WhenBuilder range(@NotNull String argumentText, boolean negated) {
473                return condition((negated ? "!in" : "in") + " " + argumentText);
474            }
475    
476            @NotNull
477            public WhenBuilder range(@Nullable JetExpression argument, boolean negated) {
478                return range(JetPsiUtil.getText(argument), negated);
479            }
480    
481            @NotNull
482            public WhenBuilder branchExpression(@NotNull String expressionText) {
483                assert !frozen;
484                assert inCondition;
485    
486                inCondition = false;
487                sb.append(" -> ").append(expressionText).append("\n");
488    
489                return this;
490            }
491    
492            @NotNull
493            public WhenBuilder branchExpression(@Nullable JetExpression expression) {
494                return branchExpression(JetPsiUtil.getText(expression));
495            }
496    
497            @NotNull
498            public WhenBuilder entry(@NotNull String entryText) {
499                assert !frozen;
500                assert !inCondition;
501    
502                sb.append(entryText).append("\n");
503    
504                return this;
505            }
506    
507            @NotNull
508            public WhenBuilder entry(@Nullable JetWhenEntry whenEntry) {
509                return entry(JetPsiUtil.getText(whenEntry));
510            }
511    
512            @NotNull
513            public WhenBuilder elseEntry(@NotNull String text) {
514                return entry("else -> " + text);
515            }
516    
517            @NotNull
518            public WhenBuilder elseEntry(@Nullable JetExpression expression) {
519                return elseEntry(JetPsiUtil.getText(expression));
520            }
521    
522            @NotNull
523            public JetWhenExpression toExpression(Project project) {
524                if (!frozen) {
525                    sb.append("}");
526                    frozen = true;
527                }
528                return (JetWhenExpression) createExpression(project, sb.toString());
529            }
530        }
531    
532        public static class FunctionBuilder {
533            static enum State {
534                MODIFIERS,
535                NAME,
536                RECEIVER,
537                FIRST_PARAM,
538                REST_PARAMS,
539                TYPE_CONSTRAINTS,
540                BODY,
541                DONE
542            }
543    
544            private final StringBuilder sb = new StringBuilder();
545            private State state = State.MODIFIERS;
546    
547            public FunctionBuilder() {
548            }
549    
550            private void closeParams() {
551                assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
552    
553                sb.append(")");
554    
555                state = State.TYPE_CONSTRAINTS;
556            }
557    
558            private void placeFun() {
559                assert state == State.MODIFIERS;
560    
561                if (sb.length() != 0) {
562                    sb.append(" ");
563                }
564                sb.append("fun ");
565    
566                state = State.RECEIVER;
567            }
568    
569            @NotNull
570            public FunctionBuilder modifier(@NotNull String modifier) {
571                assert state == State.MODIFIERS;
572    
573                sb.append(modifier);
574    
575                return this;
576            }
577    
578            @NotNull
579            public FunctionBuilder typeParams(@NotNull Collection<String> values) {
580                placeFun();
581                if (!values.isEmpty()) {
582                    sb.append(KotlinPackage.makeString(values, ", ", "<", "> ", -1, ""));
583                }
584    
585                return this;
586            }
587    
588            @NotNull
589            public FunctionBuilder receiver(@NotNull String receiverType) {
590                assert state == State.RECEIVER;
591    
592                sb.append(receiverType).append(".");
593                state = State.NAME;
594    
595                return this;
596            }
597    
598            @NotNull
599            public FunctionBuilder name(@NotNull String name) {
600                assert state == State.NAME || state == State.RECEIVER;
601    
602                sb.append(name).append("(");
603                state = State.FIRST_PARAM;
604    
605                return this;
606            }
607    
608            @NotNull
609            public FunctionBuilder param(@NotNull String name, @NotNull String type) {
610                assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
611    
612                if (state == State.REST_PARAMS) {
613                    sb.append(", ");
614                }
615                sb.append(name).append(": ").append(type);
616                if (state == State.FIRST_PARAM) {
617                    state = State.REST_PARAMS;
618                }
619    
620                return this;
621            }
622    
623            @NotNull
624            public FunctionBuilder returnType(@NotNull String type) {
625                closeParams();
626                sb.append(": ").append(type);
627    
628                return this;
629            }
630    
631            @NotNull
632            public FunctionBuilder noReturnType() {
633                closeParams();
634    
635                return this;
636            }
637    
638            @NotNull
639            public FunctionBuilder typeConstraints(@NotNull Collection<String> values) {
640                assert state == State.TYPE_CONSTRAINTS;
641    
642                if (!values.isEmpty()) {
643                    sb.append(KotlinPackage.makeString(values, ", ", " where ", "", -1, ""));
644                }
645                state = State.BODY;
646    
647                return this;
648            }
649    
650            @NotNull
651            public FunctionBuilder simpleBody(@NotNull String body) {
652                assert state == State.BODY || state == State.TYPE_CONSTRAINTS;
653    
654                sb.append(" = ").append(body);
655                state = State.DONE;
656    
657                return this;
658            }
659    
660            @NotNull
661            public FunctionBuilder blockBody(@NotNull String body) {
662                assert state == State.BODY || state == State.TYPE_CONSTRAINTS;
663    
664                sb.append(" {\n").append(body).append("\n}");
665                state = State.DONE;
666    
667                return this;
668            }
669    
670            @NotNull
671            public String toFunctionText() {
672                if (state != State.DONE) {
673                    state = State.DONE;
674                }
675    
676                return sb.toString();
677            }
678        }
679    
680        @NotNull
681        public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
682            JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
683            return func.getBodyExpression();
684        }
685    
686        @NotNull
687        public static JetClassObject createEmptyClassObject(Project project) {
688            JetClass klass = createClass(project, "class foo { class object { } }");
689            return klass.getClassObject();
690        }
691    
692        @NotNull
693        public static JetBlockExpression wrapInABlock(@NotNull JetExpression expression) {
694            if (expression instanceof JetBlockExpression) {
695                return (JetBlockExpression) expression;
696            }
697            return BlockWrapper.create(expression);
698        }
699    
700        private static class BlockWrapper extends JetBlockExpression implements JetPsiUtil.JetExpressionWrapper {
701            private final JetExpression expression;
702    
703            public static BlockWrapper create(@NotNull JetExpression expressionToWrap) {
704                JetNamedFunction function = createFunction(expressionToWrap.getProject(), "fun f() { " + expressionToWrap.getText() + "}");
705                JetBlockExpression block = (JetBlockExpression) function.getBodyExpression();
706                assert block != null;
707                return new BlockWrapper(block, expressionToWrap);
708            }
709    
710            private BlockWrapper(@NotNull JetBlockExpression fakeBlockExpression, @NotNull JetExpression expressionToWrap) {
711                super(fakeBlockExpression.getNode());
712                this.expression = expressionToWrap;
713            }
714    
715            @NotNull
716            @Override
717            public List<JetElement> getStatements() {
718                return Collections.<JetElement>singletonList(expression);
719            }
720    
721            @Override
722            public JetExpression getBaseExpression() {
723                return expression;
724            }
725        }
726    }