Scala Neues Projekt "Scava"

Marco13

Top Contributor
Hmja, hab auch noch nicht so viel Zeit gefunden, mich mit Scala näher auseinanderzusetzen, aber ... vielleicht würde so eine kleine "Brücke" ja helfen... bei Gelegenheit mal näher ansehen :rtfm:
 

Noctarius

Top Contributor
Ok jetzt habe ich das problem auch verstanden (nachdem ich ein bisschen Source darstellen wollte). Ich werde mich gleich mal mit dem Thema befassen. Wie funktioniert das bei Mixins (heißt das so bei Scala)?

Wird dann ein Traint vom anderen abgeleitet und überschreiben sich die Traint Implementierungen wenn man ein Traint extended?
 

Landei

Top Contributor
Siehe Chapter*4.*Traits

Zusammengefaßt:
- Traits sind wie Interfaces, erlauben aber, Methoden zu implementieren
- Traits haben keinen Konstruktor
- Die Vererbungsreihenfolge wird nach einfachen Regeln bestimmt ("Linearisierung")
- Damit hat man im Prinzip die Vorteile der Mehrfachvererbung ohne die Nachteile (wie das Diamantproblem)

Die Dinger heißen übrigens "Traits" ohne "n" :)
 

Noctarius

Top Contributor
Ich bastel gerade an einer Lösung, schätze noch ne knappe Stunde, dann kann ich dir mal einen Rohdiamanten zeigen :p Allerdings mit einem Teil eingebettetem ASM. Anders ist es vermutlich wirklich nicht machbar. Aus dem ASM kann auch noch ein wenig entfernt werden, aber das mache ich jetzt spontan erstmal nicht, da ich überlege auch noch name() und name_$eq in den Proxy zu integrieren.
 

Noctarius

Top Contributor
Hat doch ein paar Minuten länger gedauert :D

Hier ein "kleines" Beispiel für den passenden Proxy-Generator. Support für Getter / Setter ist noch nicht drin, baue ich aber auf Wunsch gerne noch ein :)
Die ASM Klassen sind bundled, so dass keine externen Abhängigkeiten bestehen.

Die Basisklasse ist die ScavaProxyFactory. Über diese kannst du neue Proxies erstellen.
Von der Factory sollten möglichst selten neue Instanzen geholt werden, da diese die generierten Proxy-Klassen cachen um den Overhead der Analyse und Generierung zu vermeiden. Gecachte Proxy-Klassen lassen sich aber auch explizit entfernen.

Die Factory erzeugt eine Subclass der übergebenen (vermutlich) abstrakten Klasse und sucht alle als abstract markierten Klassen und die dazugehörigen Scala Trait Staticmethoden. Wird eine nicht gefunden gibts eine Exception. Passt alles wird der Bytecode generiert und geladen.
Neben diesen Methoden wird zusätzlich noch die [c]int $tag()[/c] Methode generiert, für was auch immer die gut ist ;)

Die nachfolgende Testklasse liegt dem src.zip unten bei :)

Java:
public class FooTest {

	public static void main(String[] args) throws Exception {
		ScavaProxyFactory proxyFactory = ScavaProxyFactory.newInstance();
		Foo2 foo2 = proxyFactory.newProxyInstance(AbstractFoo.class);

		System.out.println(foo2.g("World"));
		System.out.println(foo2.f());
		System.out.println(foo2.t("Fantastic World of Bytecode Generation"));

		Method method = foo2.getClass().getMethod("$tag");
		System.out.println(method.invoke(foo2));
	}

	public static interface Foo extends ScalaObject {
		int f();
		
		int t(String s);
	}
	
	public static class Foo$class {
		public static int t(Foo foo, String s) {
			if (foo instanceof Foo2)
				return ((Foo2) foo).g(s);
				
			return -1;
		}
	}

	public static interface Foo2 extends Foo {
		int g(String s);
	}

	public static class Foo2$class {
		public static int g(Foo2 foo2, String s) {
			return s.length();
		}
	}

	public static abstract class AbstractFoo implements Foo2 {

		@Override
		public int f() {
			return g("Hello World");
		}
	}
}

PS: Ausgabe ist
Code:
5
11
38
0

Sieht aus wie gewollt :D
 

Anhänge

  • src.zip
    64,9 KB · Aufrufe: 5

Landei

Top Contributor
Hey, cool! Ich muss es mir zwar noch genauer ansehen, würde es aber auf jeden fall gern in Scava aufnehmen wollen (aber dann möglichst so, dass man asm nicht zwingend benötigt, und wenn es nicht vorhanden ist, auf die UnsupportedOperationException-Variante "zurückfällt").

