/* Copyright 2008-2010 Gephi Authors : Martin Škurla Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.neo4j.plugin.impl; import org.gephi.neo4j.plugin.api.ClassNotFulfillRequirementsException; import org.gephi.neo4j.plugin.api.FileSystemClassLoader; import java.util.HashSet; import java.util.Set; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openide.util.lookup.ServiceProvider; import static org.gephi.neo4j.plugin.impl.FileSystemClassLoaderImpl.ReflectionUtils.*; /** * * @author Martin Škurla */ @ServiceProvider(service = FileSystemClassLoader.class) public class FileSystemClassLoaderImpl extends ClassLoader implements FileSystemClassLoader { private final Map<String, Class<?>> nameToClassMapper = new HashMap<String, Class<?>>(); private Class<?>[] requiredImplementedInterfaces; private boolean requirePublicNonparamConstructor; public FileSystemClassLoaderImpl() { super(FileSystemClassLoaderImpl.class.getClassLoader()); } @Override public Class<?> loadClass(File file) throws ClassNotFoundException { return loadClass(file, false, new Class<?>[0]); } @Override public Class<?> loadClass(File file, boolean requirePublicNonparamConstructor, Class<?>... requiredImplementedInterfaces) throws ClassNotFoundException { this.requiredImplementedInterfaces = requiredImplementedInterfaces; this.requirePublicNonparamConstructor = requirePublicNonparamConstructor; if (file == null || requiredImplementedInterfaces == null) { throw new NullPointerException(); } return super.loadClass(file.getAbsolutePath(), true); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException, NoClassDefFoundError, IllegalArgumentException, ClassNotFulfillRequirementsException { validatePrerequisites(name); Class<?> loadedClass = loadClassFromFile(new File(name)); validateClassRequirements(loadedClass); return loadedClass; } private void validatePrerequisites(String name) throws ClassNotFoundException, IllegalArgumentException { File classFile = new File(name); if (!(classFile.exists() && classFile.isFile())) { throw new ClassNotFoundException("Class " + name + " was not found."); } if (!name.endsWith(".class")) { throw new IllegalArgumentException("Name of the file must have suffix '.class'."); } if (!areAllInterfaces(requiredImplementedInterfaces)) { throw new IllegalArgumentException("Not all of " + Arrays.toString(requiredImplementedInterfaces) + " are interfaces."); } } private void validateClassRequirements(Class<?> loadedClass) throws ClassNotFulfillRequirementsException { if (!isClass(loadedClass)) { throw new IllegalArgumentException("Type " + loadedClass + " is not class."); } if (requirePublicNonparamConstructor && !hasPublicNonparamConstructor(loadedClass)) { throw new ClassNotFulfillRequirementsException( "Class " + loadedClass.getName() + " does not have nonparam constructor."); } if (requiredImplementedInterfaces.length > 0 && !isImplementingAllInterfaces(loadedClass, requiredImplementedInterfaces)) { String message = String.format("Class %s does not implement all of interfaces: %s", loadedClass.getName(), Arrays.toString(requiredImplementedInterfaces)); throw new ClassNotFulfillRequirementsException(message); } } private Class<?> loadClassFromFile(File classFile) throws NoClassDefFoundError { int classFileLength = (int) classFile.length(); byte[] classFileContent = new byte[classFileLength]; readFileContent(classFile, classFileContent); String binaryClassName = classFile.getName().substring(0, classFile.getName().lastIndexOf(".")); try { // very special case when we try to load class with default package, but it was already // loaded as part of other class // this situation only exists when other loaded class extends currently class located without // package and currently class in in classpath return super.loadClass(binaryClassName); } catch (ClassNotFoundException cnfe) { // do nothing, explanation is above } File parentDirectory = classFile; while (true) { if (nameToClassMapper.containsKey(binaryClassName)) { return nameToClassMapper.get(binaryClassName); } try { Class<?> definedClass = defineClass(binaryClassName, classFileContent, 0, classFileLength); nameToClassMapper.put(binaryClassName, definedClass); return definedClass; } catch (NoClassDefFoundError error) { parentDirectory = parentDirectory.getParentFile(); if (parentDirectory == null) { throw new NoClassDefFoundError("Class file " + classFile.getName() + " is not located in proper directory structure."); } binaryClassName = parentDirectory.getName() + "." + binaryClassName; } } } private void readFileContent(File sourceFile, byte[] targetByteArray) { FileInputStream fis = null; try { fis = new FileInputStream(sourceFile); fis.read(targetByteArray); } catch (IOException ioe) { ioe.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException ioe) { } } } } static class ReflectionUtils { private ReflectionUtils() { } static boolean isImplementingAllInterfaces(Class<?> type, Class<?>... interfaceTypes) { List<Class<?>> requiredInterfaces = Arrays.asList(interfaceTypes); Set<Class<?>> typeInterfaces = new HashSet<Class<?>>(); while (type != null) { typeInterfaces.addAll(Arrays.asList(type.getInterfaces())); type = type.getSuperclass(); } return typeInterfaces.containsAll(requiredInterfaces); } static boolean hasPublicNonparamConstructor(Class<?> type) { Constructor<?>[] constructors = type.getConstructors(); for (Constructor<?> constructor : constructors) { if (constructor.getParameterTypes().length == 0 && Modifier.isPublic(constructor.getModifiers())) { return true; } } return false; } static boolean isClass(Class<?> type) { return !type.isAnnotation() && !type.isArray() && !type.isInterface() && !type.isPrimitive(); } static boolean areAllInterfaces(Class<?>... interfaceTypes) { int interfaceTypesCount = 0; for (Class<?> interfaceType : interfaceTypes) { if (interfaceType.isInterface()) { interfaceTypesCount++; } } return interfaceTypesCount == interfaceTypes.length; } } }