001    /*
002     * Copyright 2010-2015 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.kotlin.js.patterns;
018    
019    import com.google.common.collect.Lists;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.descriptors.*;
023    import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
024    import org.jetbrains.kotlin.name.Name;
025    import org.jetbrains.kotlin.idea.KotlinLanguage;
026    import org.jetbrains.kotlin.resolve.DescriptorUtils;
027    import org.jetbrains.kotlin.resolve.OverrideResolver;
028    
029    import java.util.Arrays;
030    import java.util.List;
031    
032    public final class PatternBuilder {
033    
034        @NotNull
035        private static final NamePredicate KOTLIN_NAME_PREDICATE = new NamePredicate("kotlin");
036    
037        @NotNull
038        private static final Name KOTLIN_NAME = Name.identifier(KotlinLanguage.NAME.toLowerCase());
039    
040        private PatternBuilder() {
041        }
042    
043        @NotNull
044        public static DescriptorPredicate pattern(@NotNull NamePredicate checker, @NotNull String stringWithPattern) {
045            List<NamePredicate> checkers = Lists.newArrayList(checker);
046            checkers.addAll(parseFqNamesFromString(stringWithPattern));
047            return pattern(checkers, parseArgumentsFromString(stringWithPattern));
048        }
049    
050        @NotNull
051        public static DescriptorPredicate pattern(@NotNull String stringWithPattern, @NotNull NamePredicate checker) {
052            List<NamePredicate> checkers = Lists.newArrayList(parseFqNamesFromString(stringWithPattern));
053            checkers.add(checker);
054            return pattern(checkers);
055        }
056    
057        @NotNull
058        public static DescriptorPredicate pattern(@NotNull String stringWithPattern) {
059            return pattern(parseFqNamesFromString(stringWithPattern), parseArgumentsFromString(stringWithPattern));
060        }
061    
062        @NotNull
063        private static List<NamePredicate> parseFqNamesFromString(@NotNull String stringWithPattern) {
064            stringWithPattern = getNamePatternFromString(stringWithPattern);
065            String[] subPatterns = stringWithPattern.split("\\.");
066            List<NamePredicate> checkers = Lists.newArrayList();
067            for (String subPattern : subPatterns) {
068                String[] validNames = subPattern.split("\\|");
069                checkers.add(new NamePredicate(validNames));
070            }
071            return checkers;
072        }
073    
074        @Nullable
075        private static List<NamePredicate> parseArgumentsFromString(@NotNull String stringWithPattern) {
076            stringWithPattern = getArgumentsPatternFromString(stringWithPattern);
077            if (stringWithPattern == null) return null;
078    
079            List<NamePredicate> checkers = Lists.newArrayList();
080            if (stringWithPattern.isEmpty()) {
081                return checkers;
082            }
083    
084            String[] subPatterns = stringWithPattern.split("\\,");
085            for (String subPattern : subPatterns) {
086                String[] validNames = subPattern.split("\\|");
087                checkers.add(new NamePredicate(validNames));
088            }
089            return checkers;
090        }
091    
092        @NotNull
093        private static String getNamePatternFromString(@NotNull String stringWithPattern) {
094            int left = stringWithPattern.indexOf("(");
095            if (left < 0) {
096                return stringWithPattern;
097            }
098            else {
099                return stringWithPattern.substring(0, left);
100            }
101        }
102    
103        @Nullable
104        private static String getArgumentsPatternFromString(@NotNull String stringWithPattern) {
105            int left = stringWithPattern.indexOf("(");
106            if (left < 0) {
107                return null;
108            }
109            else {
110                int right = stringWithPattern.indexOf(")");
111                assert right == stringWithPattern.length() - 1 : "expected ')' at the end: " + stringWithPattern;
112                return stringWithPattern.substring(left + 1, right);
113            }
114        }
115    
116        @NotNull
117        private static DescriptorPredicate pattern(@NotNull List<NamePredicate> checkers) {
118            return pattern(checkers, null);
119        }
120    
121        @NotNull
122        private static DescriptorPredicate pattern(@NotNull List<NamePredicate> checkers, @Nullable List<NamePredicate> arguments) {
123            assert !checkers.isEmpty();
124            final List<NamePredicate> checkersWithPrefixChecker = Lists.newArrayList();
125            if (!checkers.get(0).apply(KOTLIN_NAME)) {
126                checkersWithPrefixChecker.add(KOTLIN_NAME_PREDICATE);
127            }
128    
129            checkersWithPrefixChecker.addAll(checkers);
130    
131            assert checkersWithPrefixChecker.size() > 1;
132    
133            final List<NamePredicate> argumentCheckers = arguments != null ? Lists.newArrayList(arguments) : null;
134    
135            return new DescriptorPredicate() {
136                @Override
137                public boolean apply(@Nullable FunctionDescriptor descriptor) {
138                    assert descriptor != null : "argument for DescriptorPredicate.apply should not be null, checkers=" + checkersWithPrefixChecker;
139                    //TODO: no need to wrap if we check beforehand
140                    try {
141                        return doApply(descriptor);
142                    }
143                    catch (IllegalArgumentException e) {
144                        return false;
145                    }
146                }
147    
148                private boolean doApply(@NotNull FunctionDescriptor descriptor) {
149                    List<Name> nameParts = DescriptorUtils.getFqName(descriptor).pathSegments();
150                    if (nameParts.size() != checkersWithPrefixChecker.size()) return false;
151    
152                    return allNamePartsValid(nameParts) && checkAllArgumentsValidIfNeeded(descriptor);
153                }
154    
155                private boolean checkAllArgumentsValidIfNeeded(@NotNull FunctionDescriptor descriptor) {
156                    if (argumentCheckers != null) {
157                        List<ValueParameterDescriptor> valueParameterDescriptors = descriptor.getValueParameters();
158                        if (valueParameterDescriptors.size() != argumentCheckers.size()) {
159                            return false;
160                        }
161                        for (int i = 0; i < valueParameterDescriptors.size(); i++) {
162                            ValueParameterDescriptor valueParameterDescriptor = valueParameterDescriptors.get(i);
163                            Name name = DescriptorUtilsKt.getNameIfStandardType(valueParameterDescriptor.getType());
164                            NamePredicate namePredicate = argumentCheckers.get(i);
165                            if (!namePredicate.apply(name)) return false;
166                        }
167                    }
168                    return true;
169                }
170    
171                private boolean allNamePartsValid(@NotNull List<Name> nameParts) {
172                    for (int i = 0; i < nameParts.size(); ++i) {
173                        Name namePart = nameParts.get(i);
174                        NamePredicate correspondingPredicate = checkersWithPrefixChecker.get(i);
175                        if (!correspondingPredicate.apply(namePart)) {
176                            return false;
177                        }
178                    }
179                    return true;
180                }
181            };
182        }
183    
184        @NotNull
185        public static DescriptorPredicate pattern(@NotNull NamePredicate... checkers) {
186            return pattern(Arrays.asList(checkers));
187        }
188    
189        @NotNull
190        public static DescriptorPredicateImpl pattern(@NotNull String... names) {
191            return new DescriptorPredicateImpl(names);
192        }
193    
194        public static class DescriptorPredicateImpl implements DescriptorPredicate {
195            private final String[] names;
196    
197            private String receiverFqName;
198    
199            private boolean checkOverridden;
200    
201            public DescriptorPredicateImpl(String... names) {
202                this.names = names;
203            }
204    
205            public DescriptorPredicateImpl isExtensionOf(String receiverFqName) {
206                this.receiverFqName = receiverFqName;
207                return this;
208            }
209    
210            public DescriptorPredicateImpl checkOverridden() {
211                this.checkOverridden = true;
212                return this;
213            }
214    
215            private boolean matches(@NotNull CallableDescriptor callable) {
216                DeclarationDescriptor descriptor = callable;
217                int nameIndex = names.length - 1;
218                while (true) {
219                    if (nameIndex == -1) {
220                        return false;
221                    }
222    
223                    if (!descriptor.getName().asString().equals(names[nameIndex])) {
224                        return false;
225                    }
226    
227                    nameIndex--;
228                    descriptor = descriptor.getContainingDeclaration();
229                    if (descriptor instanceof PackageFragmentDescriptor) {
230                        return nameIndex == 0 && names[0].equals(((PackageFragmentDescriptor) descriptor).getFqName().asString());
231                    }
232                }
233            }
234    
235            @Override
236            public boolean apply(@Nullable FunctionDescriptor functionDescriptor) {
237                assert functionDescriptor != null :
238                        "argument for DescriptorPredicate.apply should not be null, receiverFqName=" + receiverFqName + " names=" + Arrays.asList(names);
239                ReceiverParameterDescriptor actualReceiver = functionDescriptor.getExtensionReceiverParameter();
240                if (actualReceiver != null) {
241                    if (receiverFqName == null) return false;
242    
243                    String actualReceiverFqName = DescriptorUtilsKt.getJetTypeFqName(actualReceiver.getType(), false);
244    
245                    if (!actualReceiverFqName.equals(receiverFqName)) return false;
246                }
247    
248                if (!(functionDescriptor.getContainingDeclaration() instanceof ClassDescriptor)) {
249                    return matches(functionDescriptor);
250                }
251    
252                for (CallableMemberDescriptor real : OverrideResolver.getOverriddenDeclarations(functionDescriptor)) {
253                    if (matches(real)) {
254                        return true;
255                    }
256                }
257    
258                if (checkOverridden) {
259                    for (CallableDescriptor overridden : DescriptorUtils.getAllOverriddenDescriptors(functionDescriptor)) {
260                        if (matches(overridden)) {
261                            return true;
262                        }
263                    }
264                }
265    
266                return false;
267            }
268        }
269    }