KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > compiler > ast > FieldReference


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.internal.compiler.ast;
12
13 import org.eclipse.jdt.internal.compiler.ASTVisitor;
14 import org.eclipse.jdt.internal.compiler.impl.*;
15 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
16 import org.eclipse.jdt.internal.compiler.codegen.*;
17 import org.eclipse.jdt.internal.compiler.flow.*;
18 import org.eclipse.jdt.internal.compiler.lookup.*;
19
20 public class FieldReference extends Reference implements InvocationSite {
21
22     public static final int READ = 0;
23     public static final int WRITE = 1;
24     public Expression receiver;
25     public char[] token;
26     public FieldBinding binding; // exact binding resulting from lookup
27
protected FieldBinding codegenBinding; // actual binding used for code generation (if no synthetic accessor)
28
public MethodBinding[] syntheticAccessors; // [0]=read accessor [1]=write accessor
29

30     public long nameSourcePosition; //(start<<32)+end
31
public TypeBinding receiverType;
32     public TypeBinding genericCast;
33     
34 public FieldReference(char[] source, long pos) {
35     token = source;
36     nameSourcePosition = pos;
37     //by default the position are the one of the field (not true for super access)
38
sourceStart = (int) (pos >>> 32);
39     sourceEnd = (int) (pos & 0x00000000FFFFFFFFL);
40     bits |= Binding.FIELD;
41
42 }
43
44 public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, Assignment assignment, boolean isCompound) {
45     // compound assignment extra work
46
if (isCompound) { // check the variable part is initialized if blank final
47
if (binding.isBlankFinal()
48             && receiver.isThis()
49             && currentScope.allowBlankFinalFieldAssignment(binding)
50             && (!flowInfo.isDefinitelyAssigned(binding))) {
51             currentScope.problemReporter().uninitializedBlankFinalField(binding, this);
52             // we could improve error msg here telling "cannot use compound assignment on final blank field"
53
}
54         manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
55     }
56     flowInfo =
57         receiver
58             .analyseCode(currentScope, flowContext, flowInfo, !binding.isStatic())
59             .unconditionalInits();
60     if (assignment.expression != null) {
61         flowInfo =
62             assignment
63                 .expression
64                 .analyseCode(currentScope, flowContext, flowInfo)
65                 .unconditionalInits();
66     }
67     manageSyntheticAccessIfNecessary(currentScope, flowInfo, false /*write-access*/);
68
69     // check if assigning a final field
70
if (binding.isFinal()) {
71         // in a context where it can be assigned?
72
if (binding.isBlankFinal()
73             && !isCompound
74             && receiver.isThis()
75             && !(receiver instanceof QualifiedThisReference)
76             && ((receiver.bits & ParenthesizedMASK) == 0) // (this).x is forbidden
77
&& currentScope.allowBlankFinalFieldAssignment(binding)) {
78             if (flowInfo.isPotentiallyAssigned(binding)) {
79                 currentScope.problemReporter().duplicateInitializationOfBlankFinalField(
80                     binding,
81                     this);
82             } else {
83                 flowContext.recordSettingFinal(binding, this, flowInfo);
84             }
85             flowInfo.markAsDefinitelyAssigned(binding);
86         } else {
87             // assigning a final field outside an initializer or constructor or wrong reference
88
currentScope.problemReporter().cannotAssignToFinalField(binding, this);
89         }
90     }
91     return flowInfo;
92 }
93
94 public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
95     return analyseCode(currentScope, flowContext, flowInfo, true);
96 }
97
98 public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) {
99     boolean nonStatic = !binding.isStatic();
100     receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic);
101     if (nonStatic) {
102         receiver.checkNPE(currentScope, flowContext, flowInfo);
103     }
104     
105     if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) {
106         manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
107     }
108     return flowInfo;
109 }
110
111 /**
112  * @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.jdt.internal.compiler.lookup.TypeBinding)
113  */

