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.context;
018    
019    import com.intellij.util.Consumer;
020    import com.intellij.util.SmartList;
021    import com.intellij.util.containers.OrderedSet;
022    import gnu.trove.THashSet;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.*;
026    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
027    
028    import java.util.List;
029    import java.util.Set;
030    
031    public final class UsageTracker {
032        @Nullable
033        private final ClassDescriptor trackedClassDescriptor;
034        @NotNull
035        private final MemberDescriptor memberDescriptor;
036    
037        @Nullable
038        private List<UsageTracker> children;
039    
040        private boolean used;
041        @Nullable
042        private Set<CallableDescriptor> capturedVariables;
043        private ClassDescriptor outerClassDescriptor;
044    
045        public UsageTracker(@NotNull MemberDescriptor memberDescriptor, @Nullable UsageTracker parent, @Nullable ClassDescriptor trackedClassDescriptor) {
046            this.memberDescriptor = memberDescriptor;
047            this.trackedClassDescriptor = trackedClassDescriptor;
048            if (parent != null) {
049                parent.addChild(this);
050            }
051        }
052    
053        public boolean isUsed() {
054            return used;
055        }
056    
057        private void addChild(UsageTracker child) {
058            if (children == null) {
059                children = new SmartList<UsageTracker>();
060            }
061            children.add(child);
062        }
063    
064        private void addCapturedMember(CallableDescriptor descriptor) {
065            if (capturedVariables == null) {
066                capturedVariables = new OrderedSet<CallableDescriptor>();
067            }
068            capturedVariables.add(descriptor);
069        }
070    
071        public void triggerUsed(DeclarationDescriptor descriptor) {
072            if ((descriptor instanceof PropertyDescriptor || descriptor instanceof PropertyAccessorDescriptor)) {
073                checkOuterClass(descriptor);
074            }
075            else if (descriptor instanceof VariableDescriptor) {
076                VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor;
077                if ((capturedVariables == null || !capturedVariables.contains(variableDescriptor)) &&
078                    !isAncestor(memberDescriptor, variableDescriptor)) {
079                    addCapturedMember(variableDescriptor);
080                }
081            }
082            else if (descriptor instanceof SimpleFunctionDescriptor) {
083                CallableDescriptor callableDescriptor = (CallableDescriptor) descriptor;
084                if (JsDescriptorUtils.isExtension(callableDescriptor)) {
085                    return;
086                }
087    
088                DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
089                if (containingDeclaration instanceof ClassDescriptor) {
090                    // skip methods defined in class, for example Int::plus
091                    if (outerClassDescriptor == null && (callableDescriptor.getExpectedThisObject() == null || isAncestor(containingDeclaration, memberDescriptor))) {
092                        outerClassDescriptor = (ClassDescriptor) containingDeclaration;
093                    }
094                    return;
095                }
096    
097                // local named function
098                if (!(containingDeclaration instanceof ClassOrNamespaceDescriptor) &&
099                    !isAncestor(memberDescriptor, descriptor)) {
100                    addCapturedMember(callableDescriptor);
101                }
102            }
103            else if (descriptor instanceof ClassDescriptor && trackedClassDescriptor == descriptor) {
104                used = true;
105            }
106        }
107    
108        private void checkOuterClass(DeclarationDescriptor descriptor) {
109            if (outerClassDescriptor == null) {
110                DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
111                if (containingDeclaration instanceof ClassDescriptor) {
112                    outerClassDescriptor = (ClassDescriptor) containingDeclaration;
113                }
114            }
115        }
116    
117        @Nullable
118        public ClassDescriptor getOuterClassDescriptor() {
119            if (outerClassDescriptor != null || children == null) {
120                return outerClassDescriptor;
121            }
122    
123            for (UsageTracker child : children) {
124                ClassDescriptor childOuterClassDescriptor = child.getOuterClassDescriptor();
125                if (childOuterClassDescriptor != null) {
126                    return childOuterClassDescriptor;
127                }
128            }
129    
130            return null;
131        }
132    
133        public void forEachCaptured(Consumer<CallableDescriptor> consumer) {
134            forEachCaptured(consumer, memberDescriptor, children == null ? null : new THashSet<CallableDescriptor>());
135        }
136    
137        private void forEachCaptured(Consumer<CallableDescriptor> consumer, MemberDescriptor requestorDescriptor, @Nullable THashSet<CallableDescriptor> visited) {
138            if (capturedVariables != null) {
139                for (CallableDescriptor callableDescriptor : capturedVariables) {
140                    if (!isAncestor(requestorDescriptor, callableDescriptor) && (visited == null || visited.add(callableDescriptor))) {
141                        consumer.consume(callableDescriptor);
142                    }
143                }
144            }
145            if (children != null) {
146                for (UsageTracker child : children) {
147                    child.forEachCaptured(consumer, requestorDescriptor, visited);
148                }
149            }
150        }
151    
152        public boolean hasCaptured() {
153            if (capturedVariables != null) {
154                assert !capturedVariables.isEmpty();
155                return true;
156            }
157    
158            if (children != null) {
159                for (UsageTracker child : children) {
160                    if (child.hasCaptured()) {
161                        return true;
162                    }
163                }
164            }
165            return false;
166        }
167    
168        // differs from DescriptorUtils - fails if reach NamespaceDescriptor
169        private static boolean isAncestor(
170                @NotNull DeclarationDescriptor ancestor,
171                @NotNull DeclarationDescriptor declarationDescriptor
172        ) {
173            DeclarationDescriptor descriptor = declarationDescriptor.getContainingDeclaration();
174            while (descriptor != null && !(descriptor instanceof NamespaceDescriptor)) {
175                if (ancestor == descriptor) {
176                    return true;
177                }
178                descriptor = descriptor.getContainingDeclaration();
179            }
180            return false;
181        }
182    }