Übrigens: Welche Scala-Bibliothek verwendest du? Das aktuelle Scala 2.8 sollte keine $tag-Methode mehr brauchen ( Scala - User - method $tag overrides nothing ?? )
 
Zuletzt bearbeitet:

Noctarius

Top Contributor
Ich hatte jetzt 2.8.1 oder so. Da gab es vermutlich auch keine $tag Methode, aber es stand so in der Beschreibung auf der Website die du weiter oben genannt hast.
Wie gesagt du brauchst ASM nicht als Abhängigkeit angeben, sondern kannst die Klassen von ASM die gebraucht werden bundlen. Kannst aber die ASM Klassen auch rauslassen und irgendwie eine zweite Factory bauen und schauen ob eine bestimmte Klasse im Classpath ist, wenn ja soll er den ASM Proxy nutzen oder so.

Ohne ASM geht es nur mit reinen Interfaces oder eben wirklich über die UnsupportedOperationException.

Wenn du Fragen zum Source hast kannst du diese gerne stellen, da auch ich am Anfang für Bytecode-Generierung etwas Zeit zum Einarbeiten brauchte. Mittlerweile geht es relativ gut :p

PS: Klar darfst du die Klasse nutzen, nimm eine Lizenz die dir gefällt ;-)

edit: hier noch mal die Factory-Class ohne $tag Methode, da fällt mir übrigens gerade ein, das geht momentan natürlich nur mit Standard-Konstruktoren. Sollen spezielle Konstruktoren zum Einsatz kommen müsste man da noch mal überlegen.

Java:
package org.scava.util.proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import scala.ScalaObject;

/**
 * The Proxy Enhancer class inspired and implemented by a subset of ASM
 * ([url=http://asm.ow2.org/]ASM - Home Page[/url]) to enable the creation of Proxy-Classes for Scala
 * Traits which are needs to be able to proxy abstract Java-Classes.
 * 
 * @author noctarius
 */
public class ScavaProxyFactory {
	private final Map<Class<?>, Class<?>> proxyClassMapping = new HashMap<Class<?>, Class<?>>();

	/**
	 * Creates a new instance of this ScalaProxyFactory.
	 * 
	 * @return A new factory instance for proxy creation
	 */
	public static ScavaProxyFactory newInstance() {
		return new ScavaProxyFactory();
	}

	// Prevent external instantiation of this Utility-Class
	private ScavaProxyFactory() {
	}

	/**
	 * Creates a new Proxy by dynamically subclassing the given class and
	 * extending all abstract marked methods by their Scala Trait counterparts
	 * which are created in special classes as static methods by the Scala
	 * Compiler.<br>
	 * As the classloader to search for classes and interfaces that are used by
	 * the given class, the classloader, used to load the given class, will be
	 * used.
	 * 
	 * @param <T>
	 *            The type of the class the be proxied
	 * @param clazz
	 *            The class a Proxy will be created for
	 * @return The Trait Proxy implementing the abstract methods
	 */
	public <T> T newProxyInstance(Class<T> clazz) {
		return newProxyInstance(clazz, clazz.getClassLoader());
	}

	/**
	 * Creates a new Proxy by dynamically subclassing the given class and
	 * extending all abstract marked methods by their Scala Trait counterparts
	 * which are created in special classes as static methods by the Scala
	 * Compiler.<br>
	 * A special classloader can be given to search for classes and interfaces
	 * that are used by the given class.
	 * 
	 * @param <T>
	 *            The type of the class the be proxied
	 * @param clazz
	 *            The class a Proxy will be created for
	 * @param classLoader
	 *            The Classloader to be used for searching interfaces and
	 *            classes
	 * @return The Trait Proxy implementing the abstract methods
	 */
	@SuppressWarnings("unchecked")
	public <T> T newProxyInstance(final Class<T> clazz, ClassLoader classLoader) {

		if (clazz == null) {
			throw new NullPointerException("clazz cannot be null");
		}

		if (classLoader == null) {
			classLoader = clazz.getClassLoader();
		}

		Class<T> proxyClass = (Class<T>) proxyClassMapping.get(clazz);

		if (proxyClass == null) {
			final Set<MethodDefinition> scalaMethodDefinitions = new HashSet<MethodDefinition>();
			final String[] interfaces = new String[clazz.getInterfaces().length];

			for (int i = 0; i < clazz.getInterfaces().length; i++) {
				interfaces[i] = Type.getInternalName(clazz.getInterfaces()[i]);
			}

			for (Method method : clazz.getMethods()) {
				if ((method.getModifiers() & Modifier.ABSTRACT) != 0) {
					Class<?> interfaze = method.getDeclaringClass();

					if (interfaze.isInterface()
							&& ScalaObject.class.isAssignableFrom(interfaze)) {
						scalaMethodDefinitions.add(buildMethodDefinition(
								classLoader, method, interfaze));

					} else {
						throw new IllegalArgumentException(clazz.getName()
								+ " is no legal Scala Trait");
					}
				}
			}

			final String proxyClassName = new StringBuilder(clazz
					.getSimpleName()).append("$SCAVAPROXY").toString();

			final String proxyClassSignature = proxyClassName.replace(".", "/");

			final ClassWriter cw = new ClassWriter(0);
			cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNTHETIC
					+ Opcodes.ACC_SUPER, proxyClassName, proxyClassSignature,
					Type.getInternalName(clazz), interfaces);

			createConstructor(cw, clazz);

			for (MethodDefinition def : scalaMethodDefinitions) {
				pushInvokeOfStaticTraitMethod(cw, def);
			}

			cw.visitEnd();

			ASMClassLoader<T> asmClassLoader = new ASMClassLoader<T>(
					classLoader);
			proxyClass = asmClassLoader.loadClass(cw.toByteArray());

			proxyClassMapping.put(clazz, proxyClass);
		}

