Inlining erzwingen kombiniert mit Entfernen von Imports..?

sirbender

Top Contributor
Hallo,

ich würde gerne den Namen (package.Klassenname) einer Klasse in einer anderen Klasse verwenden.

HelloWorld.class, HelloWorld.class.getName(), HelloWorld.class.toString() ergeben implizit oder explizit genau den String den ich haben will.

Ich frage mich in dem Zusammenhang, ob ich den Java-Compiler dazu bringen kann, den String zu inlinen und den HelloWorld import zu entfernen. Im Endeffekt wäre HelloWorld nur eine Compile aber keine Runtime-Abhängigkeit.
 

httpdigest

Top Contributor
Nein, das funktioniert leider nicht. Es sei denn, du schreibst den String wirklich als String hin und nicht als Methodenaufruf getName() oder toString() auf dem Class-Objekt HelloWorld.class, was ja erst zur Laufzeit existiert.
 

sirbender

Top Contributor
Wie gesagt, ich will nur eine Compile-Abhaengigkeit zwischen Library und Programm. Von der Library brauche ich nur einige Klassennamen und die will ich nicht als statische Strings ala (new String("hello.HelloWorld") einbinden.

Ist ja schade, das sowas nicht geht oder das man es wenigstens in Ausnahmefaellen erzwingen kann via String.intern() oder so.
 

httpdigest

Top Contributor
Falls du ganz abenteuerlich bist, kannst du auch ein kleines Tool mittels der ASM Library schreiben, welches eine .class Datei einliest, und alle expliziten Aufrufe von SomeClass.class.getName() in den String transformiert. Ich habe dir mal eben schnell so einen Transformer geschrieben (benötigt die asm Library z.B. downloadbar per hier). Das bekommt als Kommandozeilenparameter die zu transformierende Datei und transformiert sie in-place. Du kannst das natürlich leicht in ein Programm umwandeln, welches ein ganzes Verzeichnis rekursiv einliest.

Java:
import static org.objectweb.asm.Opcodes.*;
import java.io.*;
import org.objectweb.asm.*;
public class ClassNameInliner {
  public static void main(String[] args) throws Exception {
    InputStream is = new FileInputStream(args[0]);
    ClassReader cr = new ClassReader(is);
    ClassWriter cw = new ClassWriter(0);
    cr.accept(new ClassVisitor(ASM6, cw) {
      public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
          String[] exceptions) {
        return new MethodVisitor(ASM6, super.visitMethod(access, name, descriptor, signature, exceptions)) {
          private Type ldcType;
          /*
           * Strategy: Detect the pattern:
           * - LDC <class>
           * - INVOKEVIRTUAL java/lang/Class.getName()
           */
          public void visitLdcInsn(Object value) {
            if (value instanceof Type) {
              /* Loading a Class type */
              ldcType = (Type) value;
            } else {
              super.visitLdcInsn(value);
              ldcType = null;
            }
          }
          public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            if (opcode == INVOKEVIRTUAL && "java/lang/Class".equals(owner) && "getName".equals(name)
                && "()Ljava/lang/String;".equals(descriptor) && !isInterface && ldcType != null) {
              /* Having previously loaded a Class type and now calling getName() on it! */
              super.visitLdcInsn(ldcType.getClassName());
            } else {
              if (ldcType != null)
                super.visitLdcInsn(ldcType);
              super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            }
            ldcType = null;
          }
          /* Make sure that all other instructions break the match pattern */
          public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
            super.visitFieldInsn(opcode, owner, name, descriptor);
            ldcType = null;
          }
          public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
            super.visitMultiANewArrayInsn(descriptor, numDimensions);
            ldcType = null;
          }
          public void visitTypeInsn(int opcode, String type) {
            super.visitTypeInsn(opcode, type);
            ldcType = null;
          }
          public void visitIincInsn(int var, int increment) {
            super.visitIincInsn(var, increment);
            ldcType = null;
          }
          public void visitInsn(int opcode) {
            super.visitInsn(opcode);
            ldcType = null;
          }
          public void visitIntInsn(int opcode, int operand) {
            super.visitIntInsn(opcode, operand);
            ldcType = null;
          }
          public void visitJumpInsn(int opcode, Label label) {
            super.visitJumpInsn(opcode, label);
            ldcType = null;
          }
          public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
            super.visitLookupSwitchInsn(dflt, keys, labels);
            ldcType = null;
          }
          public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
            super.visitTableSwitchInsn(min, max, dflt, labels);
            ldcType = null;
          }
          public void visitVarInsn(int opcode, int var) {
            super.visitVarInsn(opcode, var);
            ldcType = null;
          }
          public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle,
              Object... bootstrapMethodArguments) {
            super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
            ldcType = null;
          }
        };
      }
    }, 0);
    is.close();
    /* Rewrite file */
    byte[] arr = cw.toByteArray();
    OutputStream fos = new FileOutputStream(args[0]);
    fos.write(arr);
    fos.flush();
    fos.close();
  }
}
 

