// Copyright 2012 Georg-August-Universität Göttingen, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package de.ugoe.cs.autoquest.eventcore.guimodel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import java.util.logging.Level;
import de.ugoe.cs.autoquest.eventcore.EventTargetModelConfigurationException;
import de.ugoe.cs.autoquest.eventcore.HierarchicalEventTargetModel;
import de.ugoe.cs.autoquest.eventcore.IEventTargetFactory;
import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec;
import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget;
import de.ugoe.cs.util.console.Console;
/**
*
* Creates {@link IGUIElement}s from a given specification. Implemented as singleton.
*
*
* @version 1.0
* @author Patrick Harms
*/
public class GUIElementFactory implements IEventTargetFactory {
/**
*
* Instance of the class (singleton)
*
*/
private static GUIElementFactory instance = new GUIElementFactory();
/**
*
* Constructor. Creates a new GUIElementFactory. Private to preserve singleton property.
*
*/
private GUIElementFactory() {}
/**
*
* Returns the instance of this class.
*
*
* @return the instance
*/
public static synchronized GUIElementFactory getInstance() {
return instance;
}
/**
*
* A property mapping that defines which Java class is created given the type of the GUI
* element found in the specification.
*
*/
private Properties mappingsFromConfiguration;
/* (non-Javadoc)
* @see IEventTargetFactory#instantiateEventTarget(IEventTargetSpec, IHierarchicalEventTarget)
*/
@SuppressWarnings("unchecked")
@Override
public T instantiateEventTarget(IEventTargetSpec specification,
T parent)
throws EventTargetModelConfigurationException
{
if (!(specification instanceof IGUIElementSpec)) {
throw new IllegalArgumentException("can only handle IGUIElementSpecs as specification");
}
if ((parent != null) && !(parent instanceof IGUIElement)) {
throw new IllegalArgumentException("can only handle IGUIElements as parent");
}
return (T) instantiateEventTarget((IGUIElementSpec) specification, (IGUIElement) parent);
}
/**
* concrete implementation of {@link #instantiateEventTarget(IEventTargetSpec, IHierarchicalEventTarget)}
*/
public IGUIElement instantiateEventTarget(IGUIElementSpec specification,
IGUIElement parent)
throws EventTargetModelConfigurationException
{
Properties mappings = getMappingsFromConfiguration();
IGUIElement guiElement = null;
String[] typeHierarchy = specification.getTypeHierarchy();
int i = 0;
String className = null;
while ((className == null) && (i < typeHierarchy.length)) {
className = mappings.getProperty(typeHierarchy[i]);
i++;
}
if (className != null) {
try {
Class> clazz = this.getClass().getClassLoader().loadClass(className);
if (!IGUIElement.class.isAssignableFrom(clazz)) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " is no valid GUIElement derivate.");
return null;
}
Constructor> constructor = null;
Class> parentClass = (parent == null) ? null : parent.getClass();
// search for a constructor, that perfectly matches the types
for (Constructor> candidate : clazz.getConstructors()) {
if ((parentClass != null) && (candidate.getParameterTypes().length == 2) &&
(candidate.getParameterTypes()[0].equals(specification.getClass())) &&
(candidate.getParameterTypes()[1].equals(parentClass)))
{
constructor = candidate;
break;
}
else if (parentClass == null) {
if ((candidate.getParameterTypes().length >= 1) &&
(candidate.getParameterTypes()[0].equals(specification.getClass())))
{
constructor = candidate;
break;
}
}
}
if (constructor == null) {
// search for an assignable constructor
for (Constructor> candidate : clazz.getConstructors()) {
if ((candidate.getParameterTypes().length == 2) &&
(candidate.getParameterTypes()[0].isInstance(specification)) &&
(candidate.getParameterTypes()[1].isInstance(parent)))
{
constructor = candidate;
break;
}
}
}
if (constructor != null) {
guiElement = (IGUIElement) constructor.newInstance(specification, parent);
}
else {
throw new NoSuchMethodException
("no constructor with two parameters and assignable parameter types for " +
specification.getClass() + " and " +
(parent != null ? parent.getClass() : "null") + " found in class " +
clazz);
}
}
catch (ClassNotFoundException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " can not be loaded.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" can not be loaded.", e);
}
catch (SecurityException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " can not be instantiated due to security reasons.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" can not be instantiated due to security reasons.", e);
}
catch (NoSuchMethodException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " does not provide an appropriate constructor.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" does not provide an appropriate constructor.", e);
}
catch (IllegalArgumentException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " does not provide an appropriate constructor " +
"accepting the provided parameters.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className + " does not " +
"provide an appropriate constructor accepting the provided parameters.", e);
}
catch (InstantiationException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " can not be instantiated.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" can not be instantiated.", e);
}
catch (IllegalAccessException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " can not be instantiated.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" can not be instantiated.", e);
}
catch (InvocationTargetException e) {
Console.traceln(Level.WARNING, "configured GUI element representing class " +
className + " can not be instantiated.");
throw new EventTargetModelConfigurationException
("configured GUI element representing class " + className +
" can not be instantiated.", e);
}
}
if (guiElement == null ) {
Console.traceln(Level.WARNING, "no class representing GUI elements of type " +
specification.getType() + " found. Please extend GUI element " +
"mapping files.");
throw new EventTargetModelConfigurationException
("no class representing GUI elements of type " + specification.getType() +
" found. Please extend GUI element mapping files");
}
return guiElement;
}
/* (non-Javadoc)
* @see IEventTargetFactory#instantiateGroup(String, IHierarchicalEventTarget, HierarchicalEventTargetModel)
*/
@SuppressWarnings("unchecked")
@Override
public T instantiateGroup(String groupName,
T parent,
HierarchicalEventTargetModel hierarchicalEventTargetModel)
{
if (!(hierarchicalEventTargetModel instanceof GUIModel)) {
throw new IllegalArgumentException("can only handle GUI elements as event targets");
}
if (!(parent instanceof IGUIElement)) {
throw new IllegalArgumentException("can only handle GUI elements as event targets");
}
return (T) new GUIElementGroup
(groupName, (IGUIElement) parent, (GUIModel) hierarchicalEventTargetModel);
}
/**
*
* Loads the mappings for GUI elements. All files that start with "guimapping", end
* with ".txt", and are located in the folder "data/guimappings" (relative
* to the working directory) are loaded.
*
*
* @return loaded GUI mappings
*/
private synchronized Properties getMappingsFromConfiguration()
throws EventTargetModelConfigurationException
{
if (mappingsFromConfiguration != null) {
return mappingsFromConfiguration;
}
else {
mappingsFromConfiguration = new Properties();
File mappingsFolder = new File("data/guimappings");
File[] children = mappingsFolder.listFiles();
if (children != null) {
for (File mappingsFile : children) {
if (!mappingsFile.isDirectory() &&
mappingsFile.getName().startsWith("guimapping") &&
mappingsFile.getName().endsWith(".txt"))
{
InputStream inStream = null;
try {
inStream = new FileInputStream(mappingsFile);
mappingsFromConfiguration.load(inStream);
}
catch (FileNotFoundException e) {
throw new EventTargetModelConfigurationException
("could not read mapping configuration file " + mappingsFile, e);
}
catch (IOException e) {
throw new EventTargetModelConfigurationException
("could not read mapping configuration file " + mappingsFile, e);
}
finally {
if (inStream != null) {
try {
inStream.close();
}
catch (IOException e) {
// ignore
}
}
}
}
}
}
else {
throw new EventTargetModelConfigurationException
("no GUI mappings file provided in folder " + mappingsFolder);
}
return mappingsFromConfiguration;
}
}
}