/*******************************************************************************
gocha.org-lib-java Библеотека общего назначения
(с) Камнев Георгий Павлович 2009 GPLv2
Данная программа является свободным программным обеспечением. Вы вправе
распространять ее и/или модифицировать в соответствии с условиями версии 2
либо по вашему выбору с условиями более поздней версии
Стандартной Общественной Лицензии GNU, опубликованной Free Software Foundation.
Мы распространяем данную программу в надежде на то, что она будет вам полезной,
однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ,
в том числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ
и ПРИГОДНОСТИ ДЛЯ ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ.
Для получения более подробной информации ознакомьтесь
со Стандартной Общественной Лицензией GNU.
Вместе с данной программой вы должны были получить экземпляр
Стандартной Общественной Лицензии GNU.
Если вы его не получили, сообщите об этом в Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*******************************************************************************/
package org.gocha.gui;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.gocha.collection.map.ReadOnlyMap;
import org.gocha.common.ExitListener;
import org.gocha.common.ExitEvent;
import org.gocha.files.FileUtil;
import org.gocha.xml.XMLUtil;
/**
* Глобальный класс GUI приложения.
* <p>
* Данный класс предназначен как единственный экземпляр приложения,
* который отмечает начало и завершение работы приложения.<br/>
* К прослушиванию события выхода можно подцепить любой объект реализующий <b>ExitListener.</b>
* Начало работы приложения отмечается при помощи метода <b>start()</b>, завершение при помощи <b>fireExit()</b>
* </p><p>
* Также можно подципить объекты к <b>AutoClose</b>, которые должны быть автоматически закрыти при получении
* сообщения выхода (<i>fireExit()</i>)
* </p><p>
* Для каждого отдельного прилоежния подразумевает свой контекст,
* который связан с соответствующей пользовательской директорией.<br/>
* Контекст прдставляется как имя класса с указанием пакета. Например: <b>myorg.app.ver</b>.
* При запросе рабочего каталога приложения
* Соответственно в каталоге пользователя будут создан каталог myorg/app/ver (если его еще нет)
* </p><p>
* При вызове метода <i>start()</i> можно указать параметры:
* <table border="1">
* <tr>
* <td><b>Параметр</b></td>
* <td><b>Описание</b></td>
* <td><b>Примеры</b></td>
* </tr>
* <tr>
* <td><b>-appdir <i>путь_к_каталогу</i></b></td>
* <td>Указывает контекст приложения - путь к каталогу.</td>
* <td>
* Пример unix:<br/>
* <b>-appdir "/home/user/applicationDataDir"</b><br/><br/>
* Пример windows:<br/>
* <b>-appdir "C:\\Documents and settings\\user\\applicationDataDir"</b>
* </td>
* </tr>
* <tr>
* <td><b>-webapp <i>on|off|true|false</i></b></td>
* <td>
* При значении <i>on</i> или <i>true</i> -
* указывает на необходимоть работы в режиме java webstart.
* В этом режиме метод <i>getLocalApplicationDirectory()</i> - Возвращает пустую ссылку
* </td>
* <td>
* Пример 1:<br/>
* <b>-webapp "on"</b>
* <br/><br/>
* Пример 2:<br/>
* <b>-webapp "false"</b>
* </td>
* </tr>
* <tr>
* <td><b>-auto <i>Класс{;Класс}</i></b></td>
* <td>Указывает на список классов которые необходимо создать после вызова start()</td>
* <td>
* Пример: <br>
* 1. <b>-auto "org.app.pluginA"</b> - Создаст при вызове start() класс org.app.pluginA<br/>
* 2. <b>-auto "org.app.PluginA;org.app.PluginB"</b>
* - Создаст при вызове start() классы org.app.PluginA затем org.app.PluginB
* </td>
* </tr>
* <tr>
* <td>
* <b>-argsFile <i>Имя файла</i></b>
* </td>
* <td>
* Указывает файл с дополнительными параметрами, которые не поместились в коммандную строку.<br />
* Файл должен быть в формате XML:<br/>
*<code>
*<start><br/>
* <arg name="имя параметра без тире" value="значение" /><br/>
*</start>
*</code>
* </td>
* <td>
* -argsFile "some_file.xml"
* </td>
* </tr>
* <tr>
* <td>
* <b>-argsFileCS <i>Кодировка файла</i></b>
* </td>
* <td>
* Указывает кодировку файла с дополнительными параметрами.
* По умолчанию испольуется UTF-8
* </td>
* <td>
* -argsFileCS "UTF-8"
* </td>
* </tr>
* </table>
* </p>
* @see org.gocha.gui.ApplicationGlobal#start(java.lang.String[])
* <b>start()</b> - Отмечает стар приложения <br/>
*
* @see org.gocha.gui.ApplicationGlobal#fireExit(java.lang.Object)
* <b>fireExit()</b> - Отмечает завершение работы приложения <br/>
*
* @see org.gocha.gui.ApplicationGlobal#getLocalApplicationDirectory()
* <b>getLocalApplicationDirectory()</b> - Возвращает каталог (контекст) приложения <br/>
*
* @see org.gocha.common.ExitListener
* <b>ExitListener</b> - Слушает выход из приложения <br/>
*
* @see org.gocha.gui.AutoClose
* <b>AutoClose</b> -
* Класс/Сиглетон слушает сообщения выхода приложения
* и закрывает при выходе указанные объекты.
* Своебразный адаптер для закрытия объектов<br/>
* @author gocha
*/
public class ApplicationGlobal
{
/**
* Конструктор закрыт для непосредственного вызова.
* Вместо этого предлогается использовать статичный метод instance()
* @see org.gocha.gui.ApplicationGlobal#instance()
*/
protected ApplicationGlobal()
{
this.addExitListener(createAutoClose());
}
/**
* Создает/Возвращает объект который закрывает обекты при выходе
* @return Автоматический закрыватель
*/
protected AutoClose createAutoClose()
{
return AutoClose.instance();
}
protected static GuiFactory factory = null;
/**
* Возвращает фабрику создающую объект ApplicationGlobal
* @return Фабрика классов
*/
public static GuiFactory getFactory()
{
if( factory == null )factory = new DefaultFactory();
return factory;
}
/**
* Устанавливает фабрику создающую объект ApplicationGlobal
* @param factory Фабрика классов
*/
public static void setFactory(GuiFactory factory)
{
ApplicationGlobal.factory = factory;
}
/**
* Создает экземпляр ApplicationGlobal
* @return экземпляр ApplicationGlobal
*/
static ApplicationGlobal create(){ return new ApplicationGlobal(); }
protected static ApplicationGlobal inst = null;
/**
* Возвращает экземпляр ApplicationGlobal.
* Доавбляет слушателя AutoClose.instance() на завершение работыю
* @return экземпляр ApplicationGlobal
*/
public static ApplicationGlobal instance()
{
if( inst==null )
{
inst = getFactory().createGlobal();
if( inst!=null )inst.addExitListener(AutoClose.instance());
}
return inst;
}
protected Collection exitListeners = new HashSet();
protected Map<String,String> programArgs = new HashMap<String, String>();
/**
* Возвращает параметры указанные при запуске программы.
* <p>
* Данные параметры доступны только для чтения.
* Параметры разделяются на ключ/значение.
* Признаком ключа является символ тире "<b>-</b>".
* </p><p>
* В данной коллекции ключ указан без тире.
* </p>
* @return параметры указанные при запуске программы
*/
public Map<String, String> getProgramAruments()
{
return programArgs;
}
protected Boolean webstartApp = null;
/**
* Указывает что программа была запущена в режиме Java web start.
* <p>
* На данный признак влияет параметр коммандой строки <b>-webapp "on"</b> либо <b>-webapp "true"</b>
* </p>
* @return Указывает что программа была запущена в режиме Java web start.
*/
public boolean isWebStartApplication()
{
if( webstartApp==null )
{
if( getProgramAruments().containsKey(WEBSTART_APPLICATION) )
{
String v = getProgramAruments().get(WEBSTART_APPLICATION);
webstartApp = v.equalsIgnoreCase("on") || v.equalsIgnoreCase("true");
}else{
webstartApp = false;
}
}
return webstartApp;
}
protected List<Class> autoClasses = new ArrayList<Class>();
/**
* Классы которые автоматически создаются при вызове start()
* @return Автоматически создаваемые классы
*/
public List<Class> getAutoCreateClasses()
{
return autoClasses;
}
protected File localApplicationDirectory = null;
/**
* Директория данных программы.
* <p>
* На данное значение влияет параметр ком. строки LOCAL_APPLICATION_DIRECTORY (-appdir).
* Если он не указан, то используется домашняя директория пользователя + контекст.
* Если запрещен доступ к локальным данным (режим WEB), то возвращает null
* </p>
* @return Директория данных программы или null
* @see #getContext()
*/
public File getLocalApplicationDirectory()
{
if( isWebStartApplication() )return null;
if( localApplicationDirectory==null )
{
if( getProgramAruments().containsKey(LOCAL_APPLICATION_DIRECTORY) )
{
File appDir = new File(getProgramAruments().get(LOCAL_APPLICATION_DIRECTORY));
if( appDir.exists() && appDir.isDirectory() )
{
localApplicationDirectory = appDir;
}
}else
{
try
{
String userHomePath = org.gocha.files.FileSystemInfo.getUserHomePath();
if( userHomePath!=null )
{
File userHome = new File(userHomePath);
if( userHome.exists() && userHome.isDirectory() && context!=null )
{
String ctx = context;
if(ctx.startsWith(".")&&ctx.length()>1)
{
ctx = ctx.substring(1);
}
String[] ctxDirNames = ctx.split("\\.");
if( context.startsWith(".")
&& ctxDirNames.length>0
&& !ctxDirNames[0].startsWith(".") )
{
ctxDirNames[0] = "." + ctxDirNames[0];
}
File appDir = userHome;
for( String ctxD : ctxDirNames )
{
appDir = new File(appDir,ctxD);
}
// File appDir = new File(userHome, ".org");
// appDir = new File(appDir, "gocha");
// appDir = new File(appDir, "notes");
if( appDir.exists() && appDir.isDirectory() )
{
localApplicationDirectory = appDir;
}else{
if( !appDir.exists() )
{
if( appDir.mkdirs() )
{
localApplicationDirectory = appDir;
}
}
}
}
}
} catch (Exception ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return localApplicationDirectory;
}
/**
* Параметр ком. строки указывающий директорию данных программы
*/
public static final String LOCAL_APPLICATION_DIRECTORY = "appdir";
/**
* Параметр ком. строки указывающий режим веб приложения<br/>
* <b>-webapp "on"</b> либо <b>-webapp "true"</b>
*/
public static final String WEBSTART_APPLICATION = "webapp";
/**
* Параметр ком. строки указывающий какие классы создавать.
* <p>
* Пример: <br>
* 1. <b>-auto "org.app.pluginA"</b> - Создаст при вызове start() класс org.app.pluginA<br/>
* 2. <b>-auto "org.app.PluginA;org.app.PluginB"</b>
* - Создаст при вызове start() классы org.app.PluginA затем org.app.PluginB
* </p>
*/
public static final String AUTOCLASSES = "auto";
protected static String context = null;
/**
* Возвращает контекст приложения
* @return Контекст приложения
* @see #setContext(java.lang.String)
*/
public static String getContext() {
return context;
}
/**
* Устанавливает контекст приложения.<br/><br/>
* Контекст - Определяет каталог в домашней директории пользователя где храняться настройки,
* если не указанно явно через коммандную строку <b>-appdir</b>.<br/>
* Например: контекст указан как: <b>.mydomain.ru.myapplication.ver1</b>
* то соответственно каталог будет:<b>~/.mydomain/ru/myapplication/ver1</b>
* @param context Контекст приложения
* @see #LOCAL_APPLICATION_DIRECTORY
*/
public static void setContext(String context) {
ApplicationGlobal.context = context;
}
/**
* Устанавливает контекст приложения
* @param context Контекст приложения
* @see #setContext(java.lang.String)
*/
public static void setContext(Class<?> context) {
if( context!=null )
{
ApplicationGlobal.context = "."+context.getName();
}
}
/**
* Параметр ком. строки указывающий на файл XML с дополнительными параметрами <b>start()</b><br/>
* Этот файл объединяется с текущими параметрами командной строки.
* @see org.gocha.gui.ApplicationGlobal#start(java.lang.String[])
* @see org.gocha.gui.ApplicationGlobal#ARGUMENT_FILE_CS
*/
public static final String ARGUMENT_FILE = "argsFile";
/**
* Указывает какую кодировку использовать для файла указаного параметром <b>argsFile</b>.
* По уполчанию используется кодировка <b>UTF8</b>.
* @see org.gocha.gui.ApplicationGlobal#ARGUMENT_FILE
*/
public static final String ARGUMENT_FILE_CS = "argsFileCS";
/**
* Отмечает стар приложения
* @param args Аргумент программы
* @see org.gocha.gui.ApplicationGlobal#ARGUMENT_FILE
* @see org.gocha.gui.ApplicationGlobal#ARGUMENT_FILE_CS
* @see org.gocha.gui.ApplicationGlobal#AUTOCLASSES
* @see org.gocha.gui.ApplicationGlobal#LOCAL_APPLICATION_DIRECTORY
* @see org.gocha.gui.ApplicationGlobal#WEBSTART_APPLICATION
*/
public void start(String[] args)
{
if (args == null) {
throw new IllegalArgumentException("args == null");
}
for( int i=0; i<args.length-1; i++ )
{
String arg = args[i];
String val = args[i+1];
if( arg.startsWith("-") && arg.length()>1 )
{
programArgs.put(arg.substring(1), val);
}
}
if( programArgs.containsKey(ARGUMENT_FILE) )
{
String cs = programArgs.containsKey(ARGUMENT_FILE_CS) ? programArgs.get(ARGUMENT_FILE_CS) : "UTF8";
String xsl = FileUtil.readAllText(ApplicationGlobal.class.getResource("startupArguments.xsl"), "UTF8");
String xml = FileUtil.readAllText(programArgs.get(ARGUMENT_FILE), cs);
if( xsl!=null && xml!=null )
{
xml = XMLUtil.toStringXSLT(xsl, xml);
if( xml!=null )
{
Object o = FileUtil.readXMLBeanFromStirng(xml,null);
if( o!=null && o instanceof Map )
{
for( Object key : ((Map)o).keySet() )
{
Object val = ((Map)o).get(key);
if( key==null || val==null )continue;
if( key instanceof String && val instanceof String )
{
programArgs.put((String)key, (String)val);
}
}
}
}
}
}
programArgs = new ReadOnlyMap<String, String>(programArgs);
createAutoClasses();
}
/**
* Добавляет класс в список автоматически создаваемых
* @param c Класс
*/
protected void addAutoCreatedClass(Class c)
{
getAutoCreateClasses().add(c);
}
/**
* Создает классы указанные в списке автоматически создаваемых
*/
protected void createAutoClasses()
{
String auto = getProgramAruments().containsKey(AUTOCLASSES) ?
getProgramAruments().get(AUTOCLASSES) : null;
if( auto!=null )
{
String[] autoCreate = auto.split(";");
for( String className : autoCreate )
{
try
{
Class c = Class.forName(className);
if( c!=null )addAutoCreatedClass(c);
} catch (ClassNotFoundException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
for( Class c : getAutoCreateClasses() )
{
try
{
if( c==null )continue;
Constructor constr = c.getConstructor();
try
{
constr.newInstance();
} catch (InstantiationException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalArgumentException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvocationTargetException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (NoSuchMethodException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
Logger.getLogger(ApplicationGlobal.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* Отмечает завершение работы приложения. Рассылает всем подпищикам о завершении работы.
* @param source Кто послал сообщение о завершении работы
*/
public void fireExit(Object source)
{
ExitEvent evnt = new ExitEvent(source);
for( Object o : exitListeners.toArray() )
{
if( o!=null && o instanceof ExitListener )
((ExitListener)o).exitEvent(evnt);
}
}
/**
* Добавляет подписчика на событие выхода
* @param listener Подписчик
*/
public void addExitListener(ExitListener listener)
{
if( listener!=null )exitListeners.add(listener);
}
/**
* Удаляет подписчика от события выхода
* @param listener Подписчик
*/
public void removeExitListener(ExitListener listener)
{
exitListeners.remove(listener);
}
}