/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.internal.element; import com.google.dart.engine.ast.ClassDeclaration; import com.google.dart.engine.ast.Identifier; import com.google.dart.engine.context.AnalysisException; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.ConstructorElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ElementAnnotation; import com.google.dart.engine.element.ElementKind; import com.google.dart.engine.element.ElementVisitor; import com.google.dart.engine.element.FieldElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.element.MethodElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.element.ToolkitObjectElement; import com.google.dart.engine.element.TypeParameterElement; import com.google.dart.engine.type.InterfaceType; import com.google.dart.engine.utilities.general.StringUtilities; import com.google.dart.engine.utilities.translation.DartName; import org.apache.commons.lang3.ArrayUtils; import java.util.ArrayList; import java.util.HashSet; /** * Instances of the class {@code ClassElementImpl} implement a {@code ClassElement}. * * @coverage dart.engine.element */ public class ClassElementImpl extends ElementImpl implements ClassElement { /** * An array containing all of the accessors (getters and setters) contained in this class. */ private PropertyAccessorElement[] accessors = PropertyAccessorElementImpl.EMPTY_ARRAY; /** * An array containing all of the constructors contained in this class. */ private ConstructorElement[] constructors = ConstructorElementImpl.EMPTY_ARRAY; /** * An array containing all of the fields contained in this class. */ private FieldElement[] fields = FieldElementImpl.EMPTY_ARRAY; /** * An array containing all of the mixins that are applied to the class being extended in order to * derive the superclass of this class. */ private InterfaceType[] mixins = InterfaceType.EMPTY_ARRAY; /** * An array containing all of the interfaces that are implemented by this class. */ private InterfaceType[] interfaces = InterfaceType.EMPTY_ARRAY; /** * An array containing all of the methods contained in this class. */ private MethodElement[] methods = MethodElementImpl.EMPTY_ARRAY; /** * The superclass of the class, or {@code null} if the class does not have an explicit superclass. */ private InterfaceType supertype; /** * An array containing all of the toolkit objects attached to this class. */ private ToolkitObjectElement[] toolkitObjects = ToolkitObjectElement.EMPTY_ARRAY; /** * The type defined by the class. */ private InterfaceType type; /** * An array containing all of the type parameters defined for this class. */ private TypeParameterElement[] typeParameters = TypeParameterElementImpl.EMPTY_ARRAY; /** * An empty array of class elements. */ public static final ClassElement[] EMPTY_ARRAY = new ClassElement[0]; /** * Initialize a newly created class element to have the given name. * * @param name the name of this element */ @DartName("forNode") public ClassElementImpl(Identifier name) { super(name); } /** * Initialize a newly created class element to have the given name. * * @param name the name of this element * @param nameOffset the offset of the name of this element in the file that contains the * declaration of this element */ public ClassElementImpl(String name, int nameOffset) { super(name, nameOffset); } @Override public <R> R accept(ElementVisitor<R> visitor) { return visitor.visitClassElement(this); } /** * Set the toolkit specific information objects attached to this class. * * @param toolkitObjects the toolkit objects attached to this class */ public void addToolkitObjects(ToolkitObjectElement toolkitObject) { ((ToolkitObjectElementImpl) toolkitObject).setEnclosingElement(this); toolkitObjects = ArrayUtils.add(toolkitObjects, toolkitObject); } @Override public PropertyAccessorElement[] getAccessors() { return accessors; } @Override public InterfaceType[] getAllSupertypes() { ArrayList<InterfaceType> list = new ArrayList<InterfaceType>(); collectAllSupertypes(list); return list.toArray(new InterfaceType[list.size()]); } @Override public ElementImpl getChild(String identifier) { // // The casts in this method are safe because the set methods would have thrown a CCE if any of // the elements in the arrays were not of the expected types. // for (PropertyAccessorElement accessor : accessors) { if (((PropertyAccessorElementImpl) accessor).getIdentifier().equals(identifier)) { return (PropertyAccessorElementImpl) accessor; } } for (ConstructorElement constructor : constructors) { if (((ConstructorElementImpl) constructor).getIdentifier().equals(identifier)) { return (ConstructorElementImpl) constructor; } } for (FieldElement field : fields) { if (((FieldElementImpl) field).getIdentifier().equals(identifier)) { return (FieldElementImpl) field; } } for (MethodElement method : methods) { if (((MethodElementImpl) method).getIdentifier().equals(identifier)) { return (MethodElementImpl) method; } } for (TypeParameterElement typeParameter : typeParameters) { if (((TypeParameterElementImpl) typeParameter).getIdentifier().equals(identifier)) { return (TypeParameterElementImpl) typeParameter; } } return null; } @Override public ConstructorElement[] getConstructors() { return constructors; } @Override public FieldElement getField(String name) { for (FieldElement fieldElement : fields) { if (name.equals(fieldElement.getName())) { return fieldElement; } } return null; } @Override public FieldElement[] getFields() { return fields; } @Override public PropertyAccessorElement getGetter(String getterName) { for (PropertyAccessorElement accessor : accessors) { if (accessor.isGetter() && accessor.getName().equals(getterName)) { return accessor; } } return null; } @Override public InterfaceType[] getInterfaces() { return interfaces; } @Override public ElementKind getKind() { return ElementKind.CLASS; } @Override public MethodElement getMethod(String methodName) { for (MethodElement method : methods) { if (method.getName().equals(methodName)) { return method; } } return null; } @Override public MethodElement[] getMethods() { return methods; } @Override public InterfaceType[] getMixins() { return mixins; } @Override public ConstructorElement getNamedConstructor(String name) { for (ConstructorElement element : getConstructors()) { String elementName = element.getName(); if (elementName != null && elementName.equals(name)) { return element; } } return null; } @Override public ClassDeclaration getNode() throws AnalysisException { return getNodeMatching(ClassDeclaration.class); } @Override public PropertyAccessorElement getSetter(String setterName) { // TODO (jwren) revisit- should we append '=' here or require clients to include it? // Do we need the check for isSetter below? if (!StringUtilities.endsWithChar(setterName, '=')) { setterName += '='; } for (PropertyAccessorElement accessor : accessors) { if (accessor.isSetter() && accessor.getName().equals(setterName)) { return accessor; } } return null; } @Override public InterfaceType getSupertype() { return supertype; } @Override public ToolkitObjectElement[] getToolkitObjects() { return toolkitObjects; } @Override public InterfaceType getType() { return type; } @Override public TypeParameterElement[] getTypeParameters() { return typeParameters; } @Override public ConstructorElement getUnnamedConstructor() { for (ConstructorElement element : getConstructors()) { String name = element.getDisplayName(); if (name == null || name.isEmpty()) { return element; } } return null; } @Override public boolean hasNonFinalField() { ArrayList<ClassElement> classesToVisit = new ArrayList<ClassElement>(); HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>(); classesToVisit.add(this); while (!classesToVisit.isEmpty()) { ClassElement currentElement = classesToVisit.remove(0); if (visitedClasses.add(currentElement)) { // check fields for (FieldElement field : currentElement.getFields()) { if (!field.isFinal() && !field.isConst() && !field.isStatic() && !field.isSynthetic()) { return true; } } // check mixins for (InterfaceType mixinType : currentElement.getMixins()) { ClassElement mixinElement = mixinType.getElement(); classesToVisit.add(mixinElement); } // check super InterfaceType supertype = currentElement.getSupertype(); if (supertype != null) { ClassElement superElement = supertype.getElement(); if (superElement != null) { classesToVisit.add(superElement); } } } } // not found return false; } @Override public boolean hasReferenceToSuper() { return hasModifier(Modifier.REFERENCES_SUPER); } @Override public boolean hasStaticMember() { for (MethodElement method : methods) { if (method.isStatic()) { return true; } } for (PropertyAccessorElement accessor : accessors) { if (accessor.isStatic()) { return true; } } return false; } @Override public boolean isAbstract() { return hasModifier(Modifier.ABSTRACT); } @Override public boolean isEnum() { return hasModifier(Modifier.ENUM); } @Override public boolean isOrInheritsProxy() { return safeIsOrInheritsProxy(this, new HashSet<ClassElement>()); } @Override public boolean isProxy() { for (ElementAnnotation annotation : getMetadata()) { if (annotation.isProxy()) { return true; } } return false; } @Override public boolean isTypedef() { return hasModifier(Modifier.TYPEDEF); } @Override public boolean isValidMixin() { return hasModifier(Modifier.MIXIN); } @Override public MethodElement lookUpConcreteMethod(String methodName, LibraryElement library) { return internalLookUpConcreteMethod(methodName, library, true); } @Override public PropertyAccessorElement lookUpGetter(String getterName, LibraryElement library) { return internalLookUpGetter(getterName, library, true); } @Override public PropertyAccessorElement lookUpInheritedConcreteGetter(String getterName, LibraryElement library) { return internalLookUpConcreteGetter(getterName, library, false); } @Override public MethodElement lookUpInheritedConcreteMethod(String methodName, LibraryElement library) { return internalLookUpConcreteMethod(methodName, library, false); } @Override public PropertyAccessorElement lookUpInheritedConcreteSetter(String setterName, LibraryElement library) { return internalLookUpConcreteSetter(setterName, library, false); } @Override public MethodElement lookUpInheritedMethod(String methodName, LibraryElement library) { return internalLookUpMethod(methodName, library, false); } @Override public MethodElement lookUpMethod(String methodName, LibraryElement library) { return internalLookUpMethod(methodName, library, true); } @Override public PropertyAccessorElement lookUpSetter(String setterName, LibraryElement library) { return internalLookUpSetter(setterName, library, true); } /** * Set whether this class is abstract to correspond to the given value. * * @param isAbstract {@code true} if the class is abstract */ public void setAbstract(boolean isAbstract) { setModifier(Modifier.ABSTRACT, isAbstract); } /** * Set the accessors contained in this class to the given accessors. * * @param accessors the accessors contained in this class */ public void setAccessors(PropertyAccessorElement[] accessors) { for (PropertyAccessorElement accessor : accessors) { ((PropertyAccessorElementImpl) accessor).setEnclosingElement(this); } this.accessors = accessors; } /** * Set the constructors contained in this class to the given constructors. * * @param constructors the constructors contained in this class */ public void setConstructors(ConstructorElement[] constructors) { for (ConstructorElement constructor : constructors) { ((ConstructorElementImpl) constructor).setEnclosingElement(this); } this.constructors = constructors; } /** * Set whether this class is defined by an enum declaration to correspond to the given value. * * @param isEnum {@code true} if the class is defined by an enum declaration */ public void setEnum(boolean isEnum) { setModifier(Modifier.ENUM, isEnum); } /** * Set the fields contained in this class to the given fields. * * @param fields the fields contained in this class */ public void setFields(FieldElement[] fields) { for (FieldElement field : fields) { ((FieldElementImpl) field).setEnclosingElement(this); } this.fields = fields; } /** * Set whether this class references 'super' to the given value. * * @param isReferencedSuper {@code true} references 'super' */ public void setHasReferenceToSuper(boolean isReferencedSuper) { setModifier(Modifier.REFERENCES_SUPER, isReferencedSuper); } /** * Set the interfaces that are implemented by this class to the given types. * * @param the interfaces that are implemented by this class */ public void setInterfaces(InterfaceType[] interfaces) { this.interfaces = interfaces; } /** * Set the methods contained in this class to the given methods. * * @param methods the methods contained in this class */ public void setMethods(MethodElement[] methods) { for (MethodElement method : methods) { ((MethodElementImpl) method).setEnclosingElement(this); } this.methods = methods; } /** * Set the mixins that are applied to the class being extended in order to derive the superclass * of this class to the given types. * * @param mixins the mixins that are applied to derive the superclass of this class */ public void setMixins(InterfaceType[] mixins) { this.mixins = mixins; } /** * Set the superclass of the class to the given type. * * @param supertype the superclass of the class */ public void setSupertype(InterfaceType supertype) { this.supertype = supertype; } /** * Set the type defined by the class to the given type. * * @param type the type defined by the class */ public void setType(InterfaceType type) { this.type = type; } /** * Set whether this class is defined by a typedef construct to correspond to the given value. * * @param isTypedef {@code true} if the class is defined by a typedef construct */ public void setTypedef(boolean isTypedef) { setModifier(Modifier.TYPEDEF, isTypedef); } /** * Set the type parameters defined for this class to the given type parameters. * * @param typeParameters the type parameters defined for this class */ public void setTypeParameters(TypeParameterElement[] typeParameters) { for (TypeParameterElement typeParameter : typeParameters) { ((TypeParameterElementImpl) typeParameter).setEnclosingElement(this); } this.typeParameters = typeParameters; } /** * Set whether this class is a valid mixin to correspond to the given value. * * @param isValidMixin {@code true} if this class can be used as a mixin */ public void setValidMixin(boolean isValidMixin) { setModifier(Modifier.MIXIN, isValidMixin); } @Override public void visitChildren(ElementVisitor<?> visitor) { super.visitChildren(visitor); safelyVisitChildren(accessors, visitor); safelyVisitChildren(constructors, visitor); safelyVisitChildren(fields, visitor); safelyVisitChildren(methods, visitor); safelyVisitChildren(toolkitObjects, visitor); safelyVisitChildren(typeParameters, visitor); } @Override protected void appendTo(StringBuilder builder) { String name = getDisplayName(); if (name == null) { builder.append("{unnamed class}"); } else { builder.append(name); } int variableCount = typeParameters.length; if (variableCount > 0) { builder.append("<"); for (int i = 0; i < variableCount; i++) { if (i > 0) { builder.append(", "); } ((TypeParameterElementImpl) typeParameters[i]).appendTo(builder); } builder.append(">"); } } private void collectAllSupertypes(ArrayList<InterfaceType> supertypes) { ArrayList<InterfaceType> typesToVisit = new ArrayList<InterfaceType>(); ArrayList<ClassElement> visitedClasses = new ArrayList<ClassElement>(); typesToVisit.add(this.getType()); while (!typesToVisit.isEmpty()) { InterfaceType currentType = typesToVisit.remove(0); ClassElement currentElement = currentType.getElement(); if (!visitedClasses.contains(currentElement)) { visitedClasses.add(currentElement); if (currentType != this.getType()) { supertypes.add(currentType); } InterfaceType supertype = currentType.getSuperclass(); if (supertype != null) { typesToVisit.add(supertype); } for (InterfaceType type : currentElement.getInterfaces()) { typesToVisit.add(type); } for (InterfaceType type : currentElement.getMixins()) { ClassElement element = type.getElement(); if (!visitedClasses.contains(element)) { supertypes.add(type); } } } } } private PropertyAccessorElement internalLookUpConcreteGetter(String getterName, LibraryElement library, boolean includeThisClass) { PropertyAccessorElement getter = internalLookUpGetter(getterName, library, includeThisClass); while (getter != null && getter.isAbstract()) { Element definingClass = getter.getEnclosingElement(); if (!(definingClass instanceof ClassElementImpl)) { return null; } getter = ((ClassElementImpl) definingClass).internalLookUpGetter(getterName, library, false); } return getter; } private MethodElement internalLookUpConcreteMethod(String methodName, LibraryElement library, boolean includeThisClass) { MethodElement method = internalLookUpMethod(methodName, library, includeThisClass); while (method != null && method.isAbstract()) { ClassElement definingClass = method.getEnclosingElement(); if (definingClass == null) { return null; } method = definingClass.lookUpInheritedMethod(methodName, library); } return method; } private PropertyAccessorElement internalLookUpConcreteSetter(String setterName, LibraryElement library, boolean includeThisClass) { PropertyAccessorElement setter = internalLookUpSetter(setterName, library, includeThisClass); while (setter != null && setter.isAbstract()) { Element definingClass = setter.getEnclosingElement(); if (!(definingClass instanceof ClassElementImpl)) { return null; } setter = ((ClassElementImpl) definingClass).internalLookUpSetter(setterName, library, false); } return setter; } private PropertyAccessorElement internalLookUpGetter(String getterName, LibraryElement library, boolean includeThisClass) { HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>(); ClassElement currentElement = this; if (includeThisClass) { PropertyAccessorElement element = currentElement.getGetter(getterName); if (element != null && element.isAccessibleIn(library)) { return element; } } while (currentElement != null && visitedClasses.add(currentElement)) { for (InterfaceType mixin : currentElement.getMixins()) { ClassElement mixinElement = mixin.getElement(); if (mixinElement != null) { PropertyAccessorElement element = mixinElement.getGetter(getterName); if (element != null && element.isAccessibleIn(library)) { return element; } } } InterfaceType supertype = currentElement.getSupertype(); if (supertype == null) { return null; } currentElement = supertype.getElement(); PropertyAccessorElement element = currentElement.getGetter(getterName); if (element != null && element.isAccessibleIn(library)) { return element; } } return null; } private MethodElement internalLookUpMethod(String methodName, LibraryElement library, boolean includeThisClass) { HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>(); ClassElement currentElement = this; if (includeThisClass) { MethodElement element = currentElement.getMethod(methodName); if (element != null && element.isAccessibleIn(library)) { return element; } } while (currentElement != null && visitedClasses.add(currentElement)) { for (InterfaceType mixin : currentElement.getMixins()) { ClassElement mixinElement = mixin.getElement(); if (mixinElement != null) { MethodElement element = mixinElement.getMethod(methodName); if (element != null && element.isAccessibleIn(library)) { return element; } } } InterfaceType supertype = currentElement.getSupertype(); if (supertype == null) { return null; } currentElement = supertype.getElement(); MethodElement element = currentElement.getMethod(methodName); if (element != null && element.isAccessibleIn(library)) { return element; } } return null; } private PropertyAccessorElement internalLookUpSetter(String setterName, LibraryElement library, boolean includeThisClass) { HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>(); ClassElement currentElement = this; if (includeThisClass) { PropertyAccessorElement element = currentElement.getSetter(setterName); if (element != null && element.isAccessibleIn(library)) { return element; } } while (currentElement != null && visitedClasses.add(currentElement)) { for (InterfaceType mixin : currentElement.getMixins()) { ClassElement mixinElement = mixin.getElement(); if (mixinElement != null) { PropertyAccessorElement element = mixinElement.getSetter(setterName); if (element != null && element.isAccessibleIn(library)) { return element; } } } InterfaceType supertype = currentElement.getSupertype(); if (supertype == null) { return null; } currentElement = supertype.getElement(); PropertyAccessorElement element = currentElement.getSetter(setterName); if (element != null && element.isAccessibleIn(library)) { return element; } } return null; } private boolean safeIsOrInheritsProxy(ClassElement classElt, HashSet<ClassElement> visitedClassElts) { if (visitedClassElts.contains(classElt)) { return false; } visitedClassElts.add(classElt); if (classElt.isProxy()) { return true; } else if (classElt.getSupertype() != null && safeIsOrInheritsProxy(classElt.getSupertype().getElement(), visitedClassElts)) { return true; } InterfaceType[] supertypes = classElt.getInterfaces(); for (int i = 0; i < supertypes.length; i++) { if (safeIsOrInheritsProxy(supertypes[i].getElement(), visitedClassElts)) { return true; } } supertypes = classElt.getMixins(); for (int i = 0; i < supertypes.length; i++) { if (safeIsOrInheritsProxy(supertypes[i].getElement(), visitedClassElts)) { return true; } } return false; } }