| /* |
| * 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; |
| } |
| } |