private void addNewClassLoader() { for (ClassDescriptorSourceUnit sourceFile : sourceRegistry.getClassDescriptorSourceFileColl()) { sourceFile.resetLastLoadedClass(); // resetea también las innerclasses } this.customClassLoader = new JProxyClassLoader(this); }
private void reloadSource( @NotNull ClassDescriptorSourceUnit sourceFile, boolean detectInnerClasses) { Class clasz = customClassLoader.loadClass(sourceFile, true); LinkedList<ClassDescriptorInner> innerClassDescList = sourceFile.getInnerClassDescriptors(); if (innerClassDescList != null && !innerClassDescList.isEmpty()) { for (ClassDescriptorInner innerClassDesc : innerClassDescList) { customClassLoader.loadClass(innerClassDesc, true); } } else if (detectInnerClasses) { // Aprovechando la carga de la clase, hacemos el esfuerzo de cargar todas las clases // dependientes lo más // posible clasz .getDeclaredClasses(); // Provoca que las inner clases miembro indirectamente se procesen // y carguen // a través del JProxyClassLoader de la clase padre clasz // Ahora bien, lo anterior NO sirve para las anonymous inner classes, afortunadamente en ese // caso podemos // conocer y cargar por fuerza bruta // http://stackoverflow.com/questions/1654889/java-reflection-how-can-i-retrieve-anonymous-inner-classes // ?rq=1 for (int i = 1; i < Integer.MAX_VALUE; i++) { String anonClassName = sourceFile.getClassName() + "$" + i; Class innerClasz = customClassLoader.loadInnerClass(sourceFile, anonClassName); if (innerClasz == null) { break; // No hay más o no hay ninguna (si i es 1) } } // ¿Qué es lo que queda por cargar pero que no podemos hacer explícitamente? // 1) Las clases privadas autónomas que fueron definidas en el mismo archivo que la clase // principal: no // las soportamos pues no podemos identificar en el ClassLoader que es una clase "hot // reloadable", no son // inner classes en el sentido estricto // 2) Las clases privadas "inner" locales, es decir no anónimas declaradas dentro de un // método, se // cargarán la primera vez que se usen, no podemos conocerlas a priori // porque siguen la notación className$NclassName ej. // JReloadExampleDocument$1AuxMemberInMethod. No // hay problema con que se carguen con un class loader antiguo pues // el ClassLoader de la clase padre contenedora será el encargado de cargarla en cuanto se // pase por el // método que la declara. } }
private void deleteClasses(@NotNull ClassDescriptorSourceUnit sourceFile) { // Puede ocurrir que esta clase nunca se haya cargado y se ha modificado el código fuente y // queramos limpiar // los .class correspondientes pues se van a recrear // como no conocemos qué inner clases están asociadas para saber que .class hay que eliminar, // pues lo que // hacemos es directamente obtener los .class que hay // en el directorio con el fin de eliminar todos .class que tengan el patrón de ser inner // classes del source // file de acuerdo a su nombre // así conseguimos por ejemplo también eliminar las local classes (inner clases con nombre // declaradas dentro // de un método) que no hay manera de conocer // a través de la carga de la clase // Hay un caso en el que puede haber .class que ya no están en el código fuente y es cuando // tocamos el código // fuente ANTES de cargar y eliminamos algún .java, // al cargar como no existe el archivo no lo relacionamos con los .class // La solución sería en tiempo de carga forzar una carga de todas las clases y de ahí deducir // todos los // .class que deben existir (excepto las clases locales // que no podríamos detectarlas), pero el que haya .class sobrantes antiguos no es gran // problema. File classFilePath = ClassDescriptor.getAbsoluteClassFilePathFromClassNameAndClassPath( sourceFile.getClassName(), folderClasses); File parentDir = JProxyUtil.getParentDir(classFilePath); String[] fileNameList = parentDir.list(); // Es más ligero que listFiles() que crea File por cada resultado if (fileNameList != null) // Si es null es que el directorio no está creado { for (String fileName : fileNameList) { int pos = fileName.lastIndexOf(".class"); if (pos == -1) { continue; } String simpleClassName = fileName.substring(0, pos); if (sourceFile.getSimpleClassName().equals(simpleClassName) || sourceFile.isInnerClass(sourceFile.getPackageName() + simpleClassName)) { new File(parentDir, fileName).delete(); } } } }
private void compile( @NotNull ClassDescriptorSourceUnit sourceFile, @NotNull JProxyCompilerContext context) { if (sourceFile.getClassBytes() != null) { return; // Ya ha sido compilado seguramente por dependencia de un archivo compilado // inmediatamente antes, } // recuerda que el atributo classBytes se pone a null antes de compilar los archivos // cambiados/nuevos compiler.compileSourceFile(sourceFile, context, customClassLoader, sourceRegistry); }
private void cleanBeforeCompile(@NotNull ClassDescriptorSourceUnit sourceFile) { if (isSaveClassesMode()) { deleteClasses( sourceFile); // Antes de que nos las carguemos en memoria la clase principal y las inner // tras } // recompilar sourceFile .cleanOnSourceCodeChanged(); // El código fuente nuevo puede haber cambiado totalmente las // innerclasses antiguas (añadido, eliminado) y por supuesto el bytecode necesita olvidarse }
private void saveClasses(@NotNull ClassDescriptorSourceUnit sourceFile) { // Salvamos la clase principal { File classFilePath = ClassDescriptor.getAbsoluteClassFilePathFromClassNameAndClassPath( sourceFile.getClassName(), folderClasses); JProxyUtil.saveFile(classFilePath, sourceFile.getClassBytes()); } // Salvamos las innerclasses si hay, no hay problema de clases inner no detectadas pues lo están // todas pues // sólo se salva tras una compilación LinkedList<ClassDescriptorInner> innerClassDescList = sourceFile.getInnerClassDescriptors(); if (innerClassDescList != null && !innerClassDescList.isEmpty()) { for (ClassDescriptorInner innerClassDesc : innerClassDescList) { File classFilePath = ClassDescriptor.getAbsoluteClassFilePathFromClassNameAndClassPath( innerClassDesc.getClassName(), folderClasses); JProxyUtil.saveFile(classFilePath, innerClassDesc.getClassBytes()); } } }