		try {
			return proxyClass.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Removes a bind class from cache to free the ProxyClass
	 * 
	 * @param clazz
	 *            Class to be removed from ProxyClass Cache
	 */
	public void unbindClass(Class<?> clazz) {
		proxyClassMapping.remove(clazz);
	}

	private void pushInvokeOfStaticTraitMethod(final ClassWriter cw,
			final MethodDefinition def) {

		final MethodWriter mw = cw.visitMethod(Opcodes.ACC_PUBLIC
				+ Opcodes.ACC_SYNTHETIC + Opcodes.ACC_FINAL, def.name,
				def.description, null, def.buildExceptionArray());

		mw.visitCode();

		prepareStackForExecution(mw, def);

		mw.visitMethodInsn(Opcodes.INVOKESTATIC, Type
				.getInternalName(def.scalaStaticTraitClass), def.name, def
				.buildStaticMethodDescriptor());

		pushReturnOpcodeByValue(mw, def);

		mw.visitMaxs(def.types.length + 1, def.types.length + 1);
		mw.visitEnd();
	}

	private void pushReturnOpcodeByValue(final MethodWriter mw,
			final MethodDefinition def) {

		switch (def.returnType.getSort()) {
		case Type.VOID:
			mw.visitInsn(Opcodes.RETURN);
			break;
		case Type.BYTE:
		case Type.SHORT:
		case Type.CHAR:
		case Type.INT:
			mw.visitInsn(Opcodes.IRETURN);
			break;
		case Type.LONG:
			mw.visitInsn(Opcodes.LRETURN);
			break;
		case Type.FLOAT:
			mw.visitInsn(Opcodes.FRETURN);
			break;
		case Type.DOUBLE:
			mw.visitInsn(Opcodes.DRETURN);
			break;
		default:
			mw.visitInsn(Opcodes.ARETURN);
		}
	}

	private void prepareStackForExecution(final MethodWriter mw,
			final MethodDefinition def) {

		// Load this instance on stack
		mw.visitVarInsn(Opcodes.ALOAD, 0);

		// Load parameters on stack
		for (int i = 1; i <= def.types.length; i++) {
			Type type = def.types[i - 1];

			switch (type.getSort()) {
			case Type.BYTE:
			case Type.SHORT:
			case Type.CHAR:
			case Type.INT:
				mw.visitVarInsn(Opcodes.ILOAD, i);
				break;
			case Type.LONG:
				mw.visitVarInsn(Opcodes.LLOAD, i);
				break;
			case Type.FLOAT:
				mw.visitVarInsn(Opcodes.FLOAD, i);
				break;
			case Type.DOUBLE:
				mw.visitVarInsn(Opcodes.DLOAD, i);
				break;
			default:
				mw.visitVarInsn(Opcodes.ALOAD, i);
			}
		}
	}

	private void createConstructor(final ClassWriter cw, final Class<?> clazz) {
		final MethodWriter mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
				"()V", null, null);

		mv.visitCode();
		mv.visitVarInsn(Opcodes.ALOAD, 0);
		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(clazz),
				"<init>", "()V");
		mv.visitInsn(Opcodes.RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	private MethodDefinition buildMethodDefinition(
			final ClassLoader classLoader, final Method method,
			final Class<?> interfaze) {

		try {
			final Class<?> scalaStaticTraintClass = retrieveScalaTraitClass(
					classLoader, interfaze);

			final Type[] types = extractParameterTypes(method);
			final Type[] exceptions = extractExceptionTypes(method);

			return new MethodDefinition(scalaStaticTraintClass, interfaze,
					method.getName(), Type.getMethodDescriptor(method), types,
					exceptions, Type.getType(method.getReturnType()));
		} catch (ClassNotFoundException e) {
			throw new IllegalStateException(
					"Scala Static Trait Class could not be found", e);
		}
	}

	private Type[] extractExceptionTypes(final Method method) {
		final Class<?>[] exceptionClasses = method.getExceptionTypes();
		final Type[] exceptions = new Type[exceptionClasses.length];

		for (int i = 0; i < exceptionClasses.length; i++) {
			exceptions[i] = Type.getType(exceptionClasses[i]);
		}

		return exceptions;
	}

	private Type[] extractParameterTypes(final Method method) {
		final Class<?>[] parameterClasses = method.getParameterTypes();
		final Type[] types = new Type[parameterClasses.length];

		for (int i = 0; i < parameterClasses.length; i++) {
			types[i] = Type.getType(parameterClasses[i]);
		}

		return types;
	}

	private Class<?> retrieveScalaTraitClass(final ClassLoader classLoader,
			final Class<?> interfaze) throws ClassNotFoundException {

		String className = Type.getInternalName(interfaze).replace("/", ".")
				+ "$class";

		return classLoader.loadClass(className);
	}

	private static class ASMClassLoader<T> extends ClassLoader {

		private ASMClassLoader(ClassLoader parent) {
			super(parent);
		}

		@SuppressWarnings("unchecked")
		public Class<T> loadClass(final byte[] data) {
			return (Class<T>) defineClass(null, data, 0, data.length);
		}
	}

	private static class MethodDefinition {
		final Class<?> scalaStaticTraitClass;
		final Class<?> interfaceClass;
		final String name;
		final String description;
		final Type[] types;
		final Type[] exceptions;
		final Type returnType;

		private MethodDefinition(final Class<?> scalaStaticTraitClass,
				final Class<?> interfaceClass, final String name,
				final String description, final Type[] types,
				final Type[] exceptions, final Type returnType) {

			this.scalaStaticTraitClass = scalaStaticTraitClass;
			this.interfaceClass = interfaceClass;
			this.name = name;
			this.description = description;
			this.types = types;
			this.exceptions = exceptions;
			this.returnType = returnType;
		}

		private String[] buildExceptionArray() {
			final String[] exceptionClasses = new String[exceptions.length];

			for (int i = 0; i < exceptions.length; i++) {
				exceptionClasses[i] = exceptions[i].getInternalName();
			}

			return exceptionClasses;
		}

		private String buildStaticMethodDescriptor() {
			final Type[] methodTypes = new Type[types.length + 1];
			methodTypes[0] = Type.getType(interfaceClass);
			System.arraycopy(types, 0, methodTypes, 1, types.length);
			return Type.getMethodDescriptor(returnType, methodTypes);
		}
	}

}
 
Zuletzt bearbeitet:

Noctarius

Top Contributor
Jetzt hast du es doch geschafft, mich für das Projekt zu interessieren :D

Wie willst du das Operator-System sauber implementieren? Über eine Helperclass? Hast du es vielleicht schon, sonst würde ich mir das mal anschauen :)
 

