diff options
author | Joonas Lehtinen <joonas.lehtinen@itmill.com> | 2006-12-29 11:43:33 +0000 |
---|---|---|
committer | Joonas Lehtinen <joonas.lehtinen@itmill.com> | 2006-12-29 11:43:33 +0000 |
commit | 5590682a3062d51fb61fdffbdd21b1a128800f50 (patch) | |
tree | cef14a0a9ed9b591e1cfbae4aa16be91b24a02b7 /src | |
parent | 035c3d4deff0433324c61643cd1c60e2f1e1783a (diff) | |
download | vaadin-framework-5590682a3062d51fb61fdffbdd21b1a128800f50.tar.gz vaadin-framework-5590682a3062d51fb61fdffbdd21b1a128800f50.zip |
New theme description format that supports combined ajax-xsl themes. This is not yet forced by default and it
should be backwards compatible with old ajax and xsl-themes.
svn changeset:184/svn branch:toolkit
Diffstat (limited to 'src')
7 files changed, 862 insertions, 672 deletions
diff --git a/src/com/itmill/toolkit/terminal/web/DirectoryThemeSource.java b/src/com/itmill/toolkit/terminal/web/DirectoryThemeSource.java index f2d26e1465..aa2df317f8 100644 --- a/src/com/itmill/toolkit/terminal/web/DirectoryThemeSource.java +++ b/src/com/itmill/toolkit/terminal/web/DirectoryThemeSource.java @@ -76,7 +76,7 @@ public class DirectoryThemeSource implements ThemeSource { this.theme = new Theme(description); } catch (Exception e) { throw new ThemeException( - "ServletThemeSource: Failed to load '" + path + "': " + e); + "ServletThemeSource: Failed to load '" + path,e); } // Debug info @@ -134,11 +134,12 @@ public class DirectoryThemeSource implements ThemeSource { } } - Collection fileNames = theme.getFileNames(type); + Collection fileNames = theme.getFileNames(type, Theme.MODE_XSLT); // Add all XSL file streams for (Iterator i = fileNames.iterator(); i.hasNext();) { File f = new File(this.path, (String) i.next()); + if (f.getName().endsWith(".xsl")) try { xslFiles.add(new XSLStream(f.getName(),new FileInputStream(f))); } catch (FileNotFoundException e) { diff --git a/src/com/itmill/toolkit/terminal/web/JarThemeSource.java b/src/com/itmill/toolkit/terminal/web/JarThemeSource.java index 4db464f3f1..f1cf08db1a 100644 --- a/src/com/itmill/toolkit/terminal/web/JarThemeSource.java +++ b/src/com/itmill/toolkit/terminal/web/JarThemeSource.java @@ -1,30 +1,30 @@ /* ************************************************************************* - IT Mill Toolkit + IT Mill Toolkit - Development of Browser User Intarfaces Made Easy + Development of Browser User Intarfaces Made Easy - Copyright (C) 2000-2006 IT Mill Ltd - - ************************************************************************* + Copyright (C) 2000-2006 IT Mill Ltd + + ************************************************************************* - This product is distributed under commercial license that can be found - from the product package on license/license.txt. Use of this product might - require purchasing a commercial license from IT Mill Ltd. For guidelines - on usage, see license/licensing-guidelines.html + This product is distributed under commercial license that can be found + from the product package on license/license.txt. Use of this product might + require purchasing a commercial license from IT Mill Ltd. For guidelines + on usage, see license/licensing-guidelines.html - ************************************************************************* - - For more information, contact: - - IT Mill Ltd phone: +358 2 4802 7180 - Ruukinkatu 2-4 fax: +358 2 4802 7181 - 20540, Turku email: info@itmill.com - Finland company www: www.itmill.com - - Primary source for information and releases: www.itmill.com + ************************************************************************* + + For more information, contact: + + IT Mill Ltd phone: +358 2 4802 7180 + Ruukinkatu 2-4 fax: +358 2 4802 7181 + 20540, Turku email: info@itmill.com + Finland company www: www.itmill.com + + Primary source for information and releases: www.itmill.com - ********************************************************************** */ + ********************************************************************** */ package com.itmill.toolkit.terminal.web; @@ -44,37 +44,48 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; -/** Theme source for reading themes from a JAR archive. - * At this time only jar files are supported and an archive - * may not contain any recursive archives. +/** + * Theme source for reading themes from a JAR archive. At this time only jar + * files are supported and an archive may not contain any recursive archives. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class JarThemeSource implements ThemeSource { private File file; + private JarFile jar; + private Theme theme; + private String path; + private String name; + private ApplicationServlet webAdapterServlet; + private Cache resourceCache = new Cache(); /** Collection of subdirectory entries */ private Collection subdirs = new LinkedList(); - /** Creates a new instance of ThemeRepository by reading the themes - * from a local directory. - * @param file Path to the JAR archive . - * @param path Path inside the archive to be processed. - * @throws FileNotFoundException if no theme files are found + /** + * Creates a new instance of ThemeRepository by reading the themes from a + * local directory. + * + * @param file + * Path to the JAR archive . + * @param path + * Path inside the archive to be processed. + * @throws FileNotFoundException + * if no theme files are found */ - public JarThemeSource( - File file, - ApplicationServlet webAdapterServlet, - String path) - throws ThemeException, FileNotFoundException, IOException { + public JarThemeSource(File file, ApplicationServlet webAdapterServlet, + String path) throws ThemeException, FileNotFoundException, + IOException { this.file = file; this.jar = new JarFile(file); @@ -88,7 +99,6 @@ public class JarThemeSource implements ThemeSource { this.name = this.name.substring(0, this.name.length() - 4); } - this.webAdapterServlet = webAdapterServlet; // Load description file @@ -97,55 +107,49 @@ public class JarThemeSource implements ThemeSource { try { this.theme = new Theme(jar.getInputStream(entry)); } catch (Exception e) { - throw new ThemeException( - "JarThemeSource: Failed to load '" + path + "': ",e); + throw new ThemeException("JarThemeSource: Failed to load '" + + path + "': ", e); } // Debug info if (webAdapterServlet.isDebugMode()) { - Log.debug("Added JarThemeSource: " + this.file + ":" + this.path); + Log.debug("Added JarThemeSource: " + this.file + ":" + + this.path); } } else { - // There was no description file found. - // Handle subdirectories recursively - for (Enumeration entries = jar.entries(); - entries.hasMoreElements(); - ) { + // There was no description file found. + // Handle subdirectories recursively + for (Enumeration entries = jar.entries(); entries.hasMoreElements();) { JarEntry e = (JarEntry) entries.nextElement(); if (e.getName().startsWith(this.path)) { if (e.getName().endsWith("/") - && e.getName().indexOf('/', this.path.length()) - == (e.getName().length() - 1)) { - this.subdirs.add( - new JarThemeSource( - this.file, - this.webAdapterServlet, - e.getName())); + && e.getName().indexOf('/', this.path.length()) == (e + .getName().length() - 1)) { + this.subdirs.add(new JarThemeSource(this.file, + this.webAdapterServlet, e.getName())); } } } if (this.subdirs.isEmpty()) { if (webAdapterServlet.isDebugMode()) { - Log.info( - "JarThemeSource: Ignoring empty JAR path: " - + this.file - + " path: " - + this.path); + Log.info("JarThemeSource: Ignoring empty JAR path: " + + this.file + " path: " + this.path); } } } } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getXSLStreams(Theme, WebBrowser) + * @see com.itmill.toolkit.terminal.web.ThemeSource#getXSLStreams(Theme, + * WebBrowser) */ public Collection getXSLStreams(Theme theme, WebBrowser type) - throws ThemeException { + throws ThemeException { Collection xslFiles = new LinkedList(); - // If this directory contains a theme - // return XSL from this theme + // If this directory contains a theme + // return XSL from this theme if (this.theme != null) { if (webAdapterServlet.isDebugMode()) { @@ -158,28 +162,27 @@ public class JarThemeSource implements ThemeSource { try { this.theme = new Theme(jar.getInputStream(entry)); } catch (IOException e) { - throw new ThemeException( - "Failed to read description: " - + this.file - + ":" - + this.path + throw new ThemeException("Failed to read description: " + + this.file + ":" + this.path + Theme.DESCRIPTIONFILE); } } - Collection fileNames = theme.getFileNames(type); + Collection fileNames = theme.getFileNames(type, Theme.MODE_XSLT); // Add all XSL file streams for (Iterator i = fileNames.iterator(); i.hasNext();) { entry = jar.getJarEntry(this.path + (String) i.next()); - try { - xslFiles.add(new XSLStream(entry.getName(),jar.getInputStream(entry))); - } catch (java.io.FileNotFoundException e) { - throw new ThemeException( - "XSL File not found: " + this.file + ": " + entry); - } catch (java.io.IOException e) { - throw new ThemeException( - "Failed to read XSL file. " + this.file + ": " + entry); - } + if (entry.getName().endsWith(".xsl")) + try { + xslFiles.add(new XSLStream(entry.getName(), jar + .getInputStream(entry))); + } catch (java.io.FileNotFoundException e) { + throw new ThemeException("XSL File not found: " + + this.file + ": " + entry); + } catch (java.io.IOException e) { + throw new ThemeException("Failed to read XSL file. " + + this.file + ": " + entry); + } } } else { @@ -195,7 +198,9 @@ public class JarThemeSource implements ThemeSource { return xslFiles; } - /** Return modication time of the jar file. + /** + * Return modication time of the jar file. + * * @see com.itmill.toolkit.terminal.web.ThemeSource#getModificationTime() */ public long getModificationTime() { @@ -206,15 +211,15 @@ public class JarThemeSource implements ThemeSource { * @see com.itmill.toolkit.terminal.web.ThemeSource#getResource(String) */ public InputStream getResource(String resourceId) - throws ThemeSource.ThemeException { + throws ThemeSource.ThemeException { // Strip off the theme name prefix from resource id - if (this.theme != null && - this.theme.getName() != null && - resourceId.startsWith(this.theme.getName()+"/")){ - resourceId = resourceId.substring(this.theme.getName().length()+1); + if (this.theme != null && this.theme.getName() != null + && resourceId.startsWith(this.theme.getName() + "/")) { + resourceId = resourceId + .substring(this.theme.getName().length() + 1); } - + // Return the resource inside the jar file JarEntry entry = jar.getJarEntry(resourceId); if (entry != null) @@ -243,13 +248,13 @@ public class JarThemeSource implements ThemeSource { } catch (IOException e) { } - throw new ThemeSource.ThemeException( - "Resource " + resourceId + " not found."); + throw new ThemeSource.ThemeException("Resource " + resourceId + + " not found."); } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemes() - */ + * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemes() + */ public Collection getThemes() { Collection themes = new LinkedList(); if (this.theme != null) { @@ -264,8 +269,8 @@ public class JarThemeSource implements ThemeSource { } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getName() - */ + * @see com.itmill.toolkit.terminal.web.ThemeSource#getName() + */ public String getName() { if (this.theme != null) { return this.theme.getName(); @@ -274,9 +279,9 @@ public class JarThemeSource implements ThemeSource { } } - /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemeByName(String) - */ + /** + * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemeByName(String) + */ public Theme getThemeByName(String name) { Collection themes = this.getThemes(); for (Iterator i = themes.iterator(); i.hasNext();) { @@ -289,17 +294,16 @@ public class JarThemeSource implements ThemeSource { /** * @author IT Mill Ltd. - * @version @VERSION@ - * @since 3.0 - */ + * @version + * @VERSION@ + * @since 3.0 + */ private class Cache { private Map data = new HashMap(); public void put(Object key, Object value) { - data.put( - key, - new SoftReference(new CacheItem(value))); + data.put(key, new SoftReference(new CacheItem(value))); } public Object get(Object key) { @@ -316,7 +320,8 @@ public class JarThemeSource implements ThemeSource { /** * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ private class CacheItem { diff --git a/src/com/itmill/toolkit/terminal/web/ServletThemeSource.java b/src/com/itmill/toolkit/terminal/web/ServletThemeSource.java index 2da70f65c2..2c178cc8ce 100644 --- a/src/com/itmill/toolkit/terminal/web/ServletThemeSource.java +++ b/src/com/itmill/toolkit/terminal/web/ServletThemeSource.java @@ -1,31 +1,30 @@ /* ************************************************************************* - IT Mill Toolkit + IT Mill Toolkit - Development of Browser User Intarfaces Made Easy + Development of Browser User Intarfaces Made Easy - Copyright (C) 2000-2006 IT Mill Ltd - - ************************************************************************* - - This product is distributed under commercial license that can be found - from the product package on license/license.txt. Use of this product might - require purchasing a commercial license from IT Mill Ltd. For guidelines - on usage, see license/licensing-guidelines.html + Copyright (C) 2000-2006 IT Mill Ltd + + ************************************************************************* - ************************************************************************* - - For more information, contact: - - IT Mill Ltd phone: +358 2 4802 7180 - Ruukinkatu 2-4 fax: +358 2 4802 7181 - 20540, Turku email: info@itmill.com - Finland company www: www.itmill.com - - Primary source for information and releases: www.itmill.com + This product is distributed under commercial license that can be found + from the product package on license/license.txt. Use of this product might + require purchasing a commercial license from IT Mill Ltd. For guidelines + on usage, see license/licensing-guidelines.html - ********************************************************************** */ + ************************************************************************* + + For more information, contact: + + IT Mill Ltd phone: +358 2 4802 7180 + Ruukinkatu 2-4 fax: +358 2 4802 7181 + 20540, Turku email: info@itmill.com + Finland company www: www.itmill.com + + Primary source for information and releases: www.itmill.com + ********************************************************************** */ package com.itmill.toolkit.terminal.web; @@ -44,35 +43,44 @@ import java.util.Map; import javax.servlet.ServletContext; -/** Theme source for reading themes from a JAR archive. - * At this time only jar files are supported and an archive - * may not contain any recursive archives. +/** + * Theme source for reading themes from a JAR archive. At this time only jar + * files are supported and an archive may not contain any recursive archives. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class ServletThemeSource implements ThemeSource { private ServletContext context; + private Theme theme; + private String path; + private ApplicationServlet webAdapterServlet; + private Cache resourceCache = new Cache(); /** Collection of subdirectory entries */ private URL descFile; - /** Creates a new instance of ThemeRepository by reading the themes - * from a local directory. - * @param file Path to the JAR archive . - * @param path Path inside the archive to be processed. - * @throws FileNotFoundException if no theme files are found + /** + * Creates a new instance of ThemeRepository by reading the themes from a + * local directory. + * + * @param file + * Path to the JAR archive . + * @param path + * Path inside the archive to be processed. + * @throws FileNotFoundException + * if no theme files are found */ - public ServletThemeSource( - ServletContext context, - ApplicationServlet webAdapterServlet, - String path) - throws IOException, ThemeException { + public ServletThemeSource(ServletContext context, + ApplicationServlet webAdapterServlet, String path) + throws IOException, ThemeException { this.theme = null; this.webAdapterServlet = webAdapterServlet; @@ -84,23 +92,21 @@ public class ServletThemeSource implements ThemeSource { this.path = this.path + "/"; } if ((this.path.length() > 0) && !this.path.startsWith("/")) { - this.path = "/" +this.path; + this.path = "/" + this.path; } - + // Load description file this.descFile = context.getResource(this.path + Theme.DESCRIPTIONFILE); - InputStream entry = - context.getResourceAsStream(this.path + Theme.DESCRIPTIONFILE); + InputStream entry = context.getResourceAsStream(this.path + + Theme.DESCRIPTIONFILE); try { if (entry != null) { try { this.theme = new Theme(entry); } catch (Exception e) { throw new ThemeException( - "ServletThemeSource: Failed to load '" - + path - + "': " - + e); + "ServletThemeSource: Failed to load '" + path + + "': " + e); } entry.close(); @@ -111,7 +117,7 @@ public class ServletThemeSource implements ThemeSource { } else { throw new IllegalArgumentException( - "ServletThemeSource: Invalid theme resource: " + path); + "ServletThemeSource: Invalid theme resource: " + path); } } finally { if (entry != null) @@ -120,14 +126,15 @@ public class ServletThemeSource implements ThemeSource { } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getXSLStreams(Theme, WebBrowser) + * @see com.itmill.toolkit.terminal.web.ThemeSource#getXSLStreams(Theme, + * WebBrowser) */ public Collection getXSLStreams(Theme theme, WebBrowser type) - throws ThemeException { + throws ThemeException { Collection xslFiles = new LinkedList(); - // If this directory contains a theme - // return XSL from this theme + // If this directory contains a theme + // return XSL from this theme if (this.theme != null) { if (webAdapterServlet.isDebugMode()) { @@ -135,15 +142,15 @@ public class ServletThemeSource implements ThemeSource { } // Reload the description file - InputStream entry = - context.getResourceAsStream(this.path + Theme.DESCRIPTIONFILE); + InputStream entry = context.getResourceAsStream(this.path + + Theme.DESCRIPTIONFILE); try { if (entry != null) { this.theme = new Theme(entry); } } catch (Exception e) { - throw new ThemeException( - "ServletThemeSource: Failed to load '" + path + "': " + e); + throw new ThemeException("ServletThemeSource: Failed to load '" + + path + "': " + e); } finally { if (entry != null) try { @@ -152,21 +159,25 @@ public class ServletThemeSource implements ThemeSource { } } - Collection fileNames = theme.getFileNames(type); + Collection fileNames = theme.getFileNames(type, Theme.MODE_XSLT); // Add all XSL file streams for (Iterator i = fileNames.iterator(); i.hasNext();) { String entryName = (String) i.next(); - entry = - context.getResourceAsStream( - (this.path + entryName)); - xslFiles.add(new XSLStream(entryName,entry)); + if (entryName.endsWith(".xsl")) { + + entry = context + .getResourceAsStream((this.path + entryName)); + xslFiles.add(new XSLStream(entryName, entry)); + } } } return xslFiles; } - /** Return modication time of the description file. + /** + * Return modication time of the description file. + * * @see com.itmill.toolkit.terminal.web.ThemeSource#getModificationTime() */ public long getModificationTime() { @@ -184,14 +195,13 @@ public class ServletThemeSource implements ThemeSource { * @see com.itmill.toolkit.terminal.web.ThemeSource#getResource(String) */ public InputStream getResource(String resourceId) - throws ThemeSource.ThemeException { + throws ThemeSource.ThemeException { // Check the id String name = this.getName(); int namelen = name.length(); - if (resourceId == null - || !resourceId.startsWith(name + "/") - || resourceId.length() <= (namelen + 1)) { + if (resourceId == null || !resourceId.startsWith(name + "/") + || resourceId.length() <= (namelen + 1)) { return null; } @@ -214,9 +224,9 @@ public class ServletThemeSource implements ThemeSource { while ((n = stream.read(buf)) >= 0) { out.write(buf, 0, n); } - try{ + try { stream.close(); - } catch (IOException ignored){ + } catch (IOException ignored) { } data = out.toByteArray(); @@ -226,13 +236,13 @@ public class ServletThemeSource implements ThemeSource { } catch (IOException e) { } - throw new ThemeSource.ThemeException( - "Resource " + resourceId + " not found."); + throw new ThemeSource.ThemeException("Resource " + resourceId + + " not found."); } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemes() - */ + * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemes() + */ public Collection getThemes() { Collection themes = new LinkedList(); if (this.theme != null) { @@ -242,15 +252,15 @@ public class ServletThemeSource implements ThemeSource { } /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getName() - */ + * @see com.itmill.toolkit.terminal.web.ThemeSource#getName() + */ public String getName() { return this.theme.getName(); } - /** - * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemeByName(String) - */ + /** + * @see com.itmill.toolkit.terminal.web.ThemeSource#getThemeByName(String) + */ public Theme getThemeByName(String name) { Collection themes = this.getThemes(); for (Iterator i = themes.iterator(); i.hasNext();) { @@ -263,17 +273,16 @@ public class ServletThemeSource implements ThemeSource { /** * @author IT Mill Ltd. - * @version @VERSION@ - * @since 3.0 - */ + * @version + * @VERSION@ + * @since 3.0 + */ private class Cache { private Map data = new HashMap(); public void put(Object key, Object value) { - data.put( - key, - new SoftReference(new CacheItem(value))); + data.put(key, new SoftReference(new CacheItem(value))); } public Object get(Object key) { @@ -290,7 +299,8 @@ public class ServletThemeSource implements ThemeSource { /** * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ private class CacheItem { diff --git a/src/com/itmill/toolkit/terminal/web/Theme.java b/src/com/itmill/toolkit/terminal/web/Theme.java index d3860b5f98..91daab4bf9 100644 --- a/src/com/itmill/toolkit/terminal/web/Theme.java +++ b/src/com/itmill/toolkit/terminal/web/Theme.java @@ -1,30 +1,30 @@ /* ************************************************************************* - IT Mill Toolkit + IT Mill Toolkit - Development of Browser User Intarfaces Made Easy + Development of Browser User Intarfaces Made Easy - Copyright (C) 2000-2006 IT Mill Ltd - - ************************************************************************* + Copyright (C) 2000-2006 IT Mill Ltd + + ************************************************************************* - This product is distributed under commercial license that can be found - from the product package on license/license.txt. Use of this product might - require purchasing a commercial license from IT Mill Ltd. For guidelines - on usage, see license/licensing-guidelines.html + This product is distributed under commercial license that can be found + from the product package on license/license.txt. Use of this product might + require purchasing a commercial license from IT Mill Ltd. For guidelines + on usage, see license/licensing-guidelines.html - ************************************************************************* - - For more information, contact: - - IT Mill Ltd phone: +358 2 4802 7180 - Ruukinkatu 2-4 fax: +358 2 4802 7181 - 20540, Turku email: info@itmill.com - Finland company www: www.itmill.com - - Primary source for information and releases: www.itmill.com + ************************************************************************* + + For more information, contact: + + IT Mill Ltd phone: +358 2 4802 7180 + Ruukinkatu 2-4 fax: +358 2 4802 7181 + 20540, Turku email: info@itmill.com + Finland company www: www.itmill.com + + Primary source for information and releases: www.itmill.com - ********************************************************************** */ + ********************************************************************** */ package com.itmill.toolkit.terminal.web; @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -45,65 +46,68 @@ import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.Attributes; -/** This class provides an interface to the meta-information - * regarding a particular webadapter theme. This entails for - * instanace the inheritance tree of the various xsl-template files, - * the different requirments that the theme imposes on the client browser, - * etc. - * <p> - * The WebAdapter uses themes to convert the UIDL description into - * client representation, typically HTML or XHTML. - * A theme consists of set of XSL template files which are used to - * perform XSL transform. - * </p> - * <p> - * XSL files are divided into sets, which can have requirements. - * A file set is included in transformation only if the given requirements - * are met. Following requirements are supported: - * <ul> - * <li>User-Agent HTTP header substring matching</li> - * <li>Markup language version</li> - * <li>JavaScript version</li> - * </ul> - * Additionally following boolean operators may be applied to above - * requirements: - * <ul> - * <li>NOT</li> - * <li>AND</li> - * <li>OR</li> - * </ul> - * The requirements are introduced in XML description file. See example below. - * </p> - * <p> - * The theme description is XML data, and it can be loaded from file or stream. - * The default filename is specified by <code>Theme.DESCRIPTIONFILE</code>. - * Example of theme description file: - * <pre> - * <?xml version="1.0" encoding="UTF-8"?> - * - * <theme name="normal"> +/** + * This class provides an interface to the meta-information regarding a + * particular webadapter theme. This entails for instanace the inheritance tree + * of the various xsl-template files, the different requirments that the theme + * imposes on the client browser, etc. + * <p> + * The WebAdapter uses themes to convert the UIDL description into client + * representation, typically HTML or XHTML. A theme consists of set of XSL + * template files which are used to perform XSL transform. + * </p> + * <p> + * XSL files are divided into sets, which can have requirements. A file set is + * included in transformation only if the given requirements are met. Following + * requirements are supported: + * <ul> + * <li>User-Agent HTTP header substring matching</li> + * <li>Markup language version</li> + * <li>JavaScript version</li> + * </ul> + * Additionally following boolean operators may be applied to above + * requirements: + * <ul> + * <li>NOT</li> + * <li>AND</li> + * <li>OR</li> + * </ul> + * The requirements are introduced in XML description file. See example below. + * </p> + * <p> + * The theme description is XML data, and it can be loaded from file or stream. + * The default filename is specified by <code>Theme.DESCRIPTIONFILE</code>. + * Example of theme description file: + * + * <pre> + * <?xml version="1.0" encoding="UTF-8"?> + * + * <theme name="normal"> + * + * <extends theme="simple"/> + * + * <description>The normal theme for all browsers</description> + * <author name="IT Mill Ltd" email="millstone@itmill.com" /> + * + * <fileset> + * <require> + * <supports javascript="JavaScript 1.0"/> + * </require> + * + * <file name="common/error.xsl" /> + * <file name="components/button.xsl" /> + * <file name="components/select.xsl" /> + * <file name="components/textfield.xsl" /> + * <file name="components/table.xsl" /> + * </fileset> + * </theme> + * </pre> + * + * </p> * - * <extends theme="simple"/> - * - * <description>The normal theme for all browsers</description> - * <author name="IT Mill Ltd" email="millstone@itmill.com" /> - * - * <fileset> - * <require> - * <supports javascript="JavaScript 1.0"/> - * </require> - * - * <file name="common/error.xsl" /> - * <file name="components/button.xsl" /> - * <file name="components/select.xsl" /> - * <file name="components/textfield.xsl" /> - * <file name="components/table.xsl" /> - * </fileset> - * </theme> - * </pre> - * </p> * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class Theme extends DefaultHandler { @@ -112,35 +116,55 @@ public class Theme extends DefaultHandler { public static final String DESCRIPTIONFILE = "description.xml"; private static final String TAG_THEME = "theme"; + private static final String TAG_EXTENDS = "extends"; + private static final String TAG_DESCRIPTION = "description"; private static final String TAG_FILE = "file"; + private static final String TAG_FILESET = "fileset"; + + private static final String TAG_MODE = "mode"; + + private static final String TAG_MODES = "modes"; + private static final String TAG_REQUIRE = "require"; + private static final String TAG_SUPPORTS = "supports"; + private static final String TAG_AUTHOR = "author"; private static final String TAG_AND = "and"; + private static final String TAG_OR = "or"; + private static final String TAG_NOT = "not"; private static final String ATTR_NAME = "name"; + private static final String ATTR_THEME = "theme"; + private static final String ATTR_EMAIL = "email"; + private static final String ATTR_MODE = "mode"; + private static final String ATTR_JAVASCRIPT = "javascript"; + private static final String ATTR_AGENT = "agent"; + private static final String ATTR_MARKUP = "markup"; private static final String UNNAMED_FILESET = "unnamed"; - /** Name of the theme. */ - private String name; + public static final String MODE_UIDL = "uidl"; + + public static final String MODE_XSLT = "xslt"; + public static final String MODE_FALLBACK = MODE_XSLT; - /** Version of the theme. */ - private String version; + /** Name of the theme. */ + private String name; /** Theme description. */ private String description; @@ -160,24 +184,40 @@ public class Theme extends DefaultHandler { /** Stack of string buffers used while parsing XML. */ private Stack openStrings = new Stack(); + /** Supported modes name-to-requirements */ + private LinkedHashMap supportedModes = new LinkedHashMap(); + + /** Currently open mode */ + private String currentlyOpenMode = null; + + /** Are we processing modes */ + private boolean modesListCurrentlyOpen = false; + /** Is a NOT requirement element open. */ private boolean isNOTRequirementOpen = false; /** Currently open requirements while parsing. */ private Stack openRequirements = new Stack(); - /** Creates a new instance using XML description file. - * Instantiate new theme, by loading the description from given File. - * @param descriptionFile Description file - * @throws FileNotFoundException Thrown if the given file is not found. + /** + * Creates a new instance using XML description file. Instantiate new theme, + * by loading the description from given File. + * + * @param descriptionFile + * Description file + * @throws FileNotFoundException + * Thrown if the given file is not found. */ public Theme(java.io.File descriptionFile) throws FileNotFoundException { parse(new InputSource(new FileInputStream(descriptionFile))); } - /** Creates a new instance using XML description stream. - * Instantiate new theme, by loading the description from given InputSource. - * @param descriptionStream XML input to parse + /** + * Creates a new instance using XML description stream. Instantiate new + * theme, by loading the description from given InputSource. + * + * @param descriptionStream + * XML input to parse */ public Theme(InputStream descriptionStream) { try { @@ -190,8 +230,50 @@ public class Theme extends DefaultHandler { } } - /** Parse XML data. - * @param descriptionSource XML input source to parse + /** + * Get the preferred operating mode supported by this theme for given + * terminal. + */ + public String getPreferredMode(WebBrowser terminal) { + + // If no supported modes are declared, then we use parents preferred + // mode + if (parentThemes != null && parentThemes.size() > 0 + && supportedModes.keySet().isEmpty()) + return ((Theme) parentThemes.get(0)).getPreferredMode(terminal); + + // Iterate and test the modes in order + for (Iterator i = supportedModes.keySet().iterator(); i.hasNext();) { + String mode = (String) i.next(); + if (supportsMode(mode, terminal)) + return mode; + } + + return MODE_FALLBACK; + } + + /** Tests if this theme suppors given mode */ + public boolean supportsMode(String mode, WebBrowser terminal) { + + // Theme must explicitly support the given mode + RequirementCollection rc = (RequirementCollection) supportedModes + .get(mode); + if (rc == null || !rc.isMet(terminal)) + return false; + + // All parents must also support the mode + for (Iterator i = parentThemes.iterator(); i.hasNext();) + if (!((Theme) i.next()).supportsMode(mode, terminal)) + return false; + + return true; + } + + /** + * Parse XML data. + * + * @param descriptionSource + * XML input source to parse */ private synchronized void parse(InputSource descriptionSource) { @@ -203,8 +285,8 @@ public class Theme extends DefaultHandler { // Parse the Document try { - XMLReader xr = - SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + XMLReader xr = SAXParserFactory.newInstance().newSAXParser() + .getXMLReader(); xr.setContentHandler(this); xr.setErrorHandler(this); @@ -223,15 +305,14 @@ public class Theme extends DefaultHandler { } - /** Parse start tag in XML stream. + /** + * Parse start tag in XML stream. * - * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) + * @see org.xml.sax.ContentHandler#startElement(java.lang.String, + * java.lang.String, java.lang.String, org.xml.sax.Attributes) */ - public void startElement( - String uri, - String local, - String qName, - Attributes atts) { + public void startElement(String uri, String local, String qName, + Attributes atts) { if (TAG_THEME.equals(qName)) { this.name = atts.getValue(ATTR_NAME); @@ -241,28 +322,28 @@ public class Theme extends DefaultHandler { } else if (TAG_EXTENDS.equals(qName)) { String themeName = atts.getValue(ATTR_THEME); if (this.name.equals(themeName)) - throw new IllegalArgumentException( - "Theme " + this.name + " extends itself."); + throw new IllegalArgumentException("Theme " + this.name + + " extends itself."); this.parentThemes.add(themeName); } else if (TAG_FILE.equals(qName)) { File f = new File(atts.getValue(ATTR_NAME)); if (this.openFilesets.isEmpty()) { - throw new IllegalStateException( - "Element '" - + TAG_FILE - + "' must be within '" - + TAG_FILESET - + "' element."); + throw new IllegalStateException("Element '" + TAG_FILE + + "' must be within '" + TAG_FILESET + "' element."); } Fileset fs = (Fileset) this.openFilesets.peek(); fs.addFile(f); } else if (TAG_FILESET.equals(qName)) { Fileset fs; - if (atts.getValue(ATTR_NAME) != null) { - fs = new Fileset(atts.getValue(ATTR_NAME)); - } else { - fs = new Fileset(atts.getValue(UNNAMED_FILESET)); - } + String mode = atts.getValue(ATTR_MODE); + if (mode != null && mode.length() == 0) + mode = null; + if (mode != null && !mode.equals(MODE_UIDL) + && !mode.equals(MODE_XSLT)) + throw new IllegalStateException("Given mode '" + mode + + "' is not supported. (This version only supports '" + + MODE_XSLT + "' and '" + MODE_UIDL + "')"); + fs = new Fileset(mode); // Use the first fileset as root fileset if (this.files == null) { @@ -276,43 +357,56 @@ public class Theme extends DefaultHandler { this.openFilesets.push(fs); } else if (TAG_AUTHOR.equals(qName)) { - this.author = - new Author(atts.getValue(ATTR_NAME), atts.getValue(ATTR_EMAIL)); + this.author = new Author(atts.getValue(ATTR_NAME), atts + .getValue(ATTR_EMAIL)); + } else if (TAG_MODES.equals(qName)) { + if (modesListCurrentlyOpen) + throw new IllegalStateException( + "Modes element can not be inside another modes element"); + modesListCurrentlyOpen = true; + } else if (TAG_MODE.equals(qName)) { + if (!modesListCurrentlyOpen) + throw new IllegalStateException( + "Mode elements must be placed inside modes element"); + if (currentlyOpenMode != null) + throw new IllegalStateException( + "No mode is allowed inside mode"); + String name = atts.getValue(ATTR_NAME); + if (name == null || name.length() == 0) + throw new IllegalStateException( + "Name is required for mode elements"); + this.currentlyOpenMode = name; + RequirementCollection rc = new AndRequirement(); + supportedModes.put(name, rc); } - // Requirements else if (TAG_REQUIRE.equals(qName)) { - if (this.openFilesets.isEmpty()) { - throw new IllegalStateException( - "Element '" - + TAG_REQUIRE - + "' must be within '" - + TAG_FILESET - + "' element."); + if (currentlyOpenMode != null) { + RequirementCollection rc = (RequirementCollection) supportedModes + .get(this.currentlyOpenMode); + if (rc == null) + throw new IllegalStateException("Tried to add requirements to mode '" + name + "', but requirements set was not properly created. (internal error)"); + this.openRequirements.push(rc); + } else { + if (this.openFilesets.isEmpty()) { + throw new IllegalStateException("Element '" + TAG_REQUIRE + + "' must be within '" + TAG_FILESET + "' element."); + } + Fileset fs = (Fileset) this.openFilesets.peek(); + this.openRequirements.push(fs.getRequirements()); } - Fileset fs = (Fileset) this.openFilesets.peek(); - this.openRequirements.push(fs.getRequirements()); } else if (TAG_SUPPORTS.equals(qName)) { - if (this.openFilesets.isEmpty()) { - throw new IllegalStateException( - "Element '" - + TAG_REQUIRE - + "' must be within '" - + TAG_FILESET - + "' element."); + if (this.openFilesets.isEmpty() && currentlyOpenMode == null) { + throw new IllegalStateException("Element '" + TAG_REQUIRE + + "' must be within '" + TAG_FILESET + "' element."); } if (this.openRequirements.isEmpty()) { - throw new IllegalStateException( - "Element '" - + TAG_SUPPORTS - + "' must be within '" - + TAG_REQUIRE - + "' element."); + throw new IllegalStateException("Element '" + TAG_SUPPORTS + + "' must be within '" + TAG_REQUIRE + "' element."); } - this.addRequirements( - atts, - (RequirementCollection) this.openRequirements.peek(), - this.isNOTRequirementOpen); + this.addRequirements(atts, + (RequirementCollection) this.openRequirements.peek(), + this.isNOTRequirementOpen); } else if (TAG_NOT.equals(qName)) { this.isNOTRequirementOpen = true; } else if (TAG_AND.equals(qName)) { @@ -322,50 +416,58 @@ public class Theme extends DefaultHandler { } } - /** Parse end tag in XML stream. + /** + * Parse end tag in XML stream. + * * @see org.xml.sax.ContentHandler#endElement(String, String, String) */ public void endElement(String namespaceURI, String localName, String qName) - throws SAXException { + throws SAXException { if (TAG_FILESET.equals(qName)) { this.openFilesets.pop(); } else if (TAG_DESCRIPTION.equals(qName)) { - this.description = - ((StringBuffer) this.openStrings.pop()).toString(); + this.description = ((StringBuffer) this.openStrings.pop()) + .toString(); } else if (TAG_REQUIRE.equals(qName)) { this.openRequirements.pop(); - } else if (TAG_NOT.equals(qName)) { this.isNOTRequirementOpen = false; + } else if (TAG_MODES.equals(qName)) { + this.modesListCurrentlyOpen = false; + } else if (TAG_MODE.equals(qName)) { + this.currentlyOpenMode = null; } } - /** Parse character data in XML stream. + /** + * Parse character data in XML stream. + * * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ public void characters(char[] data, int start, int length) { // if stack is not ready, data is not content of recognized element if (!this.openStrings.isEmpty()) { - ((StringBuffer) this.openStrings.peek()).append( - data, - start, - length); + ((StringBuffer) this.openStrings.peek()) + .append(data, start, length); } else { // read data which is not part of recognized element } } - /** Add all requirements specified in attributes to fileset. - * @param atts Attribute set - * @param requirements Collection where to add requirement rules. - * @param applyNot Should the meaning of these requirement be negated. + /** + * Add all requirements specified in attributes to fileset. + * + * @param atts + * Attribute set + * @param requirements + * Collection where to add requirement rules. + * @param applyNot + * Should the meaning of these requirement be negated. */ - private void addRequirements( - Attributes atts, - RequirementCollection requirements, - boolean applyNot) { + private void addRequirements(Attributes atts, + RequirementCollection requirements, boolean applyNot) { // Create temporary collection for requirements Collection tmpReqs = new LinkedList(); @@ -374,22 +476,20 @@ public class Theme extends DefaultHandler { for (int i = 0; i < atts.getLength(); i++) { req = null; if (ATTR_JAVASCRIPT.equals(atts.getQName(i))) { - req = - new JavaScriptRequirement( - WebBrowser.parseJavaScriptVersion(atts.getValue(i))); + req = new JavaScriptRequirement(WebBrowser + .parseJavaScriptVersion(atts.getValue(i))); } else if (ATTR_AGENT.equals(atts.getQName(i))) { req = new AgentRequirement(atts.getValue(i)); } else if (ATTR_MARKUP.equals(atts.getQName(i))) { - req = - new MarkupLanguageRequirement( - WebBrowser.parseHTMLVersion(atts.getValue(i))); + req = new MarkupLanguageRequirement(WebBrowser + .parseHTMLVersion(atts.getValue(i))); } // Add to temporary requirement collection and clear reference if (req != null) tmpReqs.add(req); } - // Create implicit AND requirement if more than one + // Create implicit AND requirement if more than one // Rrequirements were specified in attributes if (tmpReqs.size() > 1) { req = new AndRequirement(tmpReqs); @@ -404,7 +504,9 @@ public class Theme extends DefaultHandler { requirements.addRequirement(req); } - /** Get list of all files in this theme. + /** + * Get list of all files in this theme. + * * @return List of filenames belonging to this theme. */ public List getFileNames() { @@ -413,61 +515,66 @@ public class Theme extends DefaultHandler { return files.getFileNames(); } - /** Get list of file names matching WebBrowserType. + /** + * Get list of file names matching WebBrowserType. + * * @return list of filenames in this theme supporting the given terminal. */ - public List getFileNames(WebBrowser terminal) { + public List getFileNames(WebBrowser terminal, String mode) { if (files == null) return new LinkedList(); - return this.files.getFileNames(terminal); + return this.files.getFileNames(terminal, mode); } - /** String representation of Theme object. - * Used for debugging purposes only. - * @see java.lang.Object#toString() + /** + * String representation of Theme object. Used for debugging purposes only. + * + * @see java.lang.Object#toString() */ public String toString() { - return this.name - + " author=[" - + this.author - + "]" - + " inherits=" - + parentThemes - + "]" - + " files={" - + (files != null ? files.toString() : "null") - + "}"; + return this.name + " author=[" + this.author + "]" + " inherits=" + + parentThemes + "]" + " files={" + + (files != null ? files.toString() : "null") + "}"; } - /** Author information class. - * This class represents an single author of a theme package. - * Authors have name and contact email address properties. + /** + * Author information class. This class represents an single author of a + * theme package. Authors have name and contact email address properties. + * * @author IT Mill Ltd. - * @version @VERSION@ - * @since 3.0 + * @version + * @VERSION@ + * @since 3.0 */ public class Author { private String name; + private String email; public Author(String name, String email) { this.name = name; this.email = email; } - /** Get the name of the author. + + /** + * Get the name of the author. + * * @return Name of the author. */ public String getName() { return this.name; } - /** Get the email address of the author. + /** + * Get the email address of the author. + * * @return Email address of the author. */ public String getEmail() { return this.email; } + /** * @see java.lang.Object#toString() */ @@ -476,62 +583,86 @@ public class Theme extends DefaultHandler { } } - /** Generic requirement. - * Interface implemented by reuirements introducing - * method for checking compability with given terminal. + /** + * Generic requirement. Interface implemented by reuirements introducing + * method for checking compability with given terminal. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public interface Requirement { - /** Check that this requirement is met by given type of browser. - * @param terminal type of the web browser. - * @return True if terminal is compatible with this rule. False otherwise. + /** + * Check that this requirement is met by given type of browser. + * + * @param terminal + * type of the web browser. + * @return True if terminal is compatible with this rule. False + * otherwise. */ public boolean isMet(WebBrowser terminal); } - /** Generic requirement collection interface. - * Requirement collection introducing methods for - * combining requirements into single requirement. + /** + * Generic requirement collection interface. Requirement collection + * introducing methods for combining requirements into single requirement. + * * @author IT Mill Ltd. - * @version @VERSION@ - * @since 3.0 + * @version + * @VERSION@ + * @since 3.0 */ public interface RequirementCollection extends Requirement { - /** Add new requirement to this collection. - * @param requirement Requirement to be added. + /** + * Add new requirement to this collection. + * + * @param requirement + * Requirement to be added. */ public void addRequirement(Requirement requirement); - /** Remove a requirement from this collection. - * @param requirement Requirement to be removed. + /** + * Remove a requirement from this collection. + * + * @param requirement + * Requirement to be removed. */ public void removeRequirement(Requirement requirement); } - /** Logical NOT requirement. - * Requirement implementing logical NOT operation. - * Wraps an another requirement and negates the meaning of it. + /** + * Logical NOT requirement. Requirement implementing logical NOT operation. + * Wraps an another requirement and negates the meaning of it. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class NotRequirement implements Requirement { private Requirement requirement; - /** Create new NOT requirement based on another requirement. - * @param requirement The requirement to ne negated. + + /** + * Create new NOT requirement based on another requirement. + * + * @param requirement + * The requirement to ne negated. */ public NotRequirement(Requirement requirement) { this.requirement = requirement; } - /** Check that this requirement is met by given type of browser. - * @param terminal type of the web browser. - * @return True if terminal is compatible with this rule. False otherwise. + /** + * Check that this requirement is met by given type of browser. + * + * @param terminal + * type of the web browser. + * @return True if terminal is compatible with this rule. False + * otherwise. */ public boolean isMet(WebBrowser terminal) { return !this.requirement.isMet(terminal); @@ -546,11 +677,13 @@ public class Theme extends DefaultHandler { } - /** Logical AND requirement. - * Implements a collection of requirements combining the - * included requirements using logical AND operation. + /** + * Logical AND requirement. Implements a collection of requirements + * combining the included requirements using logical AND operation. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class AndRequirement implements RequirementCollection { @@ -577,7 +710,9 @@ public class Theme extends DefaultHandler { this.requirements.remove(requirement); } - /** Checks that all os the requirements in this collection are met. + /** + * Checks that all os the requirements in this collection are met. + * * @see Theme.Requirement#isMet(WebBrowser) */ public boolean isMet(WebBrowser terminal) { @@ -600,11 +735,13 @@ public class Theme extends DefaultHandler { } - /** Logical OR requirement. - * Implements a collection of requirements combining the - * included requirements using logical AND operation. + /** + * Logical OR requirement. Implements a collection of requirements combining + * the included requirements using logical AND operation. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class OrRequirement implements RequirementCollection { @@ -631,7 +768,9 @@ public class Theme extends DefaultHandler { this.requirements.remove(requirement); } - /** Checks that some of the requirements in this collection is met. + /** + * Checks that some of the requirements in this collection is met. + * * @see Theme.Requirement#isMet(WebBrowser) */ public boolean isMet(WebBrowser terminal) { @@ -653,11 +792,14 @@ public class Theme extends DefaultHandler { } } - /** HTTP user agent requirement - * This requirements is used to ensure that the User-Agent string - * provided in HTTP request headers contains given substring. + /** + * HTTP user agent requirement This requirements is used to ensure that the + * User-Agent string provided in HTTP request headers contains given + * substring. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class AgentRequirement implements Requirement { @@ -669,14 +811,10 @@ public class Theme extends DefaultHandler { } public boolean isMet(WebBrowser terminal) { - if (terminal.getBrowserApplication().indexOf(this.agentSubstring) - > 0) + if (terminal.getBrowserApplication().indexOf(this.agentSubstring) > 0) return true; - Log.info( - "Requirement: '" - + this.agentSubstring - + "' is not met by " - + terminal.getBrowserApplication()); + Log.info("Requirement: '" + this.agentSubstring + + "' is not met by " + terminal.getBrowserApplication()); return false; } @@ -688,11 +826,13 @@ public class Theme extends DefaultHandler { } } - /** Javascript version requirement - * This requirement is used to ensure a certain level of - * JavaScript version support. + /** + * Javascript version requirement This requirement is used to ensure a + * certain level of JavaScript version support. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class JavaScriptRequirement implements Requirement { @@ -700,17 +840,14 @@ public class Theme extends DefaultHandler { private WebBrowser.JavaScriptVersion requiredVersion; public JavaScriptRequirement( - WebBrowser.JavaScriptVersion requiredVersion) { + WebBrowser.JavaScriptVersion requiredVersion) { this.requiredVersion = requiredVersion; } public boolean isMet(WebBrowser terminal) { if (terminal.getJavaScriptVersion().supports(this.requiredVersion)) return true; - Log.info( - "Requirement: " - + this.requiredVersion - + " is not met by " + Log.info("Requirement: " + this.requiredVersion + " is not met by " + terminal.getJavaScriptVersion()); return false; } @@ -723,11 +860,13 @@ public class Theme extends DefaultHandler { } } - /** Markup language version requirement - * This requirement is used to ensure a certain level of - * Markup language version support. + /** + * Markup language version requirement This requirement is used to ensure a + * certain level of Markup language version support. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class MarkupLanguageRequirement implements Requirement { @@ -735,17 +874,14 @@ public class Theme extends DefaultHandler { private WebBrowser.MarkupVersion requiredVersion; public MarkupLanguageRequirement( - WebBrowser.MarkupVersion requiredVersion) { + WebBrowser.MarkupVersion requiredVersion) { this.requiredVersion = requiredVersion; } public boolean isMet(WebBrowser terminal) { if (terminal.getMarkupVersion().supports(this.requiredVersion)) return true; - Log.info( - "Requirement: " - + this.requiredVersion - + " is not met by " + Log.info("Requirement: " + this.requiredVersion + " is not met by " + terminal.getMarkupVersion()); return false; @@ -760,34 +896,43 @@ public class Theme extends DefaultHandler { } - /** Theme XSL file description - * Description of a single XSL file included a theme. + /** + * Theme XSL file description Description of a single XSL file included a + * theme. + * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class File { private String name; - /** Create new file. - * @param name Name of the file. + /** + * Create new file. + * + * @param name + * Name of the file. */ public File(String name) { this.name = name; } - /** Get name of the file. - * The file name is relative and unique within a theme. - * @return Name of the file. + /** + * Get name of the file. The file name is relative and unique within a + * theme. + * + * @return Name of the file. */ public String getName() { return this.name; } - /** Does this file support the given terminal. - * Single file requirements are not supported and - * therefore this always returns true. + /** + * Does this file support the given terminal. Single file requirements + * are not supported and therefore this always returns true. + * * @see Theme.Fileset * @return Always returns true. */ @@ -804,10 +949,13 @@ public class Theme extends DefaultHandler { } - /** A recursive set of files sharing the same requirements. + /** + * A recursive set of files sharing the same requirements. + * * @author IT Mill Ltd. - * @version @VERSION@ - * @since 3.0 + * @version + * @VERSION@ + * @since 3.0 */ public class Fileset extends File { @@ -815,22 +963,20 @@ public class Theme extends DefaultHandler { private Collection files = new LinkedList(); - /** Create new empty fileset. - * @param name Name of the fileset. - */ - public Fileset(String name) { - super(name); - } + private String mode; - /** Create new fileset. - * @param name Name of the fileset. - * @param files Collection of files to include in the set. + /** + * Create new empty fileset. + * + * @param name + * Name of the fileset. */ - public Fileset(String name, Collection files) { - super(name); + public Fileset(String mode) { + super(null); + this.mode = mode; } - /**Add a file into fileset. */ + /** Add a file into fileset. */ private void addFile(File file) { this.files.add(file); } @@ -840,7 +986,9 @@ public class Theme extends DefaultHandler { return this.requirements; } - /** Get list of all files in this theme. + /** + * Get list of all files in this theme. + * * @return list of filenames. */ public List getFileNames() { @@ -861,14 +1009,20 @@ public class Theme extends DefaultHandler { } - /** Get list of file names matching WebBrowserType. + /** + * Get list of file names matching WebBrowserType. + * * @return list of filenames supporting the given terminal. */ - public List getFileNames(WebBrowser terminal) { + public List getFileNames(WebBrowser terminal, String mode) { List list = new LinkedList(); - if (!this.supports(terminal)) + // If this set is not supported by the terminal or is explicitly set + // into + // another mode, no files are given + if (!this.supports(terminal) + || (this.mode != null && !this.mode.equals(mode))) return list; for (Iterator i = this.files.iterator(); i.hasNext();) { @@ -877,7 +1031,7 @@ public class Theme extends DefaultHandler { // Recursively add included filesets if they are // supported if (f instanceof Fileset) { - list.addAll(((Fileset) f).getFileNames(terminal)); + list.addAll(((Fileset) f).getFileNames(terminal, mode)); } else { list.add(f.getName()); @@ -886,16 +1040,15 @@ public class Theme extends DefaultHandler { return list; } - /** Does this file support the given terminal. + /** + * Does this file support the given terminal. + * * @return True if fileset supports the given browser. False otherwise. */ public boolean supports(WebBrowser terminal) { if (requirements.isMet(terminal)) return true; - Log.info( - "Skipped fileset " - + Theme.this.getName() - + "/" + Log.info("Skipped fileset " + Theme.this.getName() + "/" + this.getName() + " because all requirements were not met."); return false; @@ -905,45 +1058,39 @@ public class Theme extends DefaultHandler { * @see java.lang.Object#toString() */ public String toString() { - return "name=[" - + this.getName() - + "] requires=[" - + this.requirements - + "] files=[" - + files - + "]"; + return "name=[" + this.getName() + "] requires=[" + + this.requirements + "] files=[" + files + "]"; } } - /** Returns the author of this theme. + /** + * Returns the author of this theme. + * * @return Author of the theme. */ public Author getAuthor() { return author; } - /** Returns the name of this theme. + /** + * Returns the name of this theme. + * * @return Name of the theme. */ public String getName() { return name; } - /** Returns the list of parent themes of this theme. - * Returns list of all inherited themes in the inheritance order. + /** + * Returns the list of parent themes of this theme. Returns list of all + * inherited themes in the inheritance order. + * * @return List of parent theme instances. */ public List getParentThemes() { return parentThemes; } - /** Returns the version of this theme. - * @return Version string - */ - public String getVersion() { - return version; - } - /** Get theme description */ public String getDescription() { return description; diff --git a/src/com/itmill/toolkit/terminal/web/ThemeFunctionLibrary.java b/src/com/itmill/toolkit/terminal/web/ThemeFunctionLibrary.java index cca9191a34..6a9398f049 100644 --- a/src/com/itmill/toolkit/terminal/web/ThemeFunctionLibrary.java +++ b/src/com/itmill/toolkit/terminal/web/ThemeFunctionLibrary.java @@ -1,30 +1,30 @@ /* ************************************************************************* - IT Mill Toolkit + IT Mill Toolkit - Development of Browser User Intarfaces Made Easy + Development of Browser User Intarfaces Made Easy - Copyright (C) 2000-2006 IT Mill Ltd - - ************************************************************************* + Copyright (C) 2000-2006 IT Mill Ltd + + ************************************************************************* - This product is distributed under commercial license that can be found - from the product package on license/license.txt. Use of this product might - require purchasing a commercial license from IT Mill Ltd. For guidelines - on usage, see license/licensing-guidelines.html + This product is distributed under commercial license that can be found + from the product package on license/license.txt. Use of this product might + require purchasing a commercial license from IT Mill Ltd. For guidelines + on usage, see license/licensing-guidelines.html - ************************************************************************* - - For more information, contact: - - IT Mill Ltd phone: +358 2 4802 7180 - Ruukinkatu 2-4 fax: +358 2 4802 7181 - 20540, Turku email: info@itmill.com - Finland company www: www.itmill.com - - Primary source for information and releases: www.itmill.com + ************************************************************************* + + For more information, contact: + + IT Mill Ltd phone: +358 2 4802 7180 + Ruukinkatu 2-4 fax: +358 2 4802 7181 + 20540, Turku email: info@itmill.com + Finland company www: www.itmill.com + + Primary source for information and releases: www.itmill.com - ********************************************************************** */ + ********************************************************************** */ package com.itmill.toolkit.terminal.web; @@ -35,6 +35,7 @@ import com.itmill.toolkit.ui.Window; import java.text.DateFormatSymbols; import java.util.Calendar; +import java.util.Collection; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; @@ -42,50 +43,48 @@ import java.util.Set; import javax.servlet.http.HttpSession; -/** This a function library that can be used from the theme XSL-files. It provides - * easy access to current application, window, theme, webbrowser and session. The - * internal threadlocal state must be maintained by the webadapter in order go guarantee - * that it works. +/** + * This a function library that can be used from the theme XSL-files. It + * provides easy access to current application, window, theme, webbrowser and + * session. The internal threadlocal state must be maintained by the webadapter + * in order go guarantee that it works. * * @author IT Mill Ltd. - * @version @VERSION@ + * @version + * @VERSION@ * @since 3.0 */ public class ThemeFunctionLibrary { static private final int APPLICATION = 0; + static private final int WINDOW = 1; + static private final int WEBBROWSER = 2; + static private final int SESSION = 3; + static private final int WEBADAPTERSERVLET = 4; + static private final int THEME = 5; static private ThreadLocal state = new ThreadLocal(); - static protected void setState( - Application application, - Window window, - WebBrowser webBrowser, - HttpSession session, - ApplicationServlet webAdapterServlet, - String theme) { - state.set( - new Object[] { - application, - window, - webBrowser, - session, - webAdapterServlet, - theme }); + static protected void setState(Application application, Window window, + WebBrowser webBrowser, HttpSession session, + ApplicationServlet webAdapterServlet, String theme) { + state.set(new Object[] { application, window, webBrowser, session, + webAdapterServlet, theme }); } static protected void cleanState() { state.set(null); } - /** Returns a reference to the application object associated - * with the session that the call came from. + /** + * Returns a reference to the application object associated with the session + * that the call came from. */ static public Application application() { try { @@ -95,8 +94,9 @@ public class ThemeFunctionLibrary { } } - /** Returns a reference to the current window object associated - * with the session that the call came from. + /** + * Returns a reference to the current window object associated with the + * session that the call came from. */ static public Window window() { try { @@ -106,8 +106,9 @@ public class ThemeFunctionLibrary { } } - /** Returns a reference to the browser object associated - * with the session that the call came from. + /** + * Returns a reference to the browser object associated with the session + * that the call came from. */ static public WebBrowser browser() { try { @@ -117,8 +118,9 @@ public class ThemeFunctionLibrary { } } - /** Returns a reference to the current servlet http session object - * that is associated with the session that the call came from. + /** + * Returns a reference to the current servlet http session object that is + * associated with the session that the call came from. */ static public HttpSession session() { try { @@ -128,8 +130,9 @@ public class ThemeFunctionLibrary { } } - /** Return a reference to the current theme object that is - * associated with the session that the call came from. + /** + * Return a reference to the current theme name that is associated with the + * session that the call came from. */ static public String theme() { try { @@ -139,74 +142,62 @@ public class ThemeFunctionLibrary { } } - /** Return an URI to the named resource from the named theme. + /** + * Return an URI to the named resource from the named theme. */ static public String resource(String resource, String theme) { try { - return ( - (ApplicationServlet) - ( - (Object[]) state - .get())[WEBADAPTERSERVLET]) - .getResourceLocation( - theme, - new ThemeResource(resource)); + return ((ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET]) + .getResourceLocation(theme, new ThemeResource(resource)); } catch (NullPointerException e) { throw new IllegalStateException(); } } - /** Return an URI to the named resource. + /** + * Return an URI to the named resource. */ static public String resource(String resource) { try { - return ( - (ApplicationServlet) - ( - (Object[]) state - .get())[WEBADAPTERSERVLET]) - .getResourceLocation( - theme(), - new ThemeResource(resource)); + return ((ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET]) + .getResourceLocation(theme(), new ThemeResource(resource)); } catch (NullPointerException e) { throw new IllegalStateException(); } } - /** Generate JavaScript for page that performs - * client-side combility checks. - * The script includes HTML/JavaScript commands to be included - * in the body of the millstone-form. + /** + * Generate JavaScript for page that performs client-side combility checks. + * The script includes HTML/JavaScript commands to be included in the body + * of the millstone-form. */ static public boolean probeClient() { - return ( - browser().performClientCheck() && !browser().isClientSideChecked()); + return (browser().performClientCheck() && !browser() + .isClientSideChecked()); } - /** Generate JavaScript for page header that handles - * window refreshing, opening and closing. + /** + * Generate JavaScript for page header that handles window refreshing, + * opening and closing. * * Generates script that: - * <ul> - * <li>Requests that all windows that need repaint be reloaded</li> - * <li>Sets the window name</li> - * <li>Closes window if it is set to be closed </li> + * <ul> + * <li>Requests that all windows that need repaint be reloaded</li> + * <li>Sets the window name</li> + * <li>Closes window if it is set to be closed </li> * <ul> * */ static public String windowScript() { return generateWindowScript( - window(), - application(), - (ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET], - browser()); + window(), + application(), + (ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET], + browser()); } - static protected String generateWindowScript( - Window window, - Application app, - ApplicationServlet wa, - WebBrowser browser) { + static protected String generateWindowScript(Window window, + Application app, ApplicationServlet wa, WebBrowser browser) { StringBuffer script = new StringBuffer(); LinkedList update = new LinkedList(); @@ -235,11 +226,9 @@ public class ThemeFunctionLibrary { LinkedList framesets = new LinkedList(); framesets.add(w.getFrameset()); while (!framesets.isEmpty()) { - FrameWindow.Frameset fs = - (FrameWindow.Frameset) framesets.removeFirst(); - for (Iterator j = fs.getFrames().iterator(); - j.hasNext(); - ) { + FrameWindow.Frameset fs = (FrameWindow.Frameset) framesets + .removeFirst(); + for (Iterator j = fs.getFrames().iterator(); j.hasNext();) { FrameWindow.Frame f = (FrameWindow.Frame) j.next(); if (f instanceof FrameWindow.Frameset) framesets.add(f); @@ -255,9 +244,7 @@ public class ThemeFunctionLibrary { // Set window name if (window != null) { - script.append( - "window.name = \"" - + getWindowTargetName(app, window) + script.append("window.name = \"" + getWindowTargetName(app, window) + "\";\n"); } @@ -280,14 +267,17 @@ public class ThemeFunctionLibrary { return script.toString(); } - /** Returns an unique target name for a given window name. - * @param windowName Name of the window. - * @return An unique ID for window target - * @throws IllegalStateException If application for window is null. + /** + * Returns an unique target name for a given window name. + * + * @param windowName + * Name of the window. + * @return An unique ID for window target + * @throws IllegalStateException + * If application for window is null. */ - static public String getWindowTargetName( - Application application, - Window window) { + static public String getWindowTargetName(Application application, + Window window) { try { return "" + application.hashCode() + "_" + window.getName(); } catch (NullPointerException e) { @@ -295,16 +285,21 @@ public class ThemeFunctionLibrary { } } - /** Returns an unique target name for current window. - * @return An unique ID for window target + /** + * Returns an unique target name for current window. + * + * @return An unique ID for window target */ static public String getWindowTargetName() { return getWindowTargetName(application(), window()); } - /** Returns an unique target name for current window. - * @return An unique ID for window target - * @throws IllegalStateException If application for window is null. + /** + * Returns an unique target name for current window. + * + * @return An unique ID for window target + * @throws IllegalStateException + * If application for window is null. */ static public String getWindowTargetName(String name) { Window w = application().getWindow(name); @@ -315,52 +310,50 @@ public class ThemeFunctionLibrary { } /* Static mapping for 0 to be sunday. */ - private static int[] weekdays = - new int[] { - Calendar.SUNDAY, - Calendar.MONDAY, - Calendar.TUESDAY, - Calendar.WEDNESDAY, - Calendar.THURSDAY, - Calendar.FRIDAY, - Calendar.SATURDAY }; - - - /** Returns the country and region code for current application locale. + private static int[] weekdays = new int[] { Calendar.SUNDAY, + Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, + Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; + + /** + * Returns the country and region code for current application locale. + * * @see Locale#getCountry() * @return language Country code of the current application locale. */ static public String getLocaleCountryId() { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; return app.getLocale().getCountry(); } catch (NullPointerException e) { throw new IllegalStateException(); } } - /** Returns the language code for current application locale. + + /** + * Returns the language code for current application locale. + * * @see Locale#getLanguage() * @return language Language code for current application locale. */ static public String getLocaleLanguageId() { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; return app.getLocale().getLanguage(); } catch (NullPointerException e) { throw new IllegalStateException(); } } - /** Get name for week day. - * @param Number of week day. 0 first day of week. + /** + * Get name for week day. + * + * @param Number + * of week day. 0 first day of week. * @return Name of week day in applications current locale. */ static public int getFirstDayOfWeek() { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; Calendar cal = new GregorianCalendar(app.getLocale()); int first = cal.getFirstDayOfWeek(); for (int i = 0; i < 7; i++) { @@ -373,14 +366,16 @@ public class ThemeFunctionLibrary { } } - /** Get name for week day. - * @param Number of week day. 0 sunday, 1 monday, ... + /** + * Get name for week day. + * + * @param Number + * of week day. 0 sunday, 1 monday, ... * @return Name of week day in applications current locale. */ static public String getShortWeekday(int dayOfWeek) { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; DateFormatSymbols df = new DateFormatSymbols(app.getLocale()); return df.getShortWeekdays()[weekdays[dayOfWeek]]; } catch (NullPointerException e) { @@ -388,14 +383,16 @@ public class ThemeFunctionLibrary { } } - /** Get short name for month. - * @param Number of month. 0 is January, 1 is February, and so on. + /** + * Get short name for month. + * + * @param Number + * of month. 0 is January, 1 is February, and so on. * @return Name of month in applications current locale. */ static public String getShortMonth(int month) { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; DateFormatSymbols df = new DateFormatSymbols(app.getLocale()); String monthName = df.getShortMonths()[month]; return monthName; @@ -404,14 +401,16 @@ public class ThemeFunctionLibrary { } } - /** Get name for month. - * @param Number of month. 0 is January, 1 is February, and so on. + /** + * Get name for month. + * + * @param Number + * of month. 0 is January, 1 is February, and so on. * @return Name of month in applications current locale. */ static public String getMonth(int month) { try { - Application app = - (Application) ((Object[]) state.get())[APPLICATION]; + Application app = (Application) ((Object[]) state.get())[APPLICATION]; DateFormatSymbols df = new DateFormatSymbols(app.getLocale()); String monthName = df.getMonths()[month]; return monthName; @@ -420,26 +419,61 @@ public class ThemeFunctionLibrary { } } - /** Get Form Action URL for the requested window. + /** + * Get Form Action URL for the requested window. * - * <p>This returns the action for the window main form. This action - * can be set through WebApplicationContect setWindowFormAction method..</p> + * <p> + * This returns the action for the window main form. This action can be set + * through WebApplicationContect setWindowFormAction method.. + * </p> * * @return Form action for the current window. */ static public String getFormAction() { - + Window win = window(); Application app = application(); - - return ((WebApplicationContext)app.getContext()).getWindowFormAction(win); + + return ((WebApplicationContext) app.getContext()) + .getWindowFormAction(win); + } + + /** Generate links for CSS files to be included in html head. */ + static public String getCssLinksForHead() { + ApplicationServlet as = (ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET]; + Theme t = as.getThemeSource().getThemeByName(theme()); + Collection allFiles = t.getFileNames(browser(), Theme.MODE_XSLT); + StringBuffer links = new StringBuffer(); + for (Iterator i = allFiles.iterator(); i.hasNext();) { + String file = (String) i.next(); + if (file.endsWith(".css")) { + links + .append("<LINK REL=\"STYLESHEET\" TYPE=\"text/css\" HREF=\"" + + resource(file) + "\"/>\n"); + } + } + return links.toString(); + } + + /** Generate links for JavaScript files to be included in html head. */ + static public String getJavaScriptLinksForHead() { + ApplicationServlet as = (ApplicationServlet) ((Object[]) state.get())[WEBADAPTERSERVLET]; + Theme t = as.getThemeSource().getThemeByName(theme()); + Collection allFiles = t.getFileNames(browser(), Theme.MODE_XSLT); + StringBuffer links = new StringBuffer(); + for (Iterator i = allFiles.iterator(); i.hasNext();) { + String file = (String) i.next(); + if (file.endsWith(".js")) { + links.append("<SCRIPT LANGUAGE=\"Javascript\" SRC=\"" + + resource(file) + "\"></SCRIPT>\n"); + } + } + return links.toString(); } /** Generate JavaScript for updating given window */ - static protected String getWindowRefreshScript( - Application application, - Window window, - WebBrowser browser) { + static protected String getWindowRefreshScript(Application application, + Window window, WebBrowser browser) { if (application == null) return ""; @@ -453,9 +487,8 @@ public class ThemeFunctionLibrary { // If window is closed or hidden if (window.getApplication() == null || !window.isVisible()) return "win = window.open(\"\",\"" - + getWindowTargetName(application, window) - + "\");\n " - + "if (win != null) { win.close(); }\n"; + + getWindowTargetName(application, window) + "\");\n " + + "if (win != null) { win.close(); }\n"; String url = window.getURL().toString(); @@ -465,51 +498,43 @@ public class ThemeFunctionLibrary { if (width >= 0) features += "width=" + width; if (height >= 0) - features += ((features.length() > 0) ? "," : "") - + "height=" - + height; + features += ((features.length() > 0) ? "," : "") + "height=" + + height; switch (window.getBorder()) { - case Window.BORDER_NONE : - features += ((features.length() > 0) ? "," : "") + case Window.BORDER_NONE: + features += ((features.length() > 0) ? "," : "") + "toolbar=0,location=0,menubar=0,status=0,resizable=1,scrollbars=" + (window.isScrollable() ? "1" : "0"); - break; - case Window.BORDER_MINIMAL : - features += ((features.length() > 0) ? "," : "") + break; + case Window.BORDER_MINIMAL: + features += ((features.length() > 0) ? "," : "") + "toolbar=1,location=0,menubar=0,status=1,resizable=1,scrollbars=" + (window.isScrollable() ? "1" : "0"); - break; - case Window.BORDER_DEFAULT : - features += ((features.length() > 0) ? "," : "") + break; + case Window.BORDER_DEFAULT: + features += ((features.length() > 0) ? "," : "") + "toolbar=1,location=1,menubar=1,status=1,resizable=1,scrollbars=" + (window.isScrollable() ? "1" : "0"); - break; + break; } - String script = - "win = window.open(\"\",\"" - + getWindowTargetName(application, window) - + "\",\"" - + features - + "\");\n" - + "if (win != null) {" - + "var form = null;"; + String script = "win = window.open(\"\",\"" + + getWindowTargetName(application, window) + "\",\"" + features + + "\");\n" + "if (win != null) {" + "var form = null;"; if (browser != null - && (browser.getJavaScriptVersion().supports(WebBrowser.JAVASCRIPT_1_5) - || browser.getJavaScriptVersion().supports( - WebBrowser.JSCRIPT_1_0))) { + && (browser.getJavaScriptVersion().supports( + WebBrowser.JAVASCRIPT_1_5) || browser + .getJavaScriptVersion() + .supports(WebBrowser.JSCRIPT_1_0))) { script += "try { form = win.document.forms[\"millstone\"];" - + "} catch (e) { form = null;}"; + + "} catch (e) { form = null;}"; } else { script += "form = win.document.forms[\"millstone\"];"; } - script += "if (form != null) {" - + "form.submit();" - + "} else {win.location.href = \"" - + url - + "\";}}"; + script += "if (form != null) {" + "form.submit();" + + "} else {win.location.href = \"" + url + "\";}}"; return script; } diff --git a/src/com/itmill/toolkit/terminal/web/ThemeSource.java b/src/com/itmill/toolkit/terminal/web/ThemeSource.java index 300e9488c4..84bd9a722d 100644 --- a/src/com/itmill/toolkit/terminal/web/ThemeSource.java +++ b/src/com/itmill/toolkit/terminal/web/ThemeSource.java @@ -48,6 +48,9 @@ public interface ThemeSource { * UIDL data. The <code>type</code> parameter is used to limit * the templates, which are returned based on the theme fileset * requirements. + * + * This implicitly operates in xslt mode. + * * @param theme Theme, which XSL should be returned * @param type The type of the current client. * @return Collection of ThemeSource.XSLStream objects. diff --git a/src/com/itmill/toolkit/terminal/web/WebApplicationContext.java b/src/com/itmill/toolkit/terminal/web/WebApplicationContext.java index 4d29e2e89f..5df7fe66c9 100644 --- a/src/com/itmill/toolkit/terminal/web/WebApplicationContext.java +++ b/src/com/itmill/toolkit/terminal/web/WebApplicationContext.java @@ -28,7 +28,6 @@ package com.itmill.toolkit.terminal.web; -import java.io.BufferedWriter; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; |