blob: 1659dd4e0c6ca1ed6ac8e479445e17b227c97538 [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.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.axiom.weaver.mixin.ClassDefinition;
import org.apache.axiom.weaver.mixin.Mixin;
import org.apache.axiom.weaver.mixin.TargetContext;
import org.apache.axiom.weaver.mixin.WeavingContext;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import com.github.veithen.jrel.References;
import com.github.veithen.jrel.association.ManyToManyAssociation;
import com.github.veithen.jrel.association.MutableReference;
import com.github.veithen.jrel.association.MutableReferences;
import com.github.veithen.jrel.association.Navigability;
import com.github.veithen.jrel.collection.LinkedIdentityHashSet;
import com.github.veithen.jrel.composition.CompositionRelation;
import com.github.veithen.jrel.transitive.TransitiveClosure;
final class ImplementationNode {
private static final ManyToManyAssociation<ImplementationNode, ImplementationNode> PARENT =
new ManyToManyAssociation<>(
ImplementationNode.class, ImplementationNode.class, Navigability.BIDIRECTIONAL);
private static final ManyToManyAssociation<ImplementationNode, MixinNode> MIXIN =
new ManyToManyAssociation<>(
ImplementationNode.class, MixinNode.class, Navigability.UNIDIRECTIONAL);
private static final TransitiveClosure<ImplementationNode> ANCESTOR =
new TransitiveClosure<>(PARENT, false);
private static final TransitiveClosure<ImplementationNode> ANCESTOR_OR_SELF =
new TransitiveClosure<>(PARENT, true);
private static final CompositionRelation<ImplementationNode, ImplementationNode, MixinNode>
TRANSITIVE_MIXIN = new CompositionRelation<>(ANCESTOR_OR_SELF, MIXIN);
private final MutableReference<Weaver> weaver = Relations.WEAVER.newReferenceHolder(this);
private final int id;
private final InterfaceNode primaryInterface;
private final MutableReferences<ImplementationNode> parents = PARENT.newReferenceHolder(this);
private final MutableReferences<ImplementationNode> children =
PARENT.getConverse().newReferenceHolder(this);
private final MutableReferences<InterfaceNode> ifaces =
Relations.IMPLEMENTS.newReferenceHolder(this);
private final MutableReferences<MixinNode> mixins = MIXIN.newReferenceHolder(this);
private final References<ImplementationNode> ancestors = ANCESTOR.newReferenceHolder(this);
private final References<ImplementationNode> ancestorsOrSelf =
ANCESTOR_OR_SELF.newReferenceHolder(this);
private final References<ImplementationNode> descendantsOrSelf =
ANCESTOR_OR_SELF.getConverse().newReferenceHolder(this);
private final References<MixinNode> transitiveMixins =
TRANSITIVE_MIXIN.newReferenceHolder(this);
private final Supplier<String> className;
private boolean requireImplementation;
ImplementationNode(
int id,
Set<ImplementationNode> parents,
InterfaceNode iface,
Set<MixinNode> mixins,
Supplier<String> className) {
this.id = id;
this.primaryInterface = iface;
ifaces.add(iface);
this.mixins.addAll(mixins);
this.parents.addAll(parents);
this.className = className;
}
InterfaceNode getPrimaryInterface() {
return primaryInterface;
}
void requireImplementation() {
requireImplementation = true;
}
String getClassName() {
return className.get();
}
Set<ImplementationNode> getRequiredDescendantsOrSelf() {
Set<ImplementationNode> result = new LinkedIdentityHashSet<>();
for (ImplementationNode node : descendantsOrSelf.asSet()) {
if (node.requireImplementation) {
result.add(node);
}
}
return result;
}
private int getWeight() {
int weight = 0;
for (MixinNode mixin : transitiveMixins) {
weight += mixin.getWeight();
}
return weight;
}
private Set<Class<?>> getInterfaces() {
Set<Class<?>> interfaces = new LinkedHashSet<>();
for (InterfaceNode ifaceNode : ifaces) {
Class<?> iface = ifaceNode.getInterface();
if (interfaces.contains(iface)) {
continue;
}
for (Class<?> i : interfaces) {
if (iface.isAssignableFrom(i)) {
continue;
}
}
for (Iterator<Class<?>> it = interfaces.iterator(); it.hasNext(); ) {
Class<?> i = it.next();
if (i.isAssignableFrom(iface)) {
it.remove();
}
}
interfaces.add(iface);
}
return interfaces;
}
void dump(StringBuilder builder, boolean showImplName) {
builder.append(" n");
builder.append(id);
builder.append(" [label=<");
if (showImplName) {
String implementationClassName = getClassName();
builder.append("<b>");
builder.append(
implementationClassName.substring(
implementationClassName.lastIndexOf('/') + 1));
builder.append("</b><br/>");
}
for (Class<?> iface : getInterfaces()) {
builder.append("<i>");
builder.append(iface.getSimpleName());
builder.append("</i><br/>");
}
for (MixinNode mixin : mixins) {
builder.append(mixin.getName());
builder.append("<br/>");
}
builder.append("[w:");
builder.append(getWeight());
builder.append("]>");
if (!mixins.isEmpty()) {
builder.append(", penwidth=2");
}
if (requireImplementation) {
builder.append(", style=filled");
}
builder.append("];\n");
for (ImplementationNode parent : parents) {
builder.append(" n");
builder.append(id);
builder.append(" -> n");
builder.append(parent.id);
builder.append(";\n");
}
}
static void merge(Set<ImplementationNode> nodes) {
ImplementationNode target = null;
// If the set contains a node requiring implementation, use that as a target (so that the
// implementation class name is predictable). Otherwise use the first node; this will in
// general be the interface the highest up in the hierarchy (because super-interfaces are
// added first).
for (ImplementationNode node : nodes) {
if (target == null) {
target = node;
}
if (node.requireImplementation) {
target = node;
break;
}
}
for (ImplementationNode node : nodes) {
node.parents.removeAll(nodes);
node.children.removeAll(nodes);
if (node != target) {
target.ifaces.addAll(node.ifaces);
target.mixins.addAll(node.mixins);
target.parents.addAll(node.parents);
target.children.addAll(node.children);
node.parents.clear();
node.children.clear();
node.ifaces.clear();
node.weaver.set(null);
}
}
}
void removeIfNotRequired() {
if (!requireImplementation && mixins.isEmpty()) {
for (ImplementationNode child : children) {
child.ifaces.addAll(ifaces);
child.parents.addAll(parents);
}
parents.clear();
children.clear();
ifaces.clear();
weaver.set(null);
}
}
void reduce() {
ANCESTOR.reduce(this);
}
boolean compact() {
ANCESTOR.reduce(this);
if (!requireImplementation && (children.size() <= 1 || mixins.isEmpty())) {
for (ImplementationNode child : children) {
child.ifaces.addAll(ifaces);
child.mixins.addAll(mixins);
parentLoop:
for (ImplementationNode parent : parents) {
for (ImplementationNode existingParent : child.parents) {
if (existingParent != this && existingParent.ancestors.contains(parent)) {
continue parentLoop;
}
}
child.parents.add(parent);
}
}
parents.clear();
children.clear();
ifaces.clear();
weaver.set(null);
return true;
} else {
return false;
}
}
void ensureSingleParent() {
if (parents.size() <= 1) {
return;
}
int maxWeight = -1;
ImplementationNode parentToKeep = null;
for (ImplementationNode parent : parents) {
int weight = parent.getWeight();
if (weight > maxWeight) {
maxWeight = weight;
parentToKeep = parent;
}
}
for (Iterator<ImplementationNode> it = parents.iterator(); it.hasNext(); ) {
ImplementationNode parent = it.next();
if (parent == parentToKeep) {
continue;
}
it.remove();
ifaces.addAll(parent.ifaces);
mixins.addAll(parent.transitiveMixins.asSet());
}
mixins.removeAll(parentToKeep.transitiveMixins.asSet());
}
boolean promoteCommonMixins() {
if (requireImplementation || children.isEmpty()) {
return false;
}
Set<MixinNode> commonMixins = new LinkedIdentityHashSet<>();
boolean first = true;
for (ImplementationNode child : children) {
if (first) {
commonMixins.addAll(child.mixins);
first = false;
} else {
commonMixins.retainAll(child.mixins);
}
}
if (commonMixins.isEmpty()) {
return false;
}
for (MixinNode mixin : commonMixins) {
mixins.add(mixin);
ifaces.add(mixin.getTargetInterface());
for (ImplementationNode child : children) {
child.mixins.remove(mixin);
child.ifaces.remove(mixin.getTargetInterface());
}
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("<");
boolean first = true;
for (Class<?> iface : getInterfaces()) {
if (first) {
first = false;
} else {
builder.append(",");
}
builder.append(iface.getSimpleName());
}
builder.append(">");
return builder.toString();
}
List<ClassDefinition> toClassDefinitions(WeavingContext weavingContext) {
List<ClassDefinition> classDefinitions = new ArrayList<>();
TargetContext targetContext = new TargetContextImpl(weavingContext, getClassName());
int version = 0;
List<Mixin> mixins = new ArrayList<>();
for (MixinNode mixinNode : this.mixins) {
Mixin mixin = mixinNode.getMixin();
if (version == 0) {
version = mixin.getBytecodeVersion();
} else if (mixin.getBytecodeVersion() != version) {
throw new WeaverException("Inconsistent bytecode versions");
}
classDefinitions.addAll(mixin.createInnerClassDefinitions(targetContext));
mixins.add(mixin);
}
if (version == 0) {
version = Opcodes.V1_7;
}
int access = Opcodes.ACC_PUBLIC;
if (!requireImplementation) {
access |= Opcodes.ACC_ABSTRACT;
}
if (children.isEmpty()) {
access |= Opcodes.ACC_FINAL;
}
List<String> ifaceNames = new ArrayList<>();
for (Class<?> iface : getInterfaces()) {
ifaceNames.add(Type.getInternalName(iface));
}
classDefinitions.add(
new ImplementationClassDefinition(
targetContext,
version,
access,
parents.isEmpty() ? null : parents.iterator().next().getClassName(),
ifaceNames.toArray(new String[ifaceNames.size()]),
primaryInterface.isSingleton(),
mixins.toArray(new Mixin[mixins.size()])));
return classDefinitions;
}
}