// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.pipe; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.swing.Icon; import pleocmd.Log; import pleocmd.exc.InternalException; import pleocmd.itfc.gui.help.HelpLoader; import pleocmd.pipe.PipePart.HelpKind; import pleocmd.pipe.cvt.Converter; import pleocmd.pipe.in.Input; import pleocmd.pipe.out.Output; public final class PipePartDetection { private static final List<Class<Input>> LIST_IN = getAllPipeParts("in"); private static final List<Class<Converter>> LIST_CVT = getAllPipeParts("cvt"); private static final List<Class<Output>> LIST_OUT = getAllPipeParts("out"); private static final List<Class<? extends PipePart>> LIST_PIPEPART; static { LIST_PIPEPART = new ArrayList<Class<? extends PipePart>>(); for (final Class<Input> ppc : LIST_IN) LIST_PIPEPART.add(ppc); for (final Class<Converter> ppc : LIST_CVT) LIST_PIPEPART.add(ppc); for (final Class<Output> ppc : LIST_OUT) LIST_PIPEPART.add(ppc); final Comparator<Class<? extends PipePart>> cmp = new Comparator<Class<? extends PipePart>>() { @Override public int compare(final Class<? extends PipePart> c1, final Class<? extends PipePart> c2) { return PipePart.getName(c1).compareTo(PipePart.getName(c2)); } }; Collections.sort(LIST_IN, cmp); Collections.sort(LIST_CVT, cmp); Collections.sort(LIST_OUT, cmp); Collections.sort(LIST_PIPEPART, cmp); } // CS_IGNORE_BEGIN need private before public here public static final List<Class<Input>> ALL_INPUT = Collections .unmodifiableList(LIST_IN); public static final List<Class<Converter>> ALL_CONVERTER = Collections .unmodifiableList(LIST_CVT); public static final List<Class<Output>> ALL_OUTPUT = Collections .unmodifiableList(LIST_OUT); public static final List<Class<? extends PipePart>> ALL_PIPEPART = Collections .unmodifiableList(LIST_PIPEPART); // CS_IGNORE_END private PipePartDetection() { // utility class => hidden } private static <E extends PipePart> List<Class<E>> getAllPipeParts( final String subPackage) { final List<Class<E>> res = new ArrayList<Class<E>>(); // Get the full name of the package final String pkg = PipePartDetection.class.getPackage().getName() + "." + subPackage; // Get all paths which may contain classes for this package Log.detail("Searching PipeParts of sub-package '%s' resolved to '%s'", subPackage, pkg); Enumeration<URL> resources; try { resources = Thread.currentThread().getContextClassLoader() .getResources(pkg.replace(".", "/")); } catch (final IOException e) { Log.error(e, "Cannot list resource-paths of package '%s'", pkg); return res; } // Check all files in this paths (CLASS files and JAR archives) while (resources.hasMoreElements()) { String path; try { path = URLDecoder.decode(resources.nextElement().getFile(), "UTF-8"); } catch (final UnsupportedEncodingException e) { throw new InternalException("UTF-8 encoding not supported"); } Log.detail("Found resource path '%s'", path); final File dir = new File(path); if (dir.isDirectory()) addFromDirectory(res, pkg, dir); else if (path.startsWith("file:") && path.contains("!")) addFromArchive(res, pkg, path); else throw new RuntimeException(String.format( "Don't know how to list the contents of the " + "resource-path '%s'", path)); } Log.detail("Searching resulted in '%s'", res); return res; } private static <E extends PipePart> void addFromDirectory( final List<Class<E>> list, final String pkg, final File dir) { Log.detail("Looking in directory '%s'", dir); final File[] files = dir.listFiles(); if (files == null) Log.error("Cannot list the contents of " + "resource-directory '%s'", dir); else for (final File file : files) { final Class<E> cls = loadClass(pkg, file.getName()); if (cls != null) list.add(cls); } } private static <E extends PipePart> void addFromArchive( final List<Class<E>> list, final String pkg, final String path) { Log.detail("Looking in archive '%s'", path); String jarPath = path.substring(5); final String jarPrefix = jarPath .substring(jarPath.lastIndexOf('!') + 2); Log.detail("Using prefix '%s'", jarPrefix); jarPath = jarPath.substring(0, jarPath.lastIndexOf('!')); try { final JarFile jar = new JarFile(new File(jarPath)); final Enumeration<JarEntry> content = jar.entries(); Log.detail("Archive '%s' contains %d entries", jar.getName(), jar.size()); while (content.hasMoreElements()) { final JarEntry entry = content.nextElement(); final String name = entry.getName(); if (name.startsWith(jarPrefix)) { final Class<E> cls = loadClass(pkg, name.substring(name.lastIndexOf('/') + 1)); if (cls != null) list.add(cls); } } jar.close(); } catch (final IOException e) { Log.error(e, "Cannot list classes in JAR-Archive '%s'", jarPath); } } @SuppressWarnings("unchecked") private static <E extends PipePart> Class<E> loadClass(final String pkg, final String fileName) { Log.detail("Loading class from '%s' in '%s'", fileName, pkg); if (!fileName.endsWith(".class")) return null; final String clsName = fileName.substring(0, fileName.length() - 6); try { final Class<E> cls = (Class<E>) Class.forName(pkg + '.' + clsName); if (!PipePart.class.isAssignableFrom(cls)) return null; if (Modifier.isAbstract(cls.getModifiers())) return null; final Method m = getHelp(cls); if (!Modifier.isStatic(m.getModifiers())) throw new NoSuchMethodException(String.format( "Method is not static: '%s'", m)); if (!Modifier.isPublic(m.getModifiers())) throw new NoSuchMethodException(String.format( "Method is not public: '%s'", m)); if (!m.getReturnType().equals(String.class)) throw new NoSuchMethodException(String.format( "Method doesn't return String: '%s'", m)); return cls; } catch (final Exception e) { // CS_IGNORE // we need to catch all here, because there are too many // this which may go wrong during class loading to handle each one Log.error(e, "Cannot load class '%s' in '%s'", clsName, pkg); return null; } } public static Method getHelp(final Class<? extends PipePart> cpp) throws NoSuchMethodException { try { return cpp.getMethod("help", HelpKind.class); } catch (final NoSuchMethodException e) { throw new NoSuchMethodException(String.format( "No 'public static String help(HelpKind)'" + " in '%s'", cpp)); } } public static String callHelp(final Class<? extends PipePart> cpp, final HelpKind kind) { try { return (String) PipePartDetection.getHelp(cpp).invoke(null, kind); } catch (final IllegalArgumentException e) { Log.error(e); } catch (final IllegalAccessException e) { Log.error(e); } catch (final InvocationTargetException e) { Log.error(e); } catch (final NoSuchMethodException e) { Log.error(e); } return ""; } public static void checkStaticValidity() { for (final Class<? extends PipePart> cpp : LIST_PIPEPART) checkStaticValidity(cpp); } public static void checkStaticValidity(final Class<? extends PipePart> cpp) { final List<String> res = new ArrayList<String>(); checkString(cpp, HelpKind.Name, res); checkString(cpp, HelpKind.Description, res); final String helpFile = PipePart.getHelpFile(cpp); if (!HelpLoader.isHelpAvailable(helpFile)) res.add(String.format("Help-file '%s' does not exist", helpFile)); final Icon icon = PipePart.getIcon(cpp); if (icon == null) res.add("Icon does not exist or could not be loaded"); final Icon image = PipePart.getConfigImage(cpp); if (image == null) res.add("Config-Image does not exist or could not be loaded"); try { final PipePart pp = cpp.newInstance(); final int ci1 = HelpKind.Config1.ordinal(); final int ciL = Math.min(ci1 + pp.getGuiConfigs().size(), HelpKind.values().length); for (int i = ci1; i < ciL; ++i) checkString(cpp, HelpKind.values()[i], res); for (int i = ciL; i < ci1 + pp.getGuiConfigs().size(); ++i) res.add(String.format("No Config enum available for '%s'", pp .getGuiConfigs().get(i))); for (int i = ciL; i < HelpKind.values().length; ++i) if (callHelp(cpp, HelpKind.values()[i]) != null) res.add(String.format("Config enum not referenced by GUI " + "defined: '%s'", HelpKind.values()[i])); } catch (final InstantiationException e) { res.add(e.toString()); } catch (final IllegalAccessException e) { res.add(e.toString()); } if (!res.isEmpty()) { final StringBuilder sb = new StringBuilder(); sb.append("Failed static checks for PipePart \""); sb.append(PipePart.getName(cpp)); sb.append("\" - "); sb.append(cpp.getName()); sb.append(":"); for (final String s : res) { sb.append("\n"); sb.append(s); } Log.warn(sb.toString()); } } private static void checkString(final Class<? extends PipePart> cpp, final HelpKind hk, final List<String> res) { final String s = callHelp(cpp, hk); if (s == null) res.add(String.format("callHelp() for '%s' is null", hk)); else if (s.isEmpty()) res.add(String.format("callHelp() for '%s' is empty", hk)); else if (s.contains("?")) res.add(String.format("callHelp() for '%s' contains '?'", hk)); } }