blob: 473bd5010f79b86917f16ecd1f2230762be48ddd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.axiom.weaver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.axiom.weaver.classio.ClassFetcher;
import org.apache.axiom.weaver.mixin.ClassDefinition;
import org.apache.axiom.weaver.mixin.Mixin;
import org.apache.axiom.weaver.mixin.WeavingContext;
import org.apache.axiom.weaver.mixin.clazz.MixinFactory;
import org.apache.axiom.weaver.mixin.factory.FactoryMixinFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.github.veithen.jrel.association.MutableReferences;
import com.github.veithen.jrel.collection.LinkedIdentityHashSet;
public final class Weaver {
private static final Log log = LogFactory.getLog(Weaver.class);
private final ClassFetcher classFetcher;
private final ImplementationClassNameMapper implementationClassNameMapper;
private final Map<Class<?>, InterfaceNode> interfaceNodes = new HashMap<>();
private final Map<Class<?>, ImplementationNode> implementationNodes = new HashMap<>();
private final Map<Class<?>, Set<Mixin>> mixinsByInterface = new HashMap<>();
private final MutableReferences<ImplementationNode> nodes =
Relations.WEAVER.getConverse().newReferenceHolder(this);
private int nextId = 1;
public Weaver(
ClassLoader classLoader, ImplementationClassNameMapper implementationClassNameMapper) {
classFetcher = new ClassFetcherImpl(classLoader);
this.implementationClassNameMapper = implementationClassNameMapper;
}
public void loadWeavablePackage(String packageName) {
for (Mixin mixin : MixinFactory.loadMixins(classFetcher, packageName)) {
addMixin(mixin);
}
}
private void addMixin(Mixin mixin) {
mixinsByInterface
.computeIfAbsent(mixin.getTargetInterface(), k -> new LinkedIdentityHashSet<>())
.add(mixin);
}
private InterfaceNode addInterface(Class<?> iface) {
InterfaceNode interfaceNode = interfaceNodes.get(iface);
if (interfaceNode == null) {
FactoryMixinFactory.createFactoryMixin(classFetcher, iface).ifPresent(this::addMixin);
Set<InterfaceNode> parentInterfaces = new LinkedIdentityHashSet<>();
Set<ImplementationNode> parentImplementations = new LinkedIdentityHashSet<>();
for (Class<?> superClass : iface.getInterfaces()) {
InterfaceNode parentInterface = addInterface(superClass);
parentInterfaces.add(parentInterface);
parentImplementations.addAll(parentInterface.getImplementations());
}
InterfaceClassVisitor cv = new InterfaceClassVisitor();
classFetcher.fetch(iface.getName(), cv);
interfaceNode = new InterfaceNode(iface, parentInterfaces, cv.isSingleton());
interfaceNodes.put(iface, interfaceNode);
Set<MixinNode> mixinNodes = new LinkedIdentityHashSet<>();
Set<Mixin> mixins = mixinsByInterface.get(iface);
if (mixins != null) {
for (Mixin mixin : mixins) {
mixinNodes.add(new MixinNode(mixin, interfaceNode));
}
}
ImplementationNode implementationNode =
new ImplementationNode(
nextId++,
parentImplementations,
interfaceNode,
mixinNodes,
// The class name is evaluated lazily so that mapper only needs to
// produce values for classes that are actually generated.
() ->
implementationClassNameMapper
.getImplementationClassName(iface)
.replace('.', '/'));
implementationNodes.put(iface, implementationNode);
nodes.add(implementationNode);
}
return interfaceNode;
}
public void addInterfaceToImplement(Class<?> iface) {
addInterface(iface).getImplementations().forEach(ImplementationNode::requireImplementation);
}
private void compact() {
while (true) {
boolean updated = false;
for (ImplementationNode node : nodes) {
updated |= node.compact();
}
if (!updated) {
break;
}
}
}
private void promoteCommonMixins() {
while (true) {
boolean updated = false;
for (ImplementationNode node : nodes) {
updated |= node.promoteCommonMixins();
}
if (!updated) {
break;
}
}
}
private void dump(String prefix, boolean showImplName) {
if (log.isDebugEnabled()) {
StringBuilder builder = new StringBuilder(prefix);
builder.append("digraph G {\n rankdir = BT;\n node [shape=box];\n");
for (ImplementationNode node : nodes) {
node.dump(builder, showImplName);
}
builder.append("}");
log.debug(builder.toString());
}
}
public ClassDefinition[] generate() {
dump("Initial graph:\n", false);
nodes.forEach(ImplementationNode::removeIfNotRequired);
dump("Graph after removing nodes that are not required:\n", false);
Map<Set<ImplementationNode>, Set<ImplementationNode>> mergableNodes = new HashMap<>();
for (ImplementationNode node : nodes) {
mergableNodes
.computeIfAbsent(
node.getRequiredDescendantsOrSelf(), k -> new LinkedIdentityHashSet<>())
.add(node);
}
for (Set<ImplementationNode> nodes : mergableNodes.values()) {
if (nodes.size() > 1) {
ImplementationNode.merge(nodes);
}
}
dump("Graph after merging nodes:\n", false);
nodes.forEach(ImplementationNode::reduce);
dump("Graph after applying transitive reduction:\n", false);
// compact();
// dump("Graph after compaction:\n", false);
nodes.forEach(ImplementationNode::ensureSingleParent);
dump("Graph after removing multiple inheritance:\n", false);
// TODO: do this in a loop until there are no more updates
compact();
promoteCommonMixins();
compact();
dump("Final graph:\n", true);
Map<Class<?>, String> implementationClassNames = new HashMap<>();
interfaceNodes
.values()
.forEach(
interfaceNode -> {
Set<ImplementationNode> implementationNodes =
interfaceNode.getImplementations();
if (implementationNodes.size() == 1) {
implementationClassNames.put(
interfaceNode.getInterface(),
implementationNodes.iterator().next().getClassName());
}
});
WeavingContext context = new WeavingContextImpl(implementationClassNames);
List<ClassDefinition> result = new ArrayList<>();
for (ImplementationNode node : nodes) {
result.addAll(node.toClassDefinitions(context));
}
return result.toArray(new ClassDefinition[result.size()]);
}
public ClassLoader toClassLoader(ClassLoader parent) {
return new WeavingClassLoader(parent, generate());
}
}