sirbender

Top Contributor
Ist für ein Build-Tool. Klassennamen werden für viele Dinge verwendet. Main-Class oder Classpath in einem Jar ist ein Beispiel von Vielen.

Aktuell habe ich halt keine hardcodierten Strings - was sehr gut ist. In der IDE ist alles "verbunden", d.h. ich kann sogar Klassen in der Library umbenennen ohne, daß mir das was im Build-Tool zerhaut. Das Problem ist, ich will die Library eigentlich gar nicht ausliefern, weil ich sie nicht brauche - ich nutze nur die Namen.
Wenn ich Library sage, dann kann das alles sein, auch ein Plugin, daß man als Enduser für das Build-Tool schreibt.
 

sirbender

Top Contributor
Falls du ganz abenteuerlich bist, kannst du auch ein kleines Tool mittels der ASM Library schreiben, welches eine .class Datei einliest, und alle expliziten Aufrufe von SomeClass.class.getName() in den String transformiert. Ich habe dir mal eben schnell so einen Transformer geschrieben (benötigt die asm Library z.B. downloadbar per hier). Das bekommt als Kommandozeilenparameter die zu transformierende Datei und transformiert sie in-place. Du kannst das natürlich leicht in ein Programm umwandeln, welches ein ganzes Verzeichnis rekursiv einliest.

Vielen Dank! Ich schau es mir schon alleine aus Interesse an. Ich weiss jetzt nicht ob ich es integrieren will, weil ich "Hacks" eigentlich nicht mag und sie meiner Erfahrung nach auf lange Sicht auch viele Probleme machen.

Ich wundere mich nur, daß wenn man hello.HelloWorld.class + "" in Java eingibt, daß dies der Compiler nicht gleich in den Klassennamen-String umwandelt und das inline ablegt - oder zumindest, dass man den Compiler überreden kann sowas zu tun.

Gibts da einen technischen Grund, das sowas nicht gemacht werden kann? Scheint mir problematischer wenn man den Klassennamen selbst hardcodiert, weil sich ja oft Klassennamen aendern oder Klassen gar ganz verschwinden. Da scheint es sicherer zu sein, das nicht hardzucodieren und mal den Compiler checken zu lassen samt tollem IDE support.
 

mrBrown

Super-Moderator
Mitarbeiter
Je nachdem, wofür du den brauchst, gibt es da auch ganz „normale“ Wege für, aber anscheinend möchtest du da nicht näher ins Deteil gehen?

Zu deinem einem Beispiel, Library für Buildtool: So wie es klingt, soll das Buildtool die Lib zur Kompilezeit kenne, aber zur Laufzeit nicht mehr? Klingt zumindest etwas merkwürdig...
Genauso, wofür das Tool den Klassennamen zur Laufzeit braucht, aber nicht die Klasse...ein bisschen Information schadet da nicht, wenn man helfen soll...


Was mir einfällt sind zB, ja nach Zweck, Annotations, SPI, passend gekapselte class - aber wie gesagt müsste man mehr als nur eine grobe Beschreibung kennen, die alles bedeuten kann...
 

sirbender

Top Contributor
Das mit dem Build-Tool ist zu kompliziert zu erklären. Aber ich kann dir ein einfacheres Beispiel geben wo ich es schon oft vermisst habe. Oft werden Klassennamen in Form von Strings benötigt. Ich würde es aber gerne so haben, daß diese Strings erst zur Compile-Zeit realisiert wird und im Source-Code noch als HelloWorld.class.toString() vorliegt. Refactoring ist möglich und die Benutzung von Klassen in Teilen von Code ist auch leichter erkennbar. Wenn ich im Source-Code nur einen String hardcodiert habe geht das alles nicht.
 

mrBrown

Super-Moderator
Mitarbeiter
Aber ich kann dir ein einfacheres Beispiel geben wo ich es schon oft vermisst habe. Oft werden Klassennamen in Form von Strings benötigt. Ich würde es aber gerne so haben, daß diese Strings erst zur Compile-Zeit realisiert wird und im Source-Code noch als HelloWorld.class.toString() vorliegt.
naja, dein Beispiel ist das gleiche wie schon ganz am Anfang und kann man auch kaum Beispiel nennen...

Das ist da in aktuellen Java-Versionen kaum noch Notwendigkeit für sehe, hab ich ja schon gesagt, aber zu "ich brauch das genau so" gibt es natürlich keine Alternativen ;)
 

httpdigest

Top Contributor
[...]und im Source-Code noch als HelloWorld.class.toString() vorliegt.
Beachte bitte, dass toString() auf einem Class Objekt dir den String "class org.meinpackage.subpack.MeineKlasse" liefert und nicht den vollqualifizierten Namen der Klasse. Hierfür solltest du getName() verwenden. Und wenn du das tust, funktioniert die ASM Bytecode Rewriting Lösung denn für dich so?
 

Ähnliche Java Themen

Neue Themen


Oben