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