xdev.Application Maven / Gradle / Ivy
Show all versions of xapi Show documentation
package xdev;
/*-
* #%L
* XDEV Application Framework
* %%
* Copyright (C) 2003 - 2020 XDEV Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.applet.Applet;
import java.applet.AppletStub;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Window;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.Permission;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import xdev.event.ApplicationExitEvent;
import xdev.event.ApplicationExitListener;
import xdev.io.IOUtils;
import xdev.ui.GraphicUtils;
import xdev.ui.XdevApplicationContainer;
import xdev.ui.XdevFocusTraversalPolicy;
import xdev.ui.XdevFullScreen;
import xdev.ui.XdevMainFrame;
import xdev.ui.XdevWindow;
import xdev.ui.laf.LookAndFeel;
import xdev.ui.laf.LookAndFeelException;
import xdev.ui.laf.LookAndFeelManager;
import xdev.ui.laf.SystemLookAndFeel;
import xdev.util.Settings;
import xdev.util.XdevDate;
import xdev.util.logging.LoggerFactory;
import xdev.util.logging.LoggingConfiguration;
import xdev.util.logging.XdevLogger;
/**
*
* This is the entry point of the XDEV Application Framework.
*
* Desktop and webstart applications are started via the {@link #main(String[])}
* method, applets per default via {@link #init()} and {@link #start()}.
*
* The user application on top of the framework is started afterwards. This is
* because runtime environment specific init routines of the framwork are
* performed in this class.
*
* The system or program parameter 'main' is used to define the user
* application's main class. Ensure that this main class is in a package, not in
* the default package.
*
*
* Desktop application:
*
* java [vm args] -Dmain=myapp.Start xdev.Application [application args]
*
*
* Applet:
*
*
* <applet code="xdev.Application.class">
* <param name="main" value="myapp.Start" />
* <param...
* </applet>
*
*
*
* Webstart:
*
*
* <jnlp...
* ...
* <resources>
* <property name="application.type" value="webstart" />
* <property name="main" value="start.Main" />
* <property...
* </resources>
* ...
* <application-desc main-class="xdev.Application">
* <argument...
* </application-desc>
* ...
* </jnlp>
*
*
*
* @see #main(String[])
* @see #initApplication(String[])
* @see #startApplication()
* @see #isApplet()
* @see #isApplication()
* @see #isWebstart()
*
* @author XDEV Software
* @since 3.0
*/
public final class Application extends JApplet implements AppletStub, XdevApplicationContainer
{
/**
* @since 4.0
*/
public static enum State
{
INITIALIZING,
RUNNING,
REQUESTING_EXIT,
EXITING
}
/**
* Logger instance for this class.
*/
private static final XdevLogger log = LoggerFactory.getLogger(Application.class);
/**
* Application type constant.
*/
public final static int APPLICATION = 0;
/**
* Application type constant.
*/
public final static int APPLET = 1;
/**
* Application type constant.
*/
public final static int WEBSTART = 2;
private static int type = APPLICATION;
/**
* @return The application's type. Either {@link #APPLICATION},
* {@link #APPLET} or {@link #WEBSTART}
*/
public static int getType()
{
return type;
}
/**
* @return true
if this is a desktop application started via
* {@link #main(String[])}, false
otherwise
*/
public static boolean isApplication()
{
return type == APPLICATION;
}
/**
* @return true
if this is a applet running in a browser or
* another applet container, false
otherwise
*/
public static boolean isApplet()
{
return type == APPLET;
}
/**
* @return true
if this is a webstart application started via a
* webstart descriptor, false
otherwise
*/
public static boolean isWebstart()
{
return type == WEBSTART;
}
private static String[] args;
private static EventListenerList listenerList = new EventListenerList();
private static XdevApplicationContainer container = null;
private static Image appIcon = null;
private static boolean fullscreen = false;
private static long serverTimeDiff = 0;
private static boolean initialized = false;
private static Map extensions = new HashMap();
// @since 4.0
private static State state = State.INITIALIZING;
// private static XdevJobQueue jobQueue = new XdevJobQueue();
/**
* Returns the extension named name
or null
if not
* found.
*
* Note: The extensions are only available after {@link #init()} has been
* called.
*
* @param name
* the extension's name
* @return the appropriate extension or null
*/
public static Extension getExtension(String name)
{
return extensions.get(name);
}
/**
* Returns all available extensions.
*
* Note: The extensions are only available after {@link #init()} has been
* called.
*
* @return All available extensions.
*/
public static Extension[] getExtensions()
{
return extensions.values().toArray(new Extension[extensions.size()]);
}
/**
* This is the entry point of the XDEV Application Framwork for desktop and
* webstart appications.
*
* It initializes the framework and starts the application based on the
* framework.
*
* First {@link #initApplication(String[])} is called, then
* {@link #startApplication()}.
*
*
* Hint: Can be used to start {@link XdevWindow}s.
* Therefore set the VM Argument
* -Dmain=FULL_QUALIFIED_CLASSNAME
. Where
* FULL_QUALIFIED_CLASSNAME
is the full qualified classname
* like xdev.ui.MyClass.
*
*
* @param args
* The application's arguments
*/
public static void main(String[] args)
{
LoggingConfiguration.readConfiguration();
checkManifests();
initApplication(args);
startApplication();
}
/**
* Reads non-standard attributes from the jar-manifests.
*/
private static void checkManifests()
{
try
{
ClassLoader loader = Application.class.getClassLoader();
for(Enumeration e = loader.getResources("META-INF/MANIFEST.MF"); e
.hasMoreElements();)
{
URL url = e.nextElement();
InputStream in = url.openStream();
try
{
Manifest manifest = new Manifest(in);
Attributes atts = manifest.getMainAttributes();
String systemProperties = atts.getValue("System-Properties");
if(systemProperties != null)
{
StringTokenizer pairs = new StringTokenizer(systemProperties," ");
while(pairs.hasMoreTokens())
{
String pair = pairs.nextToken();
StringTokenizer st = new StringTokenizer(pair,"=");
String key = st.nextToken().trim();
String value = st.hasMoreTokens() ? st.nextToken().trim() : "true";
System.setProperty(key,value);
}
}
}
finally
{
in.close();
}
}
}
catch(Exception e)
{
log.error(e);
}
}
/**
* Initializes the XDEV Application Framework.
*
* - Determines whether this is a desktop or a webstart application
* - Sets the look and feel to the system's default
* - Evaluates the application's arguments
args
* - Initializes the applications top level container: a frame, a
* fullscreen window if the system property
fullscreen
is
* true
or an applet
*
*
* This method is called by {@link #main(String[])}.
*
* If you want to start your application without using
* {@link #main(String[])} of this class, call
* {@link #initApplication(String[])} at the beginning of your application
* to initialize the XDEV Application Framwork properly.
*
* @param args
* The application's arguments
*/
public static void initApplication(String[] args)
{
initApplicationImpl(args,"webstart".equalsIgnoreCase(getSystemPropertyOrArgument(
"application.type","")) ? WEBSTART : APPLICATION);
}
private static void initApplicationImpl(String[] args, int type)
{
Application.type = type;
// see
// https://xdevcollaboration.com/pages/viewpage.action?pageId=13730460
if(IOUtils.isMac())
{
System.setProperty("swing.volatileImageBufferEnabled","false");
}
try
{
Application.args = args != null ? args : new String[0];
String icon = System.getProperty("icon");
if(icon != null && icon.length() > 0)
{
try
{
appIcon = GraphicUtils.loadImagePlain(icon);
}
catch(IOException e)
{
}
}
loadExtensions();
initLookAndFeel();
}
catch(Exception e)
{
log.error(e);
}
finally
{
initialized = true;
state = State.RUNNING;
}
if(container == null)
{
fullscreen = Settings.checkUserSetting(System.getProperty("fullscreen"));
}
}
private static void loadExtensions() throws IOException
{
ClassLoader loader = Application.class.getClassLoader();
for(Enumeration urls = loader.getResources("xdev/Extensions"); urls.hasMoreElements();)
{
URL url = urls.nextElement();
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(url.openStream());
doc.getDocumentElement().normalize();
NodeList children = doc.getElementsByTagName("extension");
for(int i = 0, c = children.getLength(); i < c; i++)
{
Element child = (Element)children.item(i);
String extensionClassName = child.getAttribute("class");
Extension extension = (Extension)Class.forName(extensionClassName)
.newInstance();
Map params = new HashMap();
NodeList paramElements = child.getElementsByTagName("param");
for(int p = 0, pc = paramElements.getLength(); p < pc; p++)
{
Element paramElement = (Element)paramElements.item(p);
params.put(paramElement.getAttribute("name"),
paramElement.getAttribute("value"));
}
try
{
extension.init(params);
}
catch(ExtensionInitializationException eie)
{
log.error(eie);
}
}
}
catch(Exception e)
{
log.error(e);
}
}
}
private static void initLookAndFeel() throws InstantiationException, IllegalAccessException,
ClassNotFoundException, LookAndFeelException
{
LookAndFeel laf = null;
String lafName = getSystemPropertyOrArgument("laf",null);
if(lafName != null && lafName.length() > 0)
{
laf = (LookAndFeel)Class.forName(lafName).newInstance();
}
if(laf == null)
{
laf = new SystemLookAndFeel();
}
LookAndFeelManager.setLookAndFeel(laf);
}
private static String getSystemPropertyOrArgument(String name, String defaultValue)
{
String value = System.getProperty(name);
if(value != null)
{
return value;
}
if(args != null && args.length > 0)
{
for(int i = 0; i < args.length; i++)
{
String arg = args[i];
if(arg.startsWith("-" + name) || arg.startsWith("--" + name))
{
int eq = arg.indexOf('=');
if(eq >= 0)
{
value = arg.substring(eq + 1).trim();
}
else if(i < args.length - 1)
{
value = args[i + 1].trim();
}
return value;
}
}
}
return defaultValue;
}
/**
* Starts the application after the XDEV application framwork has been
* initialized.
*
* This method is called by {@link #main(String[])} after
* {@link #initApplication(String[])}.
*
* The system property main is used to determine the main class.
*
* - If the class has a valid main method, it is called with the arguments
* handed over in {@link #initApplication(String[])}.
* - If the class is an instance of {@link XdevWindow} it is initialized
* via the default constructor and shown in the application's top level
* container.
* - If the class is an instance of {@link JComponent} it is initialized
* via the default constructor, placed in a {@link XdevWindow} and shown in
* the application's top level container.
*
*
* @throws IllegalStateException
* if the framework has not been initialized via
* {@link #initApplication(String[])}
*
* @see #main(String[])
* @see #initApplication(String[])
*/
public static void startApplication() throws IllegalStateException
{
startApplicationImpl(getSystemPropertyOrArgument("main",null));
}
private static void startApplicationImpl(String main) throws IllegalStateException
{
if(!initialized)
{
throw new IllegalStateException("XDEV Application Framework has not been initialized "
+ "(Application.initApplication(String[]");
}
if(main == null || main.length() == 0)
{
throw new IllegalArgumentException("No main class specified");
}
try
{
Class mainClass = Class.forName(main);
try
{
mainClass.getMethod("main",String[].class).invoke(null,(Object)args);
}
catch(NoSuchMethodException nsme)
{
try
{
mainClass.getDeclaredConstructor(new Class[0]);
}
catch(NoSuchMethodException e)
{
throw new IllegalArgumentException(main + " has no default constructor");
}
XdevApplicationContainer container = getContainer();
if(XdevWindow.class.isAssignableFrom(mainClass))
{
XdevWindow window = (XdevWindow)mainClass.newInstance();
container.setXdevWindow(window);
container.pack();
container.setLocationRelativeTo(null);
container.setVisible(true);
}
else if(JComponent.class.isAssignableFrom(mainClass))
{
JComponent cpn = (JComponent)mainClass.newInstance();
XdevWindow window = new XdevWindow();
window.setLayout(new BorderLayout());
window.add(cpn,BorderLayout.CENTER);
container.setXdevWindow(window);
container.pack();
container.setLocationRelativeTo(null);
container.setVisible(true);
}
else
{
throw new IllegalArgumentException(main + " has no main method nor is a bean");
}
}
}
catch(Exception e)
{
Throwable t = e;
if(e instanceof InvocationTargetException)
{
Throwable cause = e.getCause();
if(cause != null)
{
t = cause;
}
}
log.error(t);
}
}
/**
* Return the arguments handed over to the application in
* {@link #main(String[])}.
*
* @return The application's arguments
* @throws IllegalStateException
* if this {@link Application} is an {@link Applet}
* @see #getType()
*/
public static String[] getArguments() throws IllegalStateException
{
if(type == APPLET)
{
throw new IllegalStateException("application is an applet");
}
return args;
}
/**
* Returns the value of the named parameter in the HTML tag. For example, if
* this applet is specified as
*
*
*
*
* then a call to getParameter("Color") returns the value "blue".
*
* The name argument is case insensitive.
*
*
* @param name
* a parameter name.
* @return the value of the named parameter, or null if not set.
* @throws IllegalStateException
* if this {@link Application} is not an {@link Applet}
* @see #getType()
*/
public static String getParam(String name) throws IllegalStateException
{
if(type == APPLET && container instanceof Applet)
{
return ((Applet)container).getParameter(name);
}
throw new IllegalStateException("not an applet");
}
public static Image getIconImage()
{
return appIcon;
}
/**
* Returns the applications top level container.
*
* It is either a frame, a fullscreen window or an applet.
*
* @return The application's top level container
*/
public static XdevApplicationContainer getContainer()
{
if(container == null)
{
if(fullscreen)
{
container = new XdevFullScreen();
}
else
{
container = new XdevMainFrame();
}
}
return container;
}
/**
* @return the application's current state
*
* @since 4.0
*/
public static State getState()
{
return state;
}
// /**
// * @return the job queue of this application
// *
// * @since 4.0
// */
// public static JobQueue getJobQueue()
// {
// return jobQueue;
// }
//
//
// /**
// * Adds the job
to the job queue of this application.
// *
// * @param job
// * the job to enqueue
// *
// * @since 4.0
// */
// public static void enqueue(Job job)
// {
// jobQueue.enqueue(job);
// }
/**
* Registers an {@link ApplicationExitListener} to obsorve the exit of this
* {@link Application}.
*
* @param l
* the listener to add
*/
public static void addExitListener(ApplicationExitListener l)
{
listenerList.add(ApplicationExitListener.class,l);
}
/**
* Removes the {@link ApplicationExitListener} l
from this
* {@link Application}.
*
* @param l
* the listener to remove
*/
public static void removeExitListener(ApplicationExitListener l)
{
listenerList.remove(ApplicationExitListener.class,l);
}
/**
* Exits the current running XDEV Application Framwork and Java Virtual
* Machine.
*
* If a registered {@link ApplicationExitListener} vetos the process, the
* exit is terminated and the XDEV Application Framework remains running.
*
* @param source
* The initiator of the exit process
* @see ApplicationExitListener
* @see ApplicationExitEvent#vetoExit()
*/
public static void exit(final Object source)
{
ApplicationExitListener[] listeners = listenerList
.getListeners(ApplicationExitListener.class);
if(listeners != null && listeners.length > 0)
{
ApplicationExitEvent event = new ApplicationExitEvent(source);
for(ApplicationExitListener l : listeners)
{
l.applicationWillExit(event);
}
if(event.hasVeto())
{
return;
}
for(ApplicationExitListener l : listeners)
{
l.applicationExits(event);
}
}
System.exit(0);
}
/**
* Returns the time of the hosting server of this applet / webstart
* application.
*
* @return The server time of this application.
*/
public static XdevDate getServerTime()
{
return new XdevDate(System.currentTimeMillis() + serverTimeDiff);
}
private static Runnable gc;
static
{
gc = new Runnable()
{
public void run()
{
System.gc();
}
};
}
/**
* Runs the garbage collector.
*
* Calling the gc method suggests that the Java Virtual Machine expend
* effort toward recycling unused objects in order to make the memory they
* currently occupy available for quick reuse. When control returns from the
* method call, the Java Virtual Machine has made a best effort to reclaim
* space from all discarded objects.
*
*/
public static void gc()
{
SwingUtilities.invokeLater(gc);
}
/**
* Returns the default {@link GraphicsConfiguration} associated with the
* {@link GraphicsDevice} this application is showing on.
*
* @return the default {@link GraphicsConfiguration} of this
* {@link GraphicsDevice}.
*/
public static GraphicsConfiguration getLocalGraphicsConfiguration()
{
if(container != null)
{
return container.getGraphicsConfiguration();
}
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration();
}
/*
* ---------------------------- Applet---------------------------
*/
/**
* {@inheritDoc}
*/
@Override
public void init()
{
try
{
System.setSecurityManager(new SecurityManager()
{
@Override
public void checkPermission(Permission perm)
{
}
@Override
public void checkPermission(Permission perm, Object context)
{
}
});
}
catch(Throwable t)
{
}
getContentPane().setLayout(new BorderLayout());
container = this;
try
{
serverTimeDiff = Long.parseLong(getParameter("serverTimeMillis")) * 1000
- System.currentTimeMillis();
}
catch(Exception e)
{
serverTimeDiff = 0;
}
try
{
fullscreen = "true".equals(getParameter("fullscreen"));
}
catch(Exception e)
{
fullscreen = false;
}
try
{
String value = getParameter("suffix");
if(value != null && value.length() > 0)
{
System.setProperty(Settings.SERVER_SIDE_SUFFIX,value);
}
}
catch(Exception e)
{
log.error(e);
}
setFocusTraversalPolicy(new XdevFocusTraversalPolicy(this));
try
{
String vmArgs = getParameter("java_args");
if(vmArgs != null && vmArgs.length() > 0)
{
StringTokenizer st = new StringTokenizer(vmArgs," ");
while(st.hasMoreTokens())
{
String token = st.nextToken().trim();
if(token.startsWith("-D"))
{
int index = token.indexOf('=');
if(index == -1)
{
System.setProperty(token.substring(2),"true");
}
else
{
System.setProperty(token.substring(2,index),token.substring(index + 1));
}
}
}
}
}
catch(Exception e)
{
log.error(e);
}
initApplicationImpl(null,APPLET);
System.out.println("XDEV Application Framework " + API.VERSION.toScreen());
}
/**
* {@inheritDoc}
*/
@Override
public void start()
{
startApplicationImpl(getParameter("main"));
}
/**
* {@inheritDoc}
*/
@Override
public void destroy()
{
System.gc();
exit(this);
}
/**
* {@inheritDoc}
*/
@Override
public Window getWindow()
{
return JOptionPane.getRootFrame();
}
/**
* {@inheritDoc}
*/
@Override
public Image getImage(URL url)
{
return getAppletContext().getImage(url);
}
/**
* {@inheritDoc}
*/
@Override
public void showDocument(URL url)
{
getAppletContext().showDocument(url);
}
/**
* {@inheritDoc}
*/
@Override
public void showDocument(URL url, String target)
{
getAppletContext().showDocument(url,target);
}
/**
* {@inheritDoc}
*/
@Override
public void appletResize(int width, int height)
{
resize(width,height);
}
private XdevWindow window;
/**
* {@inheritDoc}
*/
@Override
public void setXdevWindow(XdevWindow window)
{
cleanup();
this.window = window;
window.setOwner(this);
String width;
String height;
if(fullscreen)
{
width = height = "\"100%\"";
}
else
{
Dimension ps = window.getPreferredSize();
width = "\"" + ps.width + "px\"";
height = "\"" + ps.height + "px\"";
}
setJMenuBar(window.getJMenuBar());
getRootPane().setDefaultButton(window.getDefaultButton());
getContentPane().add(Util.createContainer(this,window));
jsc("resize","width",width,"height",height);
validate();
repaint();
window.fireWindowOpened(new WindowEvent(SwingUtilities.getWindowAncestor(this),0));
}
/**
* {@inheritDoc}
*/
@Override
public XdevWindow getXdevWindow()
{
return window;
}
private void cleanup()
{
try
{
getRootPane().setJMenuBar(null);
getContentPane().removeAll();
if(window != null)
{
window.setOwner(null);
window = null;
}
}
catch(Exception e)
{
}
}
/**
* {@inheritDoc}
*/
@Override
public void setTitle(String title)
{
jsc("setTitle","title",jsc_escape(title));
}
private void jsc(String action, String... params)
{
try
{
StringBuilder sb = new StringBuilder();
sb.append("jsc.");
sb.append(Settings.getServerSideSuffix());
sb.append("?action=");
sb.append(action);
for(int i = 0; i < params.length; i += 2)
{
sb.append("&");
sb.append(params[i]);
sb.append("=");
sb.append(URLEncoder.encode(params[i + 1],"UTF8"));
}
showDocument(new URL(Application.getContainer().getCodeBase(),sb.toString()),"jsc");
}
catch(Exception e)
{
log.error(e);
}
}
private String jsc_escape(String str)
{
int len = str.length();
StringBuilder sb = new StringBuilder(len + 2);
sb.append("'");
for(int i = 0; i < len; i++)
{
char ch = str.charAt(i);
if(ch == '\'')
{
sb.append('\\');
}
sb.append(ch);
}
sb.append("'");
return sb.toString();
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void setIconImage(Image img)
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void pack()
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void setLocationRelativeTo(Component c)
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void setResizable(boolean b)
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void setExtendedState(int extendedState)
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public int getExtendedState()
{
return NORMAL;
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void toFront()
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void toBack()
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void addWindowListener(WindowListener listener)
{
// no op
}
/**
* Implemented with no action for {@link XdevApplicationContainer}.
*/
@Override
public void removeWindowListener(WindowListener listener)
{
// no op
}
/**
* {@inheritDoc}
*/
@Override
public void close()
{
exit(this);
}
/**
* Returns the {@link XdevLogger} of this Application which is used to log
* messages for the XDEV Application Framework.
*
*
*
* Hint: Consider using local loggers instead of using one
* application global logger. With local loggers you benefit from many
* advantages i.e. name based filtering and message routing.
*
*
*
* @return the {@link XdevLogger} of this Application.
*
*
*/
public static XdevLogger getLogger()
{
return log;
}
}