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 JetImportList createImportDirectiveWithImportList(Project project, @NotNull ImportPath importPath) {
310            JetImportDirective importDirective = createImportDirective(project, importPath);
311            return (JetImportList) importDirective.getParent();
312        }
313    
314        @NotNull
315        public static PsiElement createPrimaryConstructor(Project project) {
316            JetClass aClass = createClass(project, "class A()");
317            return aClass.findElementAt(7).getParent();
318        }
319    
320        @NotNull
321        public static JetSimpleNameExpression createClassLabel(Project project, @NotNull String labelName) {
322            JetThisExpression expression = (JetThisExpression) createExpression(project, "this@" + labelName);
323            return expression.getTargetLabel();
324        }
325    
326        @NotNull
327        public static JetExpression createFieldIdentifier(Project project, @NotNull String fieldName) {
328            return createExpression(project, "$" + fieldName);
329        }
330    
331        @NotNull
332        public static JetBinaryExpression createBinaryExpression(Project project, @NotNull String lhs, @NotNull String op, @NotNull String rhs) {
333            return (JetBinaryExpression) createExpression(project, lhs + " " + op + " " + rhs);
334        }
335    
336        @NotNull
337        public static JetBinaryExpression createBinaryExpression(Project project, @Nullable JetExpression lhs, @NotNull String op, @Nullable JetExpression rhs) {
338            return createBinaryExpression(project, JetPsiUtil.getText(lhs), op, JetPsiUtil.getText(rhs));
339        }
340    
341        @NotNull
342        public static JetTypeCodeFragment createTypeCodeFragment(Project project, String text, PsiElement context) {
343            return new JetTypeCodeFragmentImpl(project, "fragment.kt", text, context);
344        }
345    
346        @NotNull
347        public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
348            return new JetExpressionCodeFragmentImpl(project, "fragment.kt", text, context);
349        }
350    
351        @NotNull
352        public static JetReturnExpression createReturn(Project project, @NotNull String text) {
353            return (JetReturnExpression) createExpression(project, "return " + text);
354        }
355    
356        @NotNull
357        public static JetReturnExpression createReturn(Project project, @Nullable JetExpression expression) {
358            return createReturn(project, JetPsiUtil.getText(expression));
359        }
360    
361        @NotNull
362        public static JetIfExpression createIf(Project project,
363                @Nullable JetExpression condition, @Nullable JetExpression thenExpr, @Nullable JetExpression elseExpr) {
364            return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr));
365        }
366    
367        @NotNull
368        public static JetValueArgument createArgumentWithName(
369                @NotNull Project project,
370                @NotNull String name,
371                @NotNull JetExpression argumentExpression
372        ) {
373            return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0);
374        }
375    
376        public static class IfChainBuilder {
377            private final StringBuilder sb = new StringBuilder();
378            private boolean first = true;
379            private boolean frozen = false;
380    
381            public IfChainBuilder() {
382            }
383    
384            @NotNull
385            public IfChainBuilder ifBranch(@NotNull String conditionText, @NotNull String expressionText) {
386                if (first) {
387                    first = false;
388                } else {
389                    sb.append("else ");
390                }
391    
392                sb.append("if (").append(conditionText).append(") ").append(expressionText).append("\n");
393                return this;
394            }
395    
396            @NotNull
397            public IfChainBuilder ifBranch(@NotNull JetExpression condition, @NotNull JetExpression expression) {
398                return ifBranch(condition.getText(), expression.getText());
399            }
400    
401            @NotNull
402            public IfChainBuilder elseBranch(@NotNull String expressionText) {
403                sb.append("else ").append(expressionText);
404                return this;
405            }
406    
407            @NotNull
408            public IfChainBuilder elseBranch(@Nullable JetExpression expression) {
409                return elseBranch(JetPsiUtil.getText(expression));
410            }
411    
412            @NotNull
413            public JetIfExpression toExpression(Project project) {
414                if (!frozen) {
415                    frozen = true;
416                }
417                return (JetIfExpression) createExpression(project, sb.toString());
418            }
419        }
420    
421        public static class WhenBuilder {
422            private final StringBuilder sb = new StringBuilder("when ");
423            private boolean frozen = false;
424            private boolean inCondition = false;
425    
426            public WhenBuilder() {
427                this((String)null);
428            }
429    
430            public WhenBuilder(@Nullable String subjectText) {
431                if (subjectText != null) {
432                    sb.append("(").append(subjectText).append(") ");
433                }
434                sb.append("{\n");
435            }
436    
437            public WhenBuilder(@Nullable JetExpression subject) {
438                this(subject != null ? subject.getText() : null);
439            }
440    
441            @NotNull
442            public WhenBuilder condition(@NotNull String text) {
443                assert !frozen;
444    
445                if (!inCondition) {
446                    inCondition = true;
447                } else {
448                    sb.append(", ");
449                }
450                sb.append(text);
451    
452                return this;
453            }
454    
455            @NotNull
456            public WhenBuilder condition(@Nullable JetExpression expression) {
457                return condition(JetPsiUtil.getText(expression));
458            }
459    
460            @NotNull
461            public WhenBuilder pattern(@NotNull String typeReferenceText, boolean negated) {
462                return condition((negated ? "!is" : "is") + " " + typeReferenceText);
463            }
464    
465            @NotNull
466            public WhenBuilder pattern(@Nullable JetTypeReference typeReference, boolean negated) {
467                return pattern(JetPsiUtil.getText(typeReference), negated);
468            }
469    
470            @NotNull
471            public WhenBuilder range(@NotNull String argumentText, boolean negated) {
472                return condition((negated ? "!in" : "in") + " " + argumentText);
473            }
474    
475            @NotNull
476            public WhenBuilder range(@Nullable JetExpression argument, boolean negated) {
477                return range(JetPsiUtil.getText(argument), negated);
478            }
479    
480            @NotNull
481            public WhenBuilder branchExpression(@NotNull String expressionText) {
482                assert !frozen;
483                assert inCondition;
484    
485                inCondition = false;
486                sb.append(" -> ").append(expressionText).append("\n");
487    
488                return this;
489            }
490    
491            @NotNull
492            public WhenBuilder branchExpression(@Nullable JetExpression expression) {
493                return branchExpression(JetPsiUtil.getText(expression));
494            }
495    
496            @NotNull
497            public WhenBuilder entry(@NotNull String entryText) {
498                assert !frozen;
499                assert !inCondition;
500    
501                sb.append(entryText).append("\n");
502    
503                return this;
504            }
505    
506            @NotNull
507            public WhenBuilder entry(@Nullable JetWhenEntry whenEntry) {
508                return entry(JetPsiUtil.getText(whenEntry));
509            }
510    
511            @NotNull
512            public WhenBuilder elseEntry(@NotNull String text) {
513                return entry("else -> " + text);
514            }
515    
516            @NotNull
517            public WhenBuilder elseEntry(@Nullable JetExpression expression) {
518                return elseEntry(JetPsiUtil.getText(expression));
519            }
520    
521            @NotNull
522            public JetWhenExpression toExpression(Project project) {
523                if (!frozen) {
524                    sb.append("}");
525                    frozen = true;
526                }
527                return (JetWhenExpression) createExpression(project, sb.toString());
528            }
529        }
530    
531        @NotNull
532        public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
533            JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
534            return func.getBodyExpression();
535        }
536    
537        @NotNull
538        public static JetClassObject createEmptyClassObject(Project project) {
539            JetClass klass = createClass(project, "class foo { class object { } }");
540            return klass.getClassObject();
541        }
542    
543        @NotNull
544        public static JetBlockExpression wrapInABlock(@NotNull JetExpression expression) {
545            if (expression instanceof JetBlockExpression) {
546                return (JetBlockExpression) expression;
547            }
548            return BlockWrapper.create(expression);
549        }
550    
551        private static class BlockWrapper extends JetBlockExpression implements JetPsiUtil.JetExpressionWrapper {
552            private final JetExpression expression;
553    
554            public static BlockWrapper create(@NotNull JetExpression expressionToWrap) {
555                JetNamedFunction function = createFunction(expressionToWrap.getProject(), "fun f() { " + expressionToWrap.getText() + "}");
556                JetBlockExpression block = (JetBlockExpression) function.getBodyExpression();
557                assert block != null;
558                return new BlockWrapper(block, expressionToWrap);
559            }
560    
561            private BlockWrapper(@NotNull JetBlockExpression fakeBlockExpression, @NotNull JetExpression expressionToWrap) {
562                super(fakeBlockExpression.getNode());
563                this.expression = expressionToWrap;
564            }
565    
566            @NotNull
567            @Override
568            public List<JetElement> getStatements() {
569                return Collections.<JetElement>singletonList(expression);
570            }
571    
572            @Override
573            public JetExpression getBaseExpression() {
574                return expression;
575            }
576        }
577    }