/* * Copyright (C) 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.jboss.errai.ioc.rebind.ioc.extension.builtin; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toCollection; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.ioc.client.api.IOCExtension; import org.jboss.errai.ioc.rebind.ioc.bootstrapper.IOCProcessingContext; import org.jboss.errai.ioc.rebind.ioc.bootstrapper.IOCProcessor; import org.jboss.errai.ioc.rebind.ioc.extension.IOCExtensionConfigurator; import org.jboss.errai.ioc.rebind.ioc.injector.api.ExtensionTypeCallback; import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext; import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import jsinterop.annotations.JsType; /** * This extension tracks non-native {@link JsType} interfaces that are implemented by 0 or 1 classes. Why? Because in * order for use of {@link JsType} instances between scripts to work, the compiler must not prune or inline method calls * to a {@link JsType} interface. * * If the compiler finds only a single implementation of an interface, it will replace all method calls on the interface * with method calls on the implementing class. * * If the compiler finds no implementations of an interface, it will generate method calls on null! * * By generating dummy implementations, we ensure the the compiler is never able to "optimize" calls on {@link JsType} * interfaces in these ways. * * @author Max Barkley <mbarkley@redhat.com> */ @IOCExtension public class JsTypeAntiInliningExtension implements IOCExtensionConfigurator { private static Multiset<MetaClass> requiringDummyImpls = null; public static boolean requiresAntiInliningDummy(final MetaClass type) { return requiringDummyImpls.contains(type); } public static int numberOfRequiredAntiInliningDummies(final MetaClass type) { return requiringDummyImpls.count(type); } @Override public void configure(final IOCProcessingContext context, final InjectionContext injectionContext) { } @Override public void afterInitialization(final IOCProcessingContext context, final InjectionContext injectionContext) { if (IOCProcessor.isJsInteropSupportEnabled()) { injectionContext.registerExtensionTypeCallback(new ExtensionTypeCallback() { final Multimap<MetaClass, MetaClass> jsTypeIfaceImpls = HashMultimap.create(); final Set<MetaClass> jsTypeIfaces = new HashSet<>(); @Override public void init() { requiringDummyImpls = null; } @Override public void callback(final MetaClass type) { if (!type.getFullyQualifiedName().startsWith("java.util")) { if (type.isInterface()) { jsTypeIfaces.add(type); } else if (type.isConcrete() && type.isPublic()) { findJsTypeIfaces(type) .forEach(iface -> { jsTypeIfaces.add(iface); jsTypeIfaceImpls.put(iface, type); }); } } } @Override public void finish() { final Multiset<MetaClass> noOrSingleImplJsTypeIfaces = jsTypeIfaces .stream() .flatMap(iface -> { final Collection<MetaClass> impls = jsTypeIfaceImpls.get(iface); if (impls.isEmpty()) { return stream(new MetaClass[] { iface, iface }); } else if (impls.size() == 1) { return stream(new MetaClass[] { iface }); } else { return stream(new MetaClass[0]); } }) .collect(toCollection(() -> HashMultiset.create())); requiringDummyImpls = noOrSingleImplJsTypeIfaces; } }); } } private Stream<MetaClass> findJsTypeIfaces(final MetaClass type) { return stream(type.getInterfaces()) .flatMap(iface -> stream(iface.getInterfaces())) .distinct() .filter(iface -> !iface.getFullyQualifiedName().startsWith("java.util")) .filter(iface -> iface.isAnnotationPresent(JsType.class) && !iface.getAnnotation(JsType.class).isNative()); } }