blob: f95dd82a84ec8659c2c73f439c98ea00aa23a25f [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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.axiom.weaver.mixin.ClassDefinition;
import org.apache.axiom.weaver.mixin.InitializerMethod;
import org.apache.axiom.weaver.mixin.Mixin;
import org.apache.axiom.weaver.mixin.MixinMethod;
import org.apache.axiom.weaver.mixin.StaticInitializerMethod;
import org.apache.axiom.weaver.mixin.TargetContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
final class ImplementationClassDefinition extends ClassDefinition {
private static final Log log = LogFactory.getLog(ImplementationClassDefinition.class);
private final TargetContext targetContext;
private final int version;
private final int access;
private final String superName;
private final String[] ifaceNames;
private final boolean singleton;
private final Mixin[] mixins;
private final MixinMethod[] methods;
private final List<InitializerMethod> initializerMethods = new ArrayList<>();
private final List<StaticInitializerMethod> staticInitializerMethods = new ArrayList<>();
ImplementationClassDefinition(
TargetContext targetContext,
int version,
int access,
String superName,
String[] ifaceNames,
boolean singleton,
Mixin[] mixins) {
super(targetContext.getTargetClassName());
this.targetContext = targetContext;
this.version = version;
this.access = access;
this.superName = superName != null ? superName : Type.getInternalName(Object.class);
this.ifaceNames = ifaceNames;
this.singleton = singleton;
this.mixins = mixins;
Map<String, MixinMethod> methodMap = new LinkedHashMap<>();
for (Mixin mixin : mixins) {
for (MixinMethod method : mixin.getMethods()) {
String signature = method.getSignature();
// TODO: check that the method being replaced is not final
MixinMethod existingMethod = methodMap.get(signature);
if (existingMethod != null
&& !method.getMixin().appliesAfter(existingMethod.getMixin())) {
if (existingMethod.getMixin().appliesAfter(method.getMixin())) {
// Keep the existing method.
continue;
} else {
String body1 = existingMethod.getBody().toString(targetContext);
String body2 = method.getBody().toString(targetContext);
// Allow two mixins to define identical methods. This is useful for DOOM
// because some methods are defined in the same way by DOM and Axiom.
if (body1.equals(body2)) {
log.info(
"Ignoring identical mixin method for "
+ signature
+ " in "
+ targetContext.getTargetClassName());
continue;
}
throw new WeaverException(
"Method collision for "
+ signature
+ " in "
+ targetContext.getTargetClassName()
+ "\n--- Method defined by "
+ existingMethod.getMixin().getName()
+ ":\n"
+ body1
+ "\n--- Method defined by "
+ method.getMixin().getName()
+ ":\n"
+ body2);
}
}
methodMap.put(signature, method);
}
InitializerMethod initializerMethod = mixin.getInitializerMethod();
if (initializerMethod != null) {
initializerMethods.add(initializerMethod);
}
StaticInitializerMethod staticInitializerMethod = mixin.getStaticInitializerMethod();
if (staticInitializerMethod != null) {
staticInitializerMethods.add(staticInitializerMethod);
}
}
methods = methodMap.values().toArray(new MixinMethod[methodMap.size()]);
}
private void generateConstructor(ClassVisitor cv) {
MethodVisitor mv =
cv.visitMethod(
singleton ? Opcodes.ACC_PRIVATE : Opcodes.ACC_PUBLIC,
"<init>",
"()V",
null,
null);
mv.visitCode();
mv.visitIntInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false);
MethodInliner inliner =
new MethodInliner(mv, 1, 1, new Object[] {targetContext.getTargetClassName()});
for (InitializerMethod method : initializerMethods) {
method.getBody().apply(targetContext, inliner);
}
mv.visitInsn(Opcodes.RETURN);
inliner.emitMaxs();
mv.visitEnd();
}
private void generateStaticInitializer(ClassVisitor cv) {
if (staticInitializerMethods.isEmpty() && !singleton) {
return;
}
MethodVisitor mv =
cv.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
MethodInliner inliner = new MethodInliner(mv, singleton ? 2 : 0, 0, new Object[0]);
for (StaticInitializerMethod method : staticInitializerMethods) {
method.getBody().apply(targetContext, inliner);
}
if (singleton) {
mv.visitTypeInsn(Opcodes.NEW, targetContext.getTargetClassName());
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
targetContext.getTargetClassName(),
"<init>",
"()V",
false);
mv.visitFieldInsn(
Opcodes.PUTSTATIC,
targetContext.getTargetClassName(),
"INSTANCE",
"L" + targetContext.getTargetClassName() + ";");
}
mv.visitInsn(Opcodes.RETURN);
inliner.emitMaxs();
mv.visitEnd();
}
@Override
public void accept(ClassVisitor cv) {
cv.visit(version, access, className, null, superName, ifaceNames);
if (singleton) {
cv.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
"INSTANCE",
"L" + targetContext.getTargetClassName() + ";",
null,
null);
}
generateConstructor(cv);
generateStaticInitializer(cv);
for (Mixin mixin : mixins) {
mixin.apply(className, cv);
}
for (MixinMethod method : methods) {
method.apply(targetContext, cv);
}
cv.visitEnd();
}
}