114 public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) {
115     if (runtimeTimeType == null || compileTimeType == null)
116         return;
117     // set the generic cast after the fact, once the type expectation is fully known (no need for strict cast)
118
if (this.binding != null && this.binding.isValidBinding()) {
119         FieldBinding originalBinding = this.binding.original();
120         TypeBinding originalType = originalBinding.type;
121         // extra cast needed if method return type is type variable
122
if (originalBinding != this.binding
123                 && originalType != this.binding.type
124                 && runtimeTimeType.id != T_JavaLangObject
125                 && (originalType.tagBits & TagBits.HasTypeVariable) != 0) {
126             TypeBinding targetType = (!compileTimeType.isBaseType() && runtimeTimeType.isBaseType())
127                 ? compileTimeType // unboxing: checkcast before conversion
128
: runtimeTimeType;
129             this.genericCast = originalBinding.type.genericCast(targetType);
130         }
131     }
132     super.computeConversion(scope, runtimeTimeType, compileTimeType);
133 }
134
135 public FieldBinding fieldBinding() {
136     return binding;
137 }
138
139 public void generateAssignment(BlockScope currentScope, CodeStream codeStream, Assignment assignment, boolean valueRequired) {
140     int pc = codeStream.position;
141     receiver.generateCode(
142         currentScope,
143         codeStream,
144         !this.codegenBinding.isStatic());
145     codeStream.recordPositionsFrom(pc, this.sourceStart);
146     assignment.expression.generateCode(currentScope, codeStream, true);
147     fieldStore(
148         codeStream,
149         this.codegenBinding,
150         syntheticAccessors == null ? null : syntheticAccessors[WRITE],
151         valueRequired);
152     if (valueRequired) {
153         codeStream.generateImplicitConversion(assignment.implicitConversion);
154     }
155     // no need for generic cast as value got dupped
156
}
157
158 /**
159  * Field reference code generation
160  *
161  * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
162  * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
163  * @param valueRequired boolean
164  */

