blob: 54fae60d1a0bbe4c3fd5dc64fada4abf59d1bebd [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2014 GK Software AG
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Please visit http://www.objectteams.org for updates and contact.
*
* Contributors:
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import static org.eclipse.objectteams.otredyn.transformer.names.ClassNames.OBJECT_SLASH;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.objectteams.otredyn.bytecode.asm.ASMByteCodeAnalyzer.ClassInformation;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
/**
* Variant of its superclass which strictly avoids the use of Class.forName(),
* because that would by-pass our transformer, when invoked from within the transformer!
*/
public class LoaderAwareClassWriter extends ClassWriter {
// Only use as a resource loader!
private ClassLoader loader;
private ASMByteCodeAnalyzer analyzer; // hopefully caching loaded classes per class-being-written is sufficient for performance
private Map<String, ClassInformation> knownClasses = new HashMap<String, ASMByteCodeAnalyzer.ClassInformation>();
public LoaderAwareClassWriter(ClassReader reader, int computeFrames, ClassLoader loader) {
super(reader, computeFrames);
this.loader = loader;
this.analyzer = new ASMByteCodeAnalyzer(false);
}
@Override
protected String getCommonSuperClass(String type1, String type2) {
// simple cases first:
if (type1.equals(type2))
return type1;
if (type1.equals(OBJECT_SLASH) || type2.equals(OBJECT_SLASH))
return OBJECT_SLASH;
ClassInformation ci1;
ClassInformation ci2;
// need to load class bytes:
try {
try (InputStream s1 = this.loader.getResourceAsStream(type1+".class")) {
ci1 = this.analyzer.getClassInformation(s1, type1);
if (ci1 == null)
return OBJECT_SLASH;
}
try (InputStream s2 = this.loader.getResourceAsStream(type2+".class")) {
ci2 = this.analyzer.getClassInformation(s2, type2);
if (ci2 == null)
return OBJECT_SLASH;
}
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
// do a breadth-first search: each iteration adds just one more level of super types,
// but strictly prefer common super class over super interface.
Set<String> allTypes1 = new HashSet<String>();
Set<String> allTypes2 = new HashSet<String>();
allTypes1.add(type1);
allTypes2.add(type2);
// Phase 1: classes:
List<String> newTypes1 = new ArrayList<String>();
addSuperClass(newTypes1, ci1);
List<String> newTypes2 = new ArrayList<String>();
addSuperClass(newTypes2, ci2);
while (!newTypes1.isEmpty() || !newTypes2.isEmpty()) {
for (String newType1 : newTypes1)
if (allTypes2.contains(newType1))
return newType1;
allTypes1.addAll(newTypes1);
for (String newType2 : newTypes2)
if (allTypes1.contains(newType2))
return newType2;
allTypes2.addAll(newTypes2);
newTypes1 = getDirectSupersLayer(newTypes1, true);
newTypes2 = getDirectSupersLayer(newTypes2, true);
}
// Phase 2: interfaces:
addSuperInterfaces(newTypes1, ci1);
addSuperInterfaces(newTypes2, ci2);
while (true) {
if (newTypes1.isEmpty() && newTypes2.isEmpty())
return OBJECT_SLASH;
for (String newType1 : newTypes1)
if (allTypes2.contains(newType1))
return newType1;
allTypes1.addAll(newTypes1);
for (String newType2 : newTypes2)
if (allTypes1.contains(newType2))
return newType2;
allTypes2.addAll(newTypes2);
newTypes1 = getDirectSupersLayer(newTypes1, false);
newTypes2 = getDirectSupersLayer(newTypes2, false);
}
}
private List<String> getDirectSupersLayer(List<String> types, boolean classes) {
List<String> result = new ArrayList<String>();
for (String type : types) {
ClassInformation ci = this.knownClasses.get(type);
if (ci == null) {
try (InputStream s = this.loader.getResourceAsStream(type+".class")) {
ci = this.analyzer.getClassInformation(s, type);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
this.knownClasses.put(type, ci);
}
if (ci != null) {
if (classes)
addSuperClass(result, ci);
else
addSuperInterfaces(result, ci);
}
}
return result;
}
private void addSuperClass(List<String> result, ClassInformation ci) {
String superClass = ci.getSuperClassName();
if (superClass != null && !superClass.equals(OBJECT_SLASH)) // avoid prematurely answering j.l.Object
result.add(superClass);
}
private void addSuperInterfaces(List<String> result, ClassInformation ci) {
for (String ifc : ci.getSuperInterfaceNames())
result.add(ifc);
}
}