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.jet.codegen.inline;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.asm4.Opcodes;
022    import org.jetbrains.asm4.Type;
023    import org.jetbrains.asm4.tree.FieldInsnNode;
024    import org.jetbrains.jet.codegen.StackValue;
025    
026    import java.util.Collection;
027    import java.util.List;
028    import java.util.Map;
029    
030    public class RegeneratedLambdaFieldRemapper extends FieldRemapper {
031    
032        private final String oldOwnerType;
033    
034        private final String newOwnerType;
035    
036        private final Parameters parameters;
037    
038        private final Map<String, LambdaInfo> recapturedLambdas;
039    
040        public RegeneratedLambdaFieldRemapper(
041                String oldOwnerType,
042                String newOwnerType,
043                Parameters parameters,
044                Map<String, LambdaInfo> recapturedLambdas,
045                FieldRemapper remapper
046        ) {
047            super(oldOwnerType, remapper, parameters);
048            this.oldOwnerType = oldOwnerType;
049            this.newOwnerType = newOwnerType;
050            this.parameters = parameters;
051            this.recapturedLambdas = recapturedLambdas;
052        }
053    
054        @Override
055        public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) {
056            if (canProcess(lambdaInfo.getLambdaClassType().getInternalName())) {
057                List<CapturedParamInfo> captured = parameters.getCaptured();
058                for (CapturedParamInfo originalField : lambdaInfo.getCapturedVars()) {
059                    CapturedParamInfo foundField = null;
060                    for (CapturedParamInfo capturedParamInfo : captured) {
061                        if (capturedParamInfo.getContainingLambdaName().equals(originalField.getContainingLambdaName())) {
062                            if (capturedParamInfo.getFieldName().equals(LambdaTransformer.getNewFieldName(originalField.getFieldName()))) {
063                                foundField = originalField;
064                                break;
065                            }
066                        }
067                    }
068    
069                    if (foundField == null) {
070                        throw new IllegalStateException("Captured parameter should exists in outer context: " + originalField.getFieldName());
071                    }
072    
073                    builder.addCapturedParam(foundField, foundField);
074                }
075            } else {
076                //in case when inlining lambda into another one inside inline function
077                parent.addCapturedFields(lambdaInfo, builder);
078            }
079        }
080    
081        @Override
082        public boolean canProcess(@NotNull String fieldOwner) {
083            return super.canProcess(fieldOwner) || isRecapturedLambdaType(fieldOwner);
084        }
085    
086        private boolean isRecapturedLambdaType(String owner) {
087            return recapturedLambdas.containsKey(owner);
088        }
089    
090        @Nullable
091        @Override
092        public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection<CapturedParamInfo> captured) {
093            boolean searchInParent = !canProcess(fieldInsnNode.owner);
094            if (searchInParent) {
095                return parent.findField(fieldInsnNode);
096            } else if (isRecapturedLambdaType(fieldInsnNode.owner)) {
097                LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner);
098                return super.findField(fieldInsnNode, info.getCapturedVars());
099            }
100            else {
101                return super.findField(fieldInsnNode, captured);
102            }
103        }
104    
105        @Nullable
106        public CapturedParamInfo findFieldInMyCaptured(@NotNull FieldInsnNode fieldInsnNode) {
107            if (isRecapturedLambdaType(fieldInsnNode.owner)) {
108                LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner);
109                return super.findField(fieldInsnNode, info.getCapturedVars());
110            }
111            else {
112                return super.findField(fieldInsnNode, parameters.getCaptured());
113            }
114        }
115    
116        @Nullable
117        @Override
118        public StackValue getFieldForInline(@NotNull FieldInsnNode node, @Nullable StackValue prefix) {
119            assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
120            FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
121            CapturedParamInfo field = findFieldInMyCaptured(fin);
122    
123            boolean searchInParent = false;
124            if (field == null) {
125                field = findFieldInMyCaptured(new FieldInsnNode(Opcodes.GETSTATIC, oldOwnerType, "this$0", Type.getObjectType(parent.getLambdaInternalName()).getDescriptor()));
126                searchInParent = true;
127                if (field == null) {
128                    throw new IllegalStateException("Could find captured this " + getLambdaInternalName());
129                }
130            }
131    
132            String newName = field.getContainingLambdaName().equals(getLambdaInternalName())
133                             ? field.getFieldName()
134                             : LambdaTransformer.getNewFieldName(field.getFieldName());
135            StackValue result =
136                    StackValue.composed(prefix == null ? StackValue.local(0, Type.getObjectType(getLambdaInternalName())) : prefix,
137                                        StackValue.field(field.getType(),
138                                                         Type.getObjectType(newOwnerType), /*TODO owner type*/
139                                                         newName, false)
140            );
141    
142            return searchInParent ? parent.getFieldForInline(node, result) : result;
143        }
144    }