165 public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {
166     int pc = codeStream.position;
167     if (constant != Constant.NotAConstant) {
168         if (valueRequired) {
169             codeStream.generateConstant(constant, implicitConversion);
170         }
171         codeStream.recordPositionsFrom(pc, this.sourceStart);
172         return;
173     }
174     boolean isStatic = this.codegenBinding.isStatic();
175     boolean isThisReceiver = this.receiver instanceof ThisReference;
176     Constant fieldConstant = this.codegenBinding.constant();
177     if (fieldConstant != Constant.NotAConstant) {
178         if (!isThisReceiver) {
179             receiver.generateCode(currentScope, codeStream, !isStatic);
180             if (!isStatic){
181                 codeStream.invokeObjectGetClass();
182                 codeStream.pop();
183             }
184         }
185         if (valueRequired) {
186             codeStream.generateConstant(fieldConstant, implicitConversion);
187         }
188         codeStream.recordPositionsFrom(pc, this.sourceStart);
189         return;
190     }
191     if (valueRequired
192             || (!isThisReceiver && currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4)
193             || ((implicitConversion & TypeIds.UNBOXING) != 0)
194             || (this.genericCast != null)) {
195         receiver.generateCode(currentScope, codeStream, !isStatic);
196         pc = codeStream.position;
197         if (this.codegenBinding.declaringClass == null) { // array length
198
codeStream.arraylength();
199             if (valueRequired) {
200                 codeStream.generateImplicitConversion(implicitConversion);
201             } else {
202                 // could occur if !valueRequired but compliance >= 1.4
203
codeStream.pop();
204             }
205         } else {
206             if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
207                 if (isStatic) {
208                     codeStream.getstatic(this.codegenBinding);
209                 } else {
210                     codeStream.getfield(this.codegenBinding);
211                 }
212             } else {
213                 codeStream.invokestatic(syntheticAccessors[READ]);
214             }
215             // required cast must occur even if no value is required
216
if (this.genericCast != null) codeStream.checkcast(this.genericCast);
217             if (valueRequired) {
218                 codeStream.generateImplicitConversion(implicitConversion);
219             } else {
220                 boolean isUnboxing = (implicitConversion & TypeIds.UNBOXING) != 0;
221                 // conversion only generated if unboxing
222
if (isUnboxing) codeStream.generateImplicitConversion(implicitConversion);
223                 switch (isUnboxing ? postConversionType(currentScope).id : this.codegenBinding.type.id) {
224                     case T_long :
225                     case T_double :
226                         codeStream.pop2();
227                         break;
228                     default :
229                         codeStream.pop();
230                 }
231             }
232         }
233     } else {
234         if (isThisReceiver) {
235             if (isStatic){
236                 // if no valueRequired, still need possible side-effects of <clinit> invocation, if field belongs to different class
237
if (this.binding.original().declaringClass != this.receiverType.erasure()) {
238                     MethodBinding accessor = syntheticAccessors == null ? null : syntheticAccessors[READ];
239                     if (accessor == null) {
240                         codeStream.getstatic(this.codegenBinding);
241                     } else {
242                         codeStream.invokestatic(accessor);
243                     }
244                     switch (this.codegenBinding.type.id) {
245                         case T_long :
246                         case T_double :
247                             codeStream.pop2();
248                             break;
249                         default :
250                             codeStream.pop();
251                     }
252                 }
253             }
254         } else {
255             receiver.generateCode(currentScope, codeStream, !isStatic);
256             if (!isStatic){
257                 codeStream.invokeObjectGetClass(); // perform null check
258
codeStream.pop();
259             }
260         }
261     }
262     codeStream.recordPositionsFrom(pc, this.sourceEnd);
263 }
264
265 public void generateCompoundAssignment(BlockScope currentScope, CodeStream codeStream, Expression expression, int operator, int assignmentImplicitConversion, boolean valueRequired) {
266     boolean isStatic;
267     receiver.generateCode(
268         currentScope,
269         codeStream,
270         !(isStatic = this.codegenBinding.isStatic()));
271     if (isStatic) {
272         if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
273             codeStream.getstatic(this.codegenBinding);
274         } else {
275             codeStream.invokestatic(syntheticAccessors[READ]);
276         }
277     } else {
278         codeStream.dup();
279         if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
280             codeStream.getfield(this.codegenBinding);
281         } else {
282             codeStream.invokestatic(syntheticAccessors[READ]);
283         }
284     }
285     int operationTypeID;
286     switch(operationTypeID = (implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) {
287         case T_JavaLangString :
288         case T_JavaLangObject :
289         case T_undefined :
290             codeStream.generateStringConcatenationAppend(currentScope, null, expression);
291             break;
292         default :
293             if (this.genericCast != null)
294                 codeStream.checkcast(this.genericCast);
295             // promote the array reference to the suitable operation type
296
codeStream.generateImplicitConversion(implicitConversion);
297             // generate the increment value (will by itself be promoted to the operation value)
298
if (expression == IntLiteral.One) { // prefix operation
299
codeStream.generateConstant(expression.constant, implicitConversion);
300             } else {
301                 expression.generateCode(currentScope, codeStream, true);
302             }
303             // perform the operation
304
codeStream.sendOperator(operator, operationTypeID);
305             // cast the value back to the array reference type
306
codeStream.generateImplicitConversion(assignmentImplicitConversion);
307     }
308     fieldStore(
309         codeStream,
310         this.codegenBinding,
311         syntheticAccessors == null ? null : syntheticAccessors[WRITE],
312         valueRequired);
313     // no need for generic cast as value got dupped
314
}
315
316 public void generatePostIncrement(BlockScope currentScope, CodeStream codeStream, CompoundAssignment postIncrement, boolean valueRequired) {
317     boolean isStatic;
318     receiver.generateCode(
319         currentScope,
320         codeStream,
321         !(isStatic = this.codegenBinding.isStatic()));
322     if (isStatic) {
323         if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
324             codeStream.getstatic(this.codegenBinding);
325         } else {
326             codeStream.invokestatic(syntheticAccessors[READ]);
327         }
328     } else {
329         codeStream.dup();
330         if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
331             codeStream.getfield(this.codegenBinding);
332         } else {
333             codeStream.invokestatic(syntheticAccessors[READ]);
334         }
335     }
336     if (valueRequired) {
337         if (isStatic) {
338             if ((this.codegenBinding.type == TypeBinding.LONG)
339                 || (this.codegenBinding.type == TypeBinding.DOUBLE)) {
340                 codeStream.dup2();
341             } else {
342                 codeStream.dup();
343             }
344         } else { // Stack: [owner][old field value] ---> [old field value][owner][old field value]
345
if ((this.codegenBinding.type == TypeBinding.LONG)
346                 || (this.codegenBinding.type == TypeBinding.DOUBLE)) {
347                 codeStream.dup2_x1();
348             } else {
349                 codeStream.dup_x1();
350             }
351         }
352     }
353     if (this.genericCast != null)
354         codeStream.checkcast(this.genericCast);
355     codeStream.generateImplicitConversion(this.implicitConversion);
356     codeStream.generateConstant(
357         postIncrement.expression.constant,
358         this.implicitConversion);
359     codeStream.sendOperator(postIncrement.operator, this.implicitConversion & COMPILE_TYPE_MASK);
360     codeStream.generateImplicitConversion(
361         postIncrement.preAssignImplicitConversion);
362     fieldStore(codeStream, this.codegenBinding, syntheticAccessors == null ? null : syntheticAccessors[WRITE], false);
363 }
364
365 /**
366  * @see org.eclipse.jdt.internal.compiler.lookup.InvocationSite#genericTypeArguments()
367  */

