Java tutorial
/** * Copyright (c) 2016 NumberFour AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * NumberFour AG - Initial API and implementation */ package eu.numberfour.n4js.validation.validators.utils; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.eclipse.emf.ecore.resource.Resource; import com.google.common.collect.Iterables; import eu.numberfour.n4js.n4JS.N4ClassDefinition; import eu.numberfour.n4js.ts.typeRefs.ParameterizedTypeRef; import eu.numberfour.n4js.ts.typeRefs.TypeRef; import eu.numberfour.n4js.ts.types.MemberType; import eu.numberfour.n4js.ts.types.TClass; import eu.numberfour.n4js.ts.types.TClassifier; import eu.numberfour.n4js.ts.types.TInterface; import eu.numberfour.n4js.ts.types.TMember; import eu.numberfour.n4js.ts.types.TMethod; import eu.numberfour.n4js.ts.types.Type; import eu.numberfour.n4js.ts.types.TypesPackage; import eu.numberfour.n4js.ts.types.util.ExtendedClassesIterable; import eu.numberfour.n4js.typesystem.N4JSTypeSystem; import eu.numberfour.n4js.typesystem.RuleEnvironmentExtensions; import eu.numberfour.n4js.utils.N4JSLanguageUtils; import it.xsemantics.runtime.RuleEnvironment; /** * A util class for member redefinition validation. */ public class MemberRedefinitionUtils { /** * Filters the given overridden members to only contain members which are metatype compatible with the overriding * member. */ public static Iterable<TMember> getMetatypeCompatibleOverriddenMembers(TMember overridingMember, Iterable<TMember> overriddenMembers) { return Iterables.filter(overriddenMembers, member -> isMetaTypeCompatible(overridingMember, member)); } /** * Returns {@code true} if the two members are metatype compatible. * * @param m1 * The first member * @param m2 * The second member */ public static boolean isMetaTypeCompatible(TMember m1, TMember m2) { boolean result = m1.getMemberType() == m2.getMemberType(); if (!result) { switch (m1.getMemberType()) { case METHOD: break; // initial test covers this case case FIELD: result = m2.getMemberType() == MemberType.GETTER || m2.getMemberType() == MemberType.SETTER; break; case SETTER: case GETTER: result = m2.getMemberType() == MemberType.FIELD; break; } } return result; } /** * Returns the fitting verb for a redefinition of all of the redefined members by the current classifier. * * @param redefinedMembers * The redefined members * @param redefiningClassifier * The classifier which redefines the given members */ public static String getRedefinitionVerb(Iterable<TMember> redefinedMembers, TClassifier redefiningClassifier) { Set<?> overriddenClassifierTypes = StreamSupport.stream(redefinedMembers.spliterator(), false) .map(member -> member.getContainingType().eClass()).collect(Collectors.toSet()); boolean isOverridingSuperclassMember = overriddenClassifierTypes .contains(TypesPackage.eINSTANCE.getTClass()); boolean isOverridingObjectPrototypeMembers = overriddenClassifierTypes .contains(TypesPackage.eINSTANCE.getTObjectPrototype()); boolean isImplementing = overriddenClassifierTypes.contains(TypesPackage.eINSTANCE.getTInterface()); boolean isOverriding = isOverridingSuperclassMember || isOverridingObjectPrototypeMembers; boolean isInterfaceExtendingInterface = redefiningClassifier instanceof TInterface; if (isOverriding && isImplementing) { return "overriding/implementing"; } else if (isOverriding || isInterfaceExtendingInterface) { return "overriding"; } else if (isImplementing) { return "implementing"; } else { // General case, should not happen return "redefining"; } } /** * Returns the class which is filled by the given static polyfill class definition. * * Returns {@code null} if staticPolyfillDefinition isn't a valid static polyfill class definition. */ public static TClass getFilledClass(N4ClassDefinition staticPolyfillDefinition) { if (!(staticPolyfillDefinition.getDefinedType() instanceof TClassifier)) { return null; } TClassifier classifier = (TClassifier) staticPolyfillDefinition.getDefinedType(); if (!classifier.isStaticPolyfill()) { return null; } ParameterizedTypeRef superClassTypeRef = Iterables.getFirst(classifier.getSuperClassifierRefs(), null); if (null == superClassTypeRef) { return null; } Type superClassType = superClassTypeRef.getDeclaredType(); if (superClassType instanceof TClass && ((TClass) superClassType).getContainingModule().isStaticPolyfillAware()) { return (TClass) superClassType; } else { return null; } } /** * Returns the context type to be used as this binding (see * {@link N4JSTypeSystem#createRuleEnvironmentForContext(TypeRef, TypeRef, Resource) here} and * {@link RuleEnvironmentExtensions#addThisType(RuleEnvironment, TypeRef)} here) for checking constructor * compatibility. * * For example, given the following code * * <pre> * class C1 { * constructor(@Spec spec: ~i~this) {} * } * * class C2 extends C1 { * public field2; * } * * @CovariantConstructor * class C3 extends C2 { * public field3; * } * * class C4 extends C3 { * // XPECT noerrors --> * constructor(@Spec spec: ~i~this) { super(spec); } * } * </pre> * * When checking constructor compatibility in C4, we have to use class C3 as the 'this' context on right-hand side, * not class C1, because the addition of fields <code>field2</code> and <code>field3</code> does not break the * contract of the @CovariantConstructor annotation. Therefore, we cannot simply use the containing type of the * right-hand side constructor as 'this' context. In the above example, we would call this method with C4 as * <code>baseContext</code> and C1's constructor as <code>ctor</code> and it would return C3 as the 'this' context * to use. */ public static final Type findThisContextForConstructor(Type baseContext, TMethod ctor) { final Type ctorContainer = ctor.getContainingType(); if (!(baseContext instanceof TClass) || !(ctorContainer instanceof TClass)) { return ctorContainer; } final TClass ctorContainerCasted = (TClass) ctorContainer; final TClass baseContextCasted = (TClass) baseContext; if (N4JSLanguageUtils.hasCovariantConstructor(ctorContainerCasted)) { return ctorContainerCasted; } final List<TClass> superClassesUpToCtorContainer = new ArrayList<>(); for (TClass superClass : new ExtendedClassesIterable(baseContextCasted)) { if (superClass != ctorContainerCasted) { superClassesUpToCtorContainer.add(superClass); } else { break; } } for (int i = superClassesUpToCtorContainer.size() - 1; i >= 0; i--) { final TClass superClass = superClassesUpToCtorContainer.get(i); if (N4JSLanguageUtils.hasCovariantConstructor(superClass)) { return superClass; } } return baseContext; } /* Non-instantiable util class. */ private MemberRedefinitionUtils() { } }