Landei

Top Contributor
Bei den Operatoren gibt es nicht viel Möglichkeiten, und der Mehrwert für den Nutzer, wenn er mit meiner Bibliothek statt [c]a.$plus(b)[/c] [c]add(a,b)[/c] schreiben kann, ist eher gering. Ich sehe folgende Gebiete, wo man wirksam unterstützen kann:
- Die Funktionalität von object-Klassen bereitstellen. [c]Foo$.MODULE$[/c] ist gewöhnungsbedürftig, wenn man diese Methoden geeignet in statische Methoden übersetzt, erleichtert das die Arbeit
- Die Ableitung von Traits. Neben der dynamischen Variante mit Proxies kann man das für wichtige Traits wie FunctionN auch statisch machen, also eine "fertige" abstrakte Klasse zum Ableiten anbieten
- Hilfsmethoden für den Aufruf fortgeschrittener Konstrukte, also z.B. wenn die Java-Typinferenz zu schwach ist, oder Konvertierung von Java-Varargs zu Scala-Varargs
- Bereitstellung impliziter Objekte oder Konvertierungen (etwa Ordering-Instanzen oder wenn das map in den Scala-Collections ein implizites CanBuildFrom-Objekt benötigt)
- Konvertierungen von/zu äquivalenten Java-Konstrukten (Comparator, Comparable, Iterator, Iterable, Runnable, Callable, Future...)

