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 }