/** * Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP). * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package net.ctdp.rfdynhud.widgets.base.widget; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import net.ctdp.rfdynhud.util.RFDHLog; import net.ctdp.rfdynhud.widgets.WidgetsConfiguration; import org.jagatoo.util.classes.ClassSearcher; import org.jagatoo.util.classes.SuperClassCriterium; /** * Creates {@link Widget} instances by class name. * * @author Marvin Froehlich (CTDP) */ public class WidgetFactory { private static List<Class<Widget>> widgetClasses = null; private static Map<String, Class<Widget>> widgetClassNameClassMap = null; private static Collection<String> excludedJars = null; public static void setExcludedJars( Collection<String> excludedJars ) { WidgetFactory.excludedJars = excludedJars; } private static void findWidgetSetJars( File folder, List<URL> jars ) { for ( File f : folder.listFiles() ) { if ( f.isDirectory() ) { if ( !f.getName().equals( ".svn" ) ) findWidgetSetJars( f, jars ); } else { String filenameLC = f.getAbsolutePath().toLowerCase(); if ( filenameLC.endsWith( ".jar" ) ) { boolean accepted = true; if ( excludedJars != null ) { for ( String exclJar : excludedJars ) { if ( !accepted ) break; if ( filenameLC.endsWith( exclJar ) ) accepted = false; } } if ( accepted ) { try { jars.add( f.toURI().toURL() ); } catch ( Throwable t ) { RFDHLog.exception( t ); } } } } } } private static URL[] findWidgetSetJars( File widgetSetsFolder ) { ArrayList<URL> jars = new ArrayList<URL>(); for ( File f : widgetSetsFolder.listFiles() ) { if ( f.isDirectory() && !f.getName().equals( ".svn" ) ) { findWidgetSetJars( f, jars ); } } URL[] urls = new URL[ jars.size() ]; return ( jars.toArray( urls ) ); } /** * Finds all publicly available and non-abstract {@link Widget} classes. * * @param widgetSetsFolder * * @return all non-abstract {@link Widget} classes in the classpath. */ @SuppressWarnings( "unchecked" ) private static List<Class<Widget>> findWidgetClasses( File widgetSetsFolder ) { List<Class<?>> classes_; if ( ( widgetSetsFolder != null ) && widgetSetsFolder.exists() ) { URLClassLoader classLoader = new URLClassLoader( findWidgetSetJars( widgetSetsFolder ), Widget.class.getClassLoader() ); try { classes_ = ClassSearcher.findClasses( true, classLoader, new SuperClassCriterium( Widget.class, false ) ); } catch ( IOException e ) { RFDHLog.exception( e ); classes_ = null; } } else { classes_ = ClassSearcher.findClasses( new SuperClassCriterium( Widget.class, false ) ); } ArrayList<Class<Widget>> classes = new ArrayList<Class<Widget>>(); for ( int i = 0; i < classes_.size(); i++ ) { Class<Widget> clazz = (Class<Widget>)classes_.get( i ); if ( !clazz.isAnnotationPresent( Hidden.class ) ) classes.add( clazz ); } Collections.sort( classes, new Comparator<Class<Widget>>() { @Override public int compare( Class<Widget> o1, Class<Widget> o2 ) { return ( String.CASE_INSENSITIVE_ORDER.compare( o1.getSimpleName(), o2.getSimpleName() ) ); } } ); //return ( classes.toArray( new Class[ classes.size() ] ) ); return ( classes ); } public static void init( File widgetSetsFolder ) { if ( widgetClasses != null ) return; widgetClasses = findWidgetClasses( widgetSetsFolder ); widgetClassNameClassMap = new HashMap<String, Class<Widget>>(); for ( Class<Widget> clazz : widgetClasses ) { widgetClassNameClassMap.put( clazz.getName(), clazz ); } } @SuppressWarnings( "unchecked" ) public static final Class<Widget>[] getWidgetClasses() { return ( widgetClasses.toArray( new Class[ widgetClasses.size() ] ) ); } private static final Throwable getRootCause( Throwable t ) { if ( t.getCause() == null ) return ( t ); return ( getRootCause( t.getCause() ) ); } /** * Creates a new {@link Widget} instance. * * @param clazz the {@link Widget} class * @param name the name to be given to the new {@link Widget} or <code>null</code> * @param widgetsConfig the {@link WidgetsConfiguration} to search a free name in * @param loadingAssembled * * @return the create {@link Widget} or <code>null</code> on error. */ private static Widget createWidget( Class<Widget> clazz, String name, WidgetsConfiguration widgetsConfig, boolean loadingAssembled ) { Widget widget = null; try { if ( loadingAssembled ) widget = clazz.getConstructor( boolean.class ).newInstance( false ); else widget = clazz.getConstructor().newInstance(); } catch ( Throwable t ) { RFDHLog.exception( getRootCause( t ) ); return ( null ); } if ( name != null ) { widget.setName( name ); } else if ( widgetsConfig != null ) { widget.setName( widgetsConfig.findFreeName( clazz.getSimpleName() ) ); } return ( widget ); } private static String getOldClassName( String className ) { if ( className.startsWith( "net.ctdp.rfdynhud." ) ) { if ( className.startsWith( "etv2010.widgets.", "net.ctdp.rfdynhud.".length() ) ) return ( "net.ctdp.rfdynhud.widgets.etv2010." + className.substring( "net.ctdp.rfdynhud.etv2010.widgets.".length() ) ); if ( className.startsWith( "widgets.", "net.ctdp.rfdynhud.".length() ) ) { if ( className.startsWith( "controls.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "dashboard.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "fuel.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "fuelneedle.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "image.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "map.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "misc.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "revmeter.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "rideheight.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "speedo.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "standings.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "startinglight.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "temperatures.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "timecomp.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "timing.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "trackposition.", "net.ctdp.rfdynhud.widgets.".length() ) || className.startsWith( "wear.", "net.ctdp.rfdynhud.widgets.".length() ) ) return ( "net.ctdp.rfdynhud.widgets.standard." + className.substring( "net.ctdp.rfdynhud.widgets.".length() ) ); } } return ( null ); } /** * Gets the {@link Widget} {@link Class} instance. * * @param className the {@link Widget} class name * * @return the {@link Widget} {@link Class} instance or <code>null</code> on error. */ public static Class<Widget> getWidgetClass( String className ) { Class<Widget> clazz = widgetClassNameClassMap.get( className ); if ( clazz == null ) { // branch for backwards compatiblity with old package names... String oldClassName = getOldClassName( className ); if ( oldClassName == null ) { return ( null ); } clazz = widgetClassNameClassMap.get( oldClassName ); } return ( clazz ); } /** * Creates a new {@link Widget} instance. * * @param className the {@link Widget} class name * @param name the name to be given to the new {@link Widget} or <code>null</code> * @param widgetsConfig the {@link WidgetsConfiguration} to search a free name in * * @return the create {@link Widget} or <code>null</code> on error. */ private static Widget createWidget( String className, String name, WidgetsConfiguration widgetsConfig ) { Class<Widget> clazz = getWidgetClass( className ); if ( clazz == null ) return ( null ); return ( createWidget( clazz, name, widgetsConfig, false ) ); } /** * Creates a new {@link Widget} instance. * * @param clazz the {@link Widget} class * @param name the name to be given to the new {@link Widget} or <code>null</code> * * @return the create {@link Widget} or <code>null</code> on error. */ public static Widget createWidget( Class<Widget> clazz, String name ) { if ( name == null ) throw new IllegalArgumentException( "name must not be null" ); return ( createWidget( clazz, name, null, false ) ); } /** * Creates a new {@link Widget} instance. * * @param clazz the {@link Widget} class * @param widgetsConfig the {@link WidgetsConfiguration} to search a free name in * * @return the create {@link Widget} or <code>null</code> on error. */ public static Widget createWidget( Class<Widget> clazz, WidgetsConfiguration widgetsConfig ) { if ( widgetsConfig == null ) throw new IllegalArgumentException( "widgetsConfig must not be null" ); return ( createWidget( clazz, null, widgetsConfig, false ) ); } /** * Creates a new {@link Widget} instance. * * @param className the {@link Widget} class name * @param name the name to be given to the new {@link Widget} or <code>null</code> * * @return the create {@link Widget} or <code>null</code> on error. */ public static Widget createWidget( String className, String name ) { if ( name == null ) throw new IllegalArgumentException( "name must not be null" ); return ( createWidget( className, name, null ) ); } /** * Creates a new {@link Widget} instance. * * @param className the {@link Widget} class name * @param widgetsConfig the {@link WidgetsConfiguration} to search a free name in * * @return the create {@link Widget} or <code>null</code> on error. */ public static Widget createWidget( String className, WidgetsConfiguration widgetsConfig ) { if ( widgetsConfig == null ) throw new IllegalArgumentException( "widgetsConfig must not be null" ); return ( createWidget( className, null, widgetsConfig ) ); } /** * Creates a new {@link Widget} instance. * * @param className the {@link Widget} class name * @param name the name to be given to the new {@link Widget} or <code>null</code> * * @return the create {@link Widget} or <code>null</code> on error. */ public static AbstractAssembledWidget createAssembledWidget( String className, String name ) { if ( name == null ) throw new IllegalArgumentException( "name must not be null" ); Class<Widget> clazz = getWidgetClass( className ); if ( clazz == null ) return ( null ); if ( !AbstractAssembledWidget.class.isAssignableFrom( clazz ) ) { RFDHLog.error( "ERROR: The given class " + className + " is not a sub class of " + AbstractAssembledWidget.class.getName() + "." ); return ( null ); } return ( (AbstractAssembledWidget)createWidget( clazz, name, null, true ) ); } private WidgetFactory() { } }