Den Code kannst du mit SVN runterziehen: Source Checkout - scava-org - Project Hosting on Google Code

Ist aber noch ziemlich dünn und wenig getestet, ich bin noch am Experimentieren.
 
Zuletzt bearbeitet:

Noctarius

Top Contributor
Den Source hab ich schon gesehen :D Da ist ja seit ein paar Tagen nichts neues drin.

Ich dachte jetzt an eine Helper-Methode im Stil von:

Java:
T operatorCalculation(String operator, T val1, T val2)

Java:
Foo result = operatorCalculation(":++", foo, bar);
 

Landei

Top Contributor
Viele Operatoren liefern einen anderen Typ zurück. Für [c]::[/c] hätte man beispielsweise:

Java:
<A, B extends A> List<A> cons(A a, List<B> list)

Wie soll man das in ein vernünftiges Schema bringen?
 

Noctarius

Top Contributor
Dann müsste man 3 Typen übergeben, Rückgabe, 1. Parameter, 2. Parameter.

Das ist alles unschön solange es kein echtes OO in Java gibt :)

Wie wird es denn im Runtime-Code von Scala gemacht?
 

Landei

Top Contributor
Es geht ja darum, Scala-Code in Java besser verwendbar zu machen, und nicht darum, dass man in Java möglichst scala-artigen Code schreiben kann. Für die Operatoren könnte ich mir höchstens ein Objekt vorstellen, das die häufigsten Aufrufe statisch abbildet, also (einmal exemplarisch für BigInt)

Java:
public class BigIntOperators {
   public static BigInt add(BigInt a, BigInt b) {...}
   public static BigInt multiply(BigInt a, BigInt b) {...}
   ...
   public static boolean lessThan(BigInt a, BigInt b) {...}
   public static boolean lessThanOrEqual(BigInt a, BigInt b) {...}
   public static boolean equal(BigInt a, BigInt b) {...}
   ...
}

Allerdings würde sowas nur sinnvoll sein, wenn man dieses Objekt mehr oder weniger automatisch erstellt (also z.B. die Klassen per Reflection nach Methoden wie $plus scannt).

Allerdings gibt es meiner Meinung nach erst einmal wichtigeres, z.B. ein möglichst reibungsloses Arbeiten mit Scala-Collections und Arrays.
 
Zuletzt bearbeitet:

Landei

Top Contributor
- Scala-Varargs basieren auf Seq und Java-Varargs auf Arrays
- Scala bindet Arrays (über eine Art Autoboxing) in die Collection-Hierarchie ein, ich kann also ein Java-Array nicht einfach übergeben, wenn eine Scala-Collection erwartet wird, sondern muss es erst "wrappen"
- Wenn Scala-Methoden ein gewrapptes Array zurückliefern, muss das erst mal ausgepackt werden
- Scala-Collection-Methoden benötigen oft implizite Objekte (CanBuildFrom, Ordering), die den Aufruf von Java aus unbequem machen (ich habe das im Collections-Objekt mal für diverse map-Methoden durchgespielt)

In diesen und ähnlichen Fällen könnte ich mir statische Hilfsmethode vorstellen.
 

Noctarius

Top Contributor
Wäre es bei sowas nicht das einfachte eine Art Prozessor zu bauen welcher automatisch Klassen erweitert und ein Eclipse und / oder Maven / Ant Plugin dafür zu erstellen?
 

Landei

Top Contributor
Da halte ich weniger davon. Wer bereit ist, so einen Aufwand zu treiben und Build-Abhängigkeiten in Kauf nimmt, kann auch gleich ganz zu Scala konvertieren. Ich stelle mir eher Situationen vor, wo Scala- und Java-Code nebeneinaner existiert und die ältere Java-Applikation mal eben an zwei, drei Stellen den neuen Scala-Code mitnutzen soll. Das muss dann nicht super-komfortabel sein, aber z.B. auch von Leuten mit wenig Scala-Erfahrung zu programmieren sein. Wer Scala nicht kennt, scheitert eventuell schon, wenn er seine Java-Liste in eine Scala-Liste konvertieren muss, oder ein bestimmtes implizites Objekt nicht findet. Und genau in solchen Situationen soll Scava helfen.
 

Noctarius

Top Contributor
Naja das bisschen alter Java-Code ... Ich denke eher, dass wenn ein Projektwechsel schleichend von statten geht. Ich kann mir nicht vorstellen mal eben so unser System in der Firma nach Scala zu portieren und nur Reste in Java zu lassen ;)
 

Neue Themen


Oben