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