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        //the pair contains the first and the last elements of a range
099        @NotNull
100        public static Pair<PsiElement, PsiElement> createColonAndWhiteSpaces(Project project) {
101            JetProperty property = createProperty(project, "val x : Int");
102            return Pair.create(property.findElementAt(5), property.findElementAt(7));
103        }
104    
105        //the pair contains the first and the last elements of a range
106        @NotNull
107        public static Pair<PsiElement, PsiElement> createTypeWhiteSpaceAndColon(Project project, String type) {
108            JetProperty property = createProperty(project, "val x: " + type);
109            return Pair.create(property.findElementAt(5), (PsiElement) property.getTypeRef());
110        }
111    
112        @NotNull
113        public static ASTNode createColonNode(Project project) {
114            JetProperty property = createProperty(project, "val x: Int");
115            return property.getNode().findChildByType(JetTokens.COLON);
116        }
117    
118        @NotNull
119        public static PsiElement createSemicolon(Project project) {
120            JetProperty property = createProperty(project, "val x: Int;");
121            PsiElement semicolon = property.findElementAt(10);
122            assert semicolon != null;
123            return semicolon;
124        }
125    
126        //the pair contains the first and the last elements of a range
127        @NotNull
128        public static Pair<PsiElement, PsiElement> createWhitespaceAndArrow(Project project) {
129            JetFunctionType functionType = (JetFunctionType) createType(project, "() -> Int").getTypeElement();
130            assert functionType != null;
131            return Pair.create(functionType.findElementAt(2), functionType.findElementAt(3));
132        }
133    
134        @NotNull
135        public static PsiElement createWhiteSpace(Project project) {
136            return createWhiteSpace(project, " ");
137        }
138    
139        @NotNull
140        private static PsiElement createWhiteSpace(Project project, String text) {
141            JetProperty property = createProperty(project, "val" + text + "x");
142            return property.findElementAt(3);
143        }
144    
145        @NotNull
146        public static PsiElement createNewLine(Project project) {
147            return createWhiteSpace(project, "\n");
148        }
149    
150        @NotNull
151        public static JetClass createClass(Project project, String text) {
152            return createDeclaration(project, text, JetClass.class);
153        }
154    
155        @NotNull
156        public static JetFile createFile(Project project, String text) {
157            return createFile(project, "dummy.kt", text);
158        }
159    
160        @NotNull
161        public static JetFile createFile(Project project, String fileName, String text) {
162            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
163                                                                                    LocalTimeCounter.currentTime(), false);
164        }
165    
166        @NotNull
167        public static JetFile createPhysicalFile(Project project, String fileName, String text) {
168            return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
169                                                                                    LocalTimeCounter.currentTime(), true);
170        }
171    
172        @NotNull
173        public static JetProperty createProperty(Project project, String name, String type, boolean isVar, @Nullable String initializer) {
174            String text = (isVar ? "var " : "val ") + name + (type != null ? ":" + type : "") + (initializer == null ? "" : " = " + initializer);
175            return createProperty(project, text);
176        }
177    
178        @NotNull
179        public static JetProperty createProperty(Project project, String name, String type, boolean isVar) {
180            return createProperty(project, name, type, isVar, null);
181        }
182    
183        @NotNull
184        public static JetProperty createProperty(Project project, String text) {
185            return createDeclaration(project, text, JetProperty.class);
186        }
187    
188        @NotNull
189        public static <T> T createDeclaration(Project project, String text, Class<T> clazz) {
190            JetFile file = createFile(project, text);
191            List<JetDeclaration> dcls = file.getDeclarations();
192            assert dcls.size() == 1 : dcls.size() + " declarations in " + text;
193            @SuppressWarnings("unchecked")
194            T result = (T) dcls.get(0);
195            return result;
196        }
197    
198        @NotNull
199        public static PsiElement createNameIdentifier(Project project, String name) {
200            return createProperty(project, name, null, false).getNameIdentifier();
201        }
202    
203        @NotNull
204        public static JetSimpleNameExpression createSimpleName(Project project, String name) {
205            return (JetSimpleNameExpression) createProperty(project, name, null, false, name).getInitializer();
206        }
207    
208        @NotNull
209        public static PsiElement createIdentifier(Project project, String name) {
210            return createSimpleName(project, name).getIdentifier();
211        }
212    
213        @NotNull
214        public static JetNamedFunction createFunction(Project project, String funDecl) {
215            return createDeclaration(project, funDecl, JetNamedFunction.class);
216        }
217    
218        @NotNull
219        public static JetModifierList createModifierList(Project project, JetKeywordToken modifier) {
220            return createModifierList(project, modifier.getValue());
221        }
222    
223        @NotNull
224        public static JetModifierList createModifierList(Project project, String text) {
225            JetProperty property = createProperty(project, text + " val x");
226            return property.getModifierList();
227        }
228    
229        @NotNull
230        public static JetAnnotation createAnnotation(Project project, String text) {
231            JetProperty property = createProperty(project, text + " val x");
232            JetModifierList modifierList = property.getModifierList();
233            assert modifierList != null;
234            return modifierList.getAnnotations().get(0);
235        }
236    
237        @NotNull
238        public static JetModifierList createConstructorModifierList(Project project, JetKeywordToken modifier) {
239            JetClass aClass = createClass(project, "class C " + modifier.getValue() + " (){}");
240            return aClass.getPrimaryConstructorModifierList();
241        }
242    
243        @NotNull
244        public static JetExpression createEmptyBody(Project project) {
245            JetNamedFunction function = createFunction(project, "fun foo() {}");
246            return function.getBodyExpression();
247        }
248    
249        @NotNull
250        public static JetClassBody createEmptyClassBody(Project project) {
251            JetClass aClass = createClass(project, "class A(){}");
252            return aClass.getBody();
253        }
254    
255        @NotNull
256        public static JetParameter createParameter(Project project, String name, String type) {
257            JetNamedFunction function = createFunction(project, "fun foo(" + name + " : " + type + ") {}");
258            return function.getValueParameters().get(0);
259        }
260    
261        @NotNull
262        public static JetParameterList createParameterList(Project project, String text) {
263            JetNamedFunction function = createFunction(project, "fun foo" + text + "{}");
264            return function.getValueParameterList();
265        }
266    
267        @NotNull
268        public static JetWhenEntry createWhenEntry(@NotNull Project project, @NotNull String entryText) {
269            JetNamedFunction function = createFunction(project, "fun foo() { when(12) { " + entryText + " } }");
270            JetWhenEntry whenEntry = PsiTreeUtil.findChildOfType(function, JetWhenEntry.class);
271    
272            assert whenEntry != null : "Couldn't generate when entry";
273            assert entryText.equals(whenEntry.getText()) : "Generate when entry text differs from the given text";
274    
275            return whenEntry;
276        }
277    
278        @NotNull
279        public static JetStringTemplateEntryWithExpression createBlockStringTemplateEntry(@NotNull Project project, @NotNull JetExpression expression) {
280            JetStringTemplateExpression stringTemplateExpression = (JetStringTemplateExpression) createExpression(project,
281                                                                                                     "\"${" + expression.getText() + "}\"");
282            return (JetStringTemplateEntryWithExpression) stringTemplateExpression.getEntries()[0];
283        }
284    
285        @NotNull
286        public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
287            return createImportDirective(project, new ImportPath(path));
288        }
289    
290        @NotNull
291        public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) {
292            if (importPath.fqnPart().isRoot()) {
293                throw new IllegalArgumentException("import path must not be empty");
294            }
295    
296            StringBuilder importDirectiveBuilder = new StringBuilder("import ");
297            importDirectiveBuilder.append(importPath.getPathStr());
298    
299            Name alias = importPath.getAlias();
300            if (alias != null) {
301                importDirectiveBuilder.append(" as ").append(alias.asString());
302            }
303    
304            JetFile namespace = createFile(project, importDirectiveBuilder.toString());
305            return namespace.getImportDirectives().iterator().next();
306        }
307    
308        @NotNull
309        public static PsiElement createPrimaryConstructor(Project project) {
310            JetClass aClass = createClass(project, "class A()");
311            return aClass.findElementAt(7).getParent();
312        }
313    
314        @NotNull
315        public static JetSimpleNameExpression createClassLabel(Project project, @NotNull String labelName) {
316            JetThisExpression expression = (JetThisExpression) createExpression(project, "this@" + labelName);
317            return expression.getTargetLabel();
318        }
319    
320        @NotNull
321        public static JetExpression createFieldIdentifier(Project project, @NotNull String fieldName) {
322            return createExpression(project, "$" + fieldName);
323        }
324    
325        @NotNull
326        public static JetBinaryExpression createBinaryExpression(Project project, @NotNull String lhs, @NotNull String op, @NotNull String rhs) {
327            return (JetBinaryExpression) createExpression(project, lhs + " " + op + " " + rhs);
328        }
329    
330        @NotNull
331        public static JetBinaryExpression createBinaryExpression(Project project, @Nullable JetExpression lhs, @NotNull String op, @Nullable JetExpression rhs) {
332            return createBinaryExpression(project, JetPsiUtil.getText(lhs), op, JetPsiUtil.getText(rhs));
333        }
334    
335        @NotNull
336        public static JetTypeCodeFragment createTypeCodeFragment(Project project, String text, PsiElement context) {
337            return new JetTypeCodeFragmentImpl(project, "fragment.kt", text, context);
338        }
339    
340        @NotNull
341        public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
342            return new JetExpressionCodeFragmentImpl(project, "fragment.kt", text, context);
343        }
344    
345        @NotNull
346        public static JetReturnExpression createReturn(Project project, @NotNull String text) {
347            return (JetReturnExpression) createExpression(project, "return " + text);
348        }
349    
350        @NotNull
351        public static JetReturnExpression createReturn(Project project, @Nullable JetExpression expression) {
352            return createReturn(project, JetPsiUtil.getText(expression));
353        }
354    
355        @NotNull
356        public static JetIfExpression createIf(Project project,
357                @Nullable JetExpression condition, @Nullable JetExpression thenExpr, @Nullable JetExpression elseExpr) {
358            return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr));
359        }
360    
361        @NotNull
362        public static JetValueArgument createArgumentWithName(
363                @NotNull Project project,
364                @NotNull String name,
365                @NotNull JetExpression argumentExpression
366        ) {
367            return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0);
368        }
369    
370        public static class IfChainBuilder {
371            private final StringBuilder sb = new StringBuilder();
372            private boolean first = true;
373            private boolean frozen = false;
374    
375            public IfChainBuilder() {
376            }
377    
378            @NotNull
379            public IfChainBuilder ifBranch(@NotNull String conditionText, @NotNull String expressionText) {
380                if (first) {
381                    first = false;
382                } else {
383                    sb.append("else ");
384                }
385    
386                sb.append("if (").append(conditionText).append(") ").append(expressionText).append("\n");
387                return this;
388            }
389    
390            @NotNull
391            public IfChainBuilder ifBranch(@NotNull JetExpression condition, @NotNull JetExpression expression) {
392                return ifBranch(condition.getText(), expression.getText());
393            }
394    
395            @NotNull
396            public IfChainBuilder elseBranch(@NotNull String expressionText) {
397                sb.append("else ").append(expressionText);
398                return this;
399            }
400    
401            @NotNull
402            public IfChainBuilder elseBranch(@Nullable JetExpression expression) {
403                return elseBranch(JetPsiUtil.getText(expression));
404            }
405    
406            @NotNull
407            public JetIfExpression toExpression(Project project) {
408                if (!frozen) {
409                    frozen = true;
410                }
411                return (JetIfExpression) createExpression(project, sb.toString());
412            }
413        }
414    
415        public static class WhenBuilder {
416            private final StringBuilder sb = new StringBuilder("when ");
417            private boolean frozen = false;
418            private boolean inCondition = false;
419    
420            public WhenBuilder() {
421                this((String)null);
422            }
423    
424            public WhenBuilder(@Nullable String subjectText) {
425                if (subjectText != null) {
426                    sb.append("(").append(subjectText).append(") ");
427                }
428                sb.append("{\n");
429            }
430    
431            public WhenBuilder(@Nullable JetExpression subject) {
432                this(subject != null ? subject.getText() : null);
433            }
434    
435            @NotNull
436            public WhenBuilder condition(@NotNull String text) {
437                assert !frozen;
438    
439                if (!inCondition) {
440                    inCondition = true;
441                } else {
442                    sb.append(", ");
443                }
444                sb.append(text);
445    
446                return this;
447            }
448    
449            @NotNull
450            public WhenBuilder condition(@Nullable JetExpression expression) {
451                return condition(JetPsiUtil.getText(expression));
452            }
453    
454            @NotNull
455            public WhenBuilder pattern(@NotNull String typeReferenceText, boolean negated) {
456                return condition((negated ? "!is" : "is") + " " + typeReferenceText);
457            }
458    
459            @NotNull
460            public WhenBuilder pattern(@Nullable JetTypeReference typeReference, boolean negated) {
461                return pattern(JetPsiUtil.getText(typeReference), negated);
462            }
463    
464            @NotNull
465            public WhenBuilder range(@NotNull String argumentText, boolean negated) {
466                return condition((negated ? "!in" : "in") + " " + argumentText);
467            }
468    
469            @NotNull
470            public WhenBuilder range(@Nullable JetExpression argument, boolean negated) {
471                return range(JetPsiUtil.getText(argument), negated);
472            }
473    
474            @NotNull
475            public WhenBuilder branchExpression(@NotNull String expressionText) {
476                assert !frozen;
477                assert inCondition;
478    
479                inCondition = false;
480                sb.append(" -> ").append(expressionText).append("\n");
481    
482                return this;
483            }
484    
485            @NotNull
486            public WhenBuilder branchExpression(@Nullable JetExpression expression) {
487                return branchExpression(JetPsiUtil.getText(expression));
488            }
489    
490            @NotNull
491            public WhenBuilder entry(@NotNull String entryText) {
492                assert !frozen;
493                assert !inCondition;
494    
495                sb.append(entryText).append("\n");
496    
497                return this;
498            }
499    
500            @NotNull
501            public WhenBuilder entry(@Nullable JetWhenEntry whenEntry) {
502                return entry(JetPsiUtil.getText(whenEntry));
503            }
504    
505            @NotNull
506            public WhenBuilder elseEntry(@NotNull String text) {
507                return entry("else -> " + text);
508            }
509    
510            @NotNull
511            public WhenBuilder elseEntry(@Nullable JetExpression expression) {
512                return elseEntry(JetPsiUtil.getText(expression));
513            }
514    
515            @NotNull
516            public JetWhenExpression toExpression(Project project) {
517                if (!frozen) {
518                    sb.append("}");
519                    frozen = true;
520                }
521                return (JetWhenExpression) createExpression(project, sb.toString());
522            }
523        }
524    
525        @NotNull
526        public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
527            JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
528            return func.getBodyExpression();
529        }
530    
531        @NotNull
532        public static JetClassObject createEmptyClassObject(Project project) {
533            JetClass klass = createClass(project, "class foo { class object { } }");
534            return klass.getClassObject();
535        }
536    
537        @NotNull
538        public static JetBlockExpression wrapInABlock(@NotNull JetExpression expression) {
539            if (expression instanceof JetBlockExpression) {
540                return (JetBlockExpression) expression;
541            }
542            return BlockWrapper.create(expression);
543        }
544    
545        private static class BlockWrapper extends JetBlockExpression implements JetPsiUtil.JetExpressionWrapper {
546            private final JetExpression expression;
547    
548            public static BlockWrapper create(@NotNull JetExpression expressionToWrap) {
549                JetNamedFunction function = createFunction(expressionToWrap.getProject(), "fun f() { " + expressionToWrap.getText() + "}");
550                JetBlockExpression block = (JetBlockExpression) function.getBodyExpression();
551                assert block != null;
552                return new BlockWrapper(block, expressionToWrap);
553            }
554    
555            private BlockWrapper(@NotNull JetBlockExpression fakeBlockExpression, @NotNull JetExpression expressionToWrap) {
556                super(fakeBlockExpression.getNode());
557                this.expression = expressionToWrap;
558            }
559    
560            @NotNull
561            @Override
562            public List<JetElement> getStatements() {
563                return Collections.<JetElement>singletonList(expression);
564            }
565    
566            @Override
567            public JetExpression getBaseExpression() {
568                return expression;
569            }
570        }
571    }