368 public TypeBinding[] genericTypeArguments() {
369     return null;
370 }
371 public boolean isSuperAccess() {
372     return receiver.isSuper();
373 }
374
375 public boolean isTypeAccess() {
376     return receiver != null && receiver.isTypeReference();
377 }
378
379 /*
380  * No need to emulate access to protected fields since not implicitly accessed
381  */

382 public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo, boolean isReadAccess) {
383     if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) != 0) return;
384
385     // if field from parameterized type got found, use the original field at codegen time
386
this.codegenBinding = this.binding.original();
387     
388     if (binding.isPrivate()) {
389         if ((currentScope.enclosingSourceType() != this.codegenBinding.declaringClass)
390                 && binding.constant() == Constant.NotAConstant) {
391             if (syntheticAccessors == null)
392                 syntheticAccessors = new MethodBinding[2];
393             syntheticAccessors[isReadAccess ? READ : WRITE] =
394                 ((SourceTypeBinding) this.codegenBinding.declaringClass).addSyntheticMethod(this.codegenBinding, isReadAccess);
395             currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
396             return;
397         }
398
399     } else if (receiver instanceof QualifiedSuperReference) { // qualified super
400

401         // qualified super need emulation always
402
SourceTypeBinding destinationType =
403             (SourceTypeBinding) (((QualifiedSuperReference) receiver)
404                 .currentCompatibleType);
405         if (syntheticAccessors == null)
406             syntheticAccessors = new MethodBinding[2];
407         syntheticAccessors[isReadAccess ? READ : WRITE] = destinationType.addSyntheticMethod(this.codegenBinding, isReadAccess);
408         currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
409         return;
410
411     } else if (binding.isProtected()) {
412
413         SourceTypeBinding enclosingSourceType;
414         if (((bits & DepthMASK) != 0)
415             && binding.declaringClass.getPackage()
416                 != (enclosingSourceType = currentScope.enclosingSourceType()).getPackage()) {
417
418             SourceTypeBinding currentCompatibleType =
419                 (SourceTypeBinding) enclosingSourceType.enclosingTypeAt(
420                     (bits & DepthMASK) >> DepthSHIFT);
421             if (syntheticAccessors == null)
422                 syntheticAccessors = new MethodBinding[2];
423             syntheticAccessors[isReadAccess ? READ : WRITE] = currentCompatibleType.addSyntheticMethod(this.codegenBinding, isReadAccess);
424             currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
425             return;
426         }
427     }
428     // if the binding declaring class is not visible, need special action
429
// for runtime compatibility on 1.2 VMs : change the declaring class of the binding
430
// NOTE: from target 1.2 on, field's declaring class is touched if any different from receiver type
431
// and not from Object or implicit static field access.
432
if (this.binding.declaringClass != this.receiverType
433             && !this.receiverType.isArrayType()
434             && this.binding.declaringClass != null // array.length
435
&& this.binding.constant() == Constant.NotAConstant) {
436         CompilerOptions options = currentScope.compilerOptions();
437         if ((options.targetJDK >= ClassFileConstants.JDK1_2
438                 && (options.complianceLevel >= ClassFileConstants.JDK1_4 || !(receiver.isImplicitThis() && this.codegenBinding.isStatic()))
439                 && this.binding.declaringClass.id != T_JavaLangObject) // no change for Object fields
440
|| !this.binding.declaringClass.canBeSeenBy(currentScope)) {
441
442             this.codegenBinding =
443                 currentScope.enclosingSourceType().getUpdatedFieldBinding(
444                     this.codegenBinding,
445                     (ReferenceBinding) this.receiverType.erasure());
446         }
447     }
448 }
449
450 public int nullStatus(FlowInfo flowInfo) {
451     return FlowInfo.UNKNOWN;
452 }
453
454 public Constant optimizedBooleanConstant() {
455     switch (this.resolvedType.id) {
456         case T_boolean :
457         case T_JavaLangBoolean :
458             return this.constant != Constant.NotAConstant ? this.constant : this.binding.constant();
459         default :
460             return Constant.NotAConstant;
461     }
462 }
463
464 /**
465  * @see org.eclipse.jdt.internal.compiler.ast.Expression#postConversionType(Scope)
466  */

467 public TypeBinding postConversionType(Scope scope) {
468     TypeBinding convertedType = this.resolvedType;
469     if (this.genericCast != null)
470         convertedType = this.genericCast;
471     int runtimeType = (this.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4;
472     switch (runtimeType) {
473         case T_boolean :
474             convertedType = TypeBinding.BOOLEAN;
475             break;
476         case T_byte :
477             convertedType = TypeBinding.BYTE;
478             break;
479         case T_short :
480             convertedType = TypeBinding.SHORT;
481             break;
482         case T_char :
483             convertedType = TypeBinding.CHAR;
484             break;
485         case T_int :
486             convertedType = TypeBinding.INT;
487             break;
488         case T_float :
489             convertedType = TypeBinding.FLOAT;
490             break;
491         case T_long :
492             convertedType = TypeBinding.LONG;
493             break;
494         case T_double :
495             convertedType = TypeBinding.DOUBLE;
496             break;
497         default :
498     }
499     if ((this.implicitConversion & BOXING) != 0) {
500         convertedType = scope.environment().computeBoxingType(convertedType);
501     }
502     return convertedType;
503 }
504
505 public StringBuffer JavaDoc printExpression(int indent, StringBuffer JavaDoc output) {
506     return receiver.printExpression(0, output).append('.').append(token);
507 }
508
509 public TypeBinding resolveType(BlockScope scope) {
510     // Answer the signature type of the field.
511
// constants are propaged when the field is final
512
// and initialized with a (compile time) constant
513

514     //always ignore receiver cast, since may affect constant pool reference
515
boolean receiverCast = false;
516     if (this.receiver instanceof CastExpression) {
517         this.receiver.bits |= DisableUnnecessaryCastCheck; // will check later on
518
receiverCast = true;
519     }
520     this.receiverType = receiver.resolveType(scope);
521     if (this.receiverType == null) {
522         constant = Constant.NotAConstant;
523         return null;
524     }
525     if (receiverCast) {
526          // due to change of declaring class with receiver type, only identity cast should be notified
527
if (((CastExpression)this.receiver).expression.resolvedType == this.receiverType) {
528                 scope.problemReporter().unnecessaryCast((CastExpression)this.receiver);
529         }
530     }
531     // the case receiverType.isArrayType and token = 'length' is handled by the scope API
532
FieldBinding fieldBinding = this.codegenBinding = this.binding = scope.getField(this.receiverType, token, this);
533     if (!fieldBinding.isValidBinding()) {
534         constant = Constant.NotAConstant;
535         scope.problemReporter().invalidField(this, this.receiverType);
536         return null;
537     }
538     TypeBinding receiverErasure = this.receiverType.erasure();
539     if (receiverErasure instanceof ReferenceBinding) {
540         if (receiverErasure.findSuperTypeWithSameErasure(fieldBinding.declaringClass) == null) {
541             this.receiverType = fieldBinding.declaringClass; // handle indirect inheritance thru variable secondary bound
542
}
543     }
544     this.receiver.computeConversion(scope, this.receiverType, this.receiverType);
545     if (isFieldUseDeprecated(fieldBinding, scope, (this.bits & IsStrictlyAssigned) !=0)) {
546         scope.problemReporter().deprecatedField(fieldBinding, this);
547     }
548     boolean isImplicitThisRcv = receiver.isImplicitThis();
549     constant = isImplicitThisRcv ? fieldBinding.constant() : Constant.NotAConstant;
550     if (fieldBinding.isStatic()) {
551         // static field accessed through receiver? legal but unoptimal (optional warning)
552
if (!(isImplicitThisRcv
553                 || (receiver instanceof NameReference
554                     && (((NameReference) receiver).bits & Binding.TYPE) != 0))) {
555             scope.problemReporter().nonStaticAccessToStaticField(this, fieldBinding);
556         }
557         if (!isImplicitThisRcv
558                 && fieldBinding.declaringClass != receiverType
559                 && fieldBinding.declaringClass.canBeSeenBy(scope)) {
560             scope.problemReporter().indirectAccessToStaticField(this, fieldBinding);
561         }
562     }
563     // perform capture conversion if read access
564
return this.resolvedType =
565         (((this.bits & IsStrictlyAssigned) == 0)
566             ? fieldBinding.type.capture(scope, this.sourceEnd)
567             : fieldBinding.type);
568 }
569
570 public void setActualReceiverType(ReferenceBinding receiverType) {
571     // ignored
572
}
573
574 public void setDepth(int depth) {
575     bits &= ~DepthMASK; // flush previous depth if any
576
if (depth > 0) {
577         bits |= (depth & 0xFF) << DepthSHIFT; // encoded on 8 bits
578
}
579 }
580
581 public void setFieldIndex(int index) {
582     // ignored
583
}
584
585 public void traverse(ASTVisitor visitor, BlockScope scope) {
586     if (visitor.visit(this, scope)) {
587         receiver.traverse(visitor, scope);
588     }
589     visitor.endVisit(this, scope);
590 }
591 }
592
Popular Tags