001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.webadapter;
039:
040: import java.io.FileInputStream;
041: import java.io.FileNotFoundException;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.util.Collection;
045: import java.util.Iterator;
046: import java.util.LinkedList;
047: import java.util.List;
048: import java.util.Stack;
049: import javax.xml.parsers.ParserConfigurationException;
050: import javax.xml.parsers.SAXParserFactory;
051: import org.xml.sax.helpers.DefaultHandler;
052: import org.xml.sax.SAXException;
053: import org.xml.sax.InputSource;
054: import org.xml.sax.XMLReader;
055: import org.xml.sax.Attributes;
056:
057: /** This class provides an interface to the meta-information
058: * regarding a particular webadapter theme. This entails for
059: * instanace the inheritance tree of the various xsl-template files,
060: * the different requirments that the theme imposes on the client browser,
061: * etc.
062: * <p>
063: * The WebAdapter uses themes to convert the UIDL description into
064: * client representation, typically HTML or XHTML.
065: * A theme consists of set of XSL template files which are used to
066: * perform XSL transform.
067: * </p>
068: * <p>
069: * XSL files are divided into sets, which can have requirements.
070: * A file set is included in transformation only if the given requirements
071: * are met. Following requirements are supported:
072: * <ul>
073: * <li>User-Agent HTTP header substring matching</li>
074: * <li>Markup language version</li>
075: * <li>JavaScript version</li>
076: * </ul>
077: * Additionally following boolean operators may be applied to above
078: * requirements:
079: * <ul>
080: * <li>NOT</li>
081: * <li>AND</li>
082: * <li>OR</li>
083: * </ul>
084: * The requirements are introduced in XML description file. See example below.
085: * </p>
086: * <p>
087: * The theme description is XML data, and it can be loaded from file or stream.
088: * The default filename is specified by <code>Theme.DESCRIPTIONFILE</code>.
089: * Example of theme description file:
090: * <pre>
091: * <?xml version="1.0" encoding="UTF-8"?>
092: *
093: * <theme name="normal">
094: *
095: * <extends theme="simple"/>
096: *
097: * <description>The normal theme for all browsers</description>
098: * <author name="IT Mill Ltd" email="millstone@itmill.com" />
099: *
100: * <fileset>
101: * <require>
102: * <supports javascript="JavaScript 1.0"/>
103: * </require>
104: *
105: * <file name="common/error.xsl" />
106: * <file name="components/button.xsl" />
107: * <file name="components/select.xsl" />
108: * <file name="components/textfield.xsl" />
109: * <file name="components/table.xsl" />
110: * </fileset>
111: * </theme>
112: * </pre>
113: * </p>
114: * @author IT Mill Ltd.
115: * @version 3.1.1
116: * @since 3.0
117: */
118: public class Theme extends DefaultHandler {
119:
120: /** Default description file name. */
121: public static final String DESCRIPTIONFILE = "description.xml";
122:
123: private static final String TAG_THEME = "theme";
124: private static final String TAG_EXTENDS = "extends";
125: private static final String TAG_DESCRIPTION = "description";
126:
127: private static final String TAG_FILE = "file";
128: private static final String TAG_FILESET = "fileset";
129: private static final String TAG_REQUIRE = "require";
130: private static final String TAG_SUPPORTS = "supports";
131: private static final String TAG_AUTHOR = "author";
132:
133: private static final String TAG_AND = "and";
134: private static final String TAG_OR = "or";
135: private static final String TAG_NOT = "not";
136:
137: private static final String ATTR_NAME = "name";
138: private static final String ATTR_THEME = "theme";
139: private static final String ATTR_EMAIL = "email";
140:
141: private static final String ATTR_JAVASCRIPT = "javascript";
142: private static final String ATTR_AGENT = "agent";
143: private static final String ATTR_MARKUP = "markup";
144:
145: private static final String UNNAMED_FILESET = "unnamed";
146:
147: /** Name of the theme. */
148: private String name;
149:
150: /** Description file. */
151: private java.io.File file;
152:
153: /** Version of the theme. */
154: private String version;
155:
156: /** Theme description. */
157: private String description;
158:
159: /** Author of the theme. */
160: private Author author;
161:
162: /** List of parent themes */
163: private List parentThemes = new LinkedList();
164:
165: /** Fileset of included XSL files. */
166: private Fileset files = null;
167:
168: /** Stack of fileset used while parsing XML. */
169: private Stack openFilesets = new Stack();
170:
171: /** Stack of string buffers used while parsing XML. */
172: private Stack openStrings = new Stack();
173:
174: /** Is a NOT requirement element open. */
175: private boolean isNOTRequirementOpen = false;
176:
177: /** Currently open requirements while parsing. */
178: private Stack openRequirements = new Stack();
179:
180: /** Creates a new instance using XML description file.
181: * Instantiate new theme, by loading the description from given File.
182: * @param descriptionFile Description file
183: * @throws FileNotFoundException Thrown if the given file is not found.
184: */
185: public Theme(java.io.File descriptionFile)
186: throws FileNotFoundException {
187: this .file = descriptionFile;
188: parse(new InputSource(new FileInputStream(descriptionFile)));
189: }
190:
191: /** Creates a new instance using XML description stream.
192: * Instantiate new theme, by loading the description from given InputSource.
193: * @param descriptionStream XML input to parse
194: */
195: public Theme(InputStream descriptionStream) {
196: try {
197: parse(new InputSource(descriptionStream));
198: } finally {
199: try {
200: descriptionStream.close();
201: } catch (IOException ignored) {
202: }
203: }
204: }
205:
206: /** Parse XML data.
207: * @param descriptionSource XML input source to parse
208: */
209: private synchronized void parse(InputSource descriptionSource) {
210:
211: // Clean-up parse time data
212: this .openStrings.clear();
213: this .openFilesets.clear();
214: this .openRequirements.clear();
215: this .files = null;
216:
217: // Parse the Document
218: try {
219: XMLReader xr = SAXParserFactory.newInstance()
220: .newSAXParser().getXMLReader();
221:
222: xr.setContentHandler(this );
223: xr.setErrorHandler(this );
224:
225: xr.parse(descriptionSource);
226:
227: return;
228: } catch (ParserConfigurationException e) {
229: e.printStackTrace();
230: } catch (SAXException e) {
231: e.getException().printStackTrace();
232: } catch (IOException e) {
233: e.printStackTrace();
234: } finally {
235: }
236:
237: }
238:
239: /** Parse start tag in XML stream.
240: *
241: * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
242: */
243: public void startElement(String uri, String local, String qName,
244: Attributes atts) {
245:
246: if (TAG_THEME.equals(qName)) {
247: this .name = atts.getValue(ATTR_NAME);
248: } else if (TAG_DESCRIPTION.equals(qName)) {
249: this .description = "(none)";
250: this .openStrings.push(new StringBuffer());
251: } else if (TAG_EXTENDS.equals(qName)) {
252: String themeName = atts.getValue(ATTR_THEME);
253: if (this .name.equals(themeName))
254: throw new IllegalArgumentException("Theme " + this .name
255: + " extends itself.");
256: this .parentThemes.add(themeName);
257: } else if (TAG_FILE.equals(qName)) {
258: File f = new File(atts.getValue(ATTR_NAME));
259: if (this .openFilesets.isEmpty()) {
260: throw new IllegalStateException("Element '" + TAG_FILE
261: + "' must be within '" + TAG_FILESET
262: + "' element.");
263: }
264: Fileset fs = (Fileset) this .openFilesets.peek();
265: fs.addFile(f);
266: } else if (TAG_FILESET.equals(qName)) {
267: Fileset fs;
268: if (atts.getValue(ATTR_NAME) != null) {
269: fs = new Fileset(atts.getValue(ATTR_NAME));
270: } else {
271: fs = new Fileset(atts.getValue(UNNAMED_FILESET));
272: }
273:
274: // Use the first fileset as root fileset
275: if (this .files == null) {
276: this .files = fs;
277: }
278:
279: // Add inner filesets to parent
280: if (!this .openFilesets.isEmpty()) {
281: ((Fileset) this .openFilesets.peek()).addFile(fs);
282: }
283:
284: this .openFilesets.push(fs);
285: } else if (TAG_AUTHOR.equals(qName)) {
286: this .author = new Author(atts.getValue(ATTR_NAME), atts
287: .getValue(ATTR_EMAIL));
288: }
289:
290: // Requirements
291: else if (TAG_REQUIRE.equals(qName)) {
292: if (this .openFilesets.isEmpty()) {
293: throw new IllegalStateException("Element '"
294: + TAG_REQUIRE + "' must be within '"
295: + TAG_FILESET + "' element.");
296: }
297: Fileset fs = (Fileset) this .openFilesets.peek();
298: this .openRequirements.push(fs.getRequirements());
299: } else if (TAG_SUPPORTS.equals(qName)) {
300: if (this .openFilesets.isEmpty()) {
301: throw new IllegalStateException("Element '"
302: + TAG_REQUIRE + "' must be within '"
303: + TAG_FILESET + "' element.");
304: }
305: if (this .openRequirements.isEmpty()) {
306: throw new IllegalStateException("Element '"
307: + TAG_SUPPORTS + "' must be within '"
308: + TAG_REQUIRE + "' element.");
309: }
310: this .addRequirements(atts,
311: (RequirementCollection) this .openRequirements
312: .peek(), this .isNOTRequirementOpen);
313: } else if (TAG_NOT.equals(qName)) {
314: this .isNOTRequirementOpen = true;
315: } else if (TAG_AND.equals(qName)) {
316: this .openRequirements.push(new AndRequirement());
317: } else if (TAG_OR.equals(qName)) {
318: this .openRequirements.push(new OrRequirement());
319: }
320: }
321:
322: /** Parse end tag in XML stream.
323: * @see org.xml.sax.ContentHandler#endElement(String, String, String)
324: */
325: public void endElement(String namespaceURI, String localName,
326: String qName) throws SAXException {
327:
328: if (TAG_FILESET.equals(qName)) {
329: this .openFilesets.pop();
330: } else if (TAG_DESCRIPTION.equals(qName)) {
331: this .description = ((StringBuffer) this .openStrings.pop())
332: .toString();
333: } else if (TAG_REQUIRE.equals(qName)) {
334: this .openRequirements.pop();
335:
336: } else if (TAG_NOT.equals(qName)) {
337: this .isNOTRequirementOpen = false;
338: }
339: }
340:
341: /** Parse character data in XML stream.
342: * @see org.xml.sax.ContentHandler#characters(char[], int, int)
343: */
344: public void characters(char[] data, int start, int length) {
345:
346: // if stack is not ready, data is not content of recognized element
347: if (!this .openStrings.isEmpty()) {
348: ((StringBuffer) this .openStrings.peek()).append(data,
349: start, length);
350: } else {
351: // read data which is not part of recognized element
352: }
353: }
354:
355: /** Add all requirements specified in attributes to fileset.
356: * @param atts Attribute set
357: * @param requirements Collection where to add requirement rules.
358: * @param applyNot Should the meaning of these requirement be negated.
359: */
360: private void addRequirements(Attributes atts,
361: RequirementCollection requirements, boolean applyNot) {
362:
363: // Create temporary collection for requirements
364: Collection tmpReqs = new LinkedList();
365: Requirement req = null;
366:
367: for (int i = 0; i < atts.getLength(); i++) {
368: req = null;
369: if (ATTR_JAVASCRIPT.equals(atts.getQName(i))) {
370: req = new JavaScriptRequirement(WebBrowser
371: .parseJavaScriptVersion(atts.getValue(i)));
372: } else if (ATTR_AGENT.equals(atts.getQName(i))) {
373: req = new AgentRequirement(atts.getValue(i));
374: } else if (ATTR_MARKUP.equals(atts.getQName(i))) {
375: req = new MarkupLanguageRequirement(WebBrowser
376: .parseHTMLVersion(atts.getValue(i)));
377: }
378: // Add to temporary requirement collection and clear reference
379: if (req != null)
380: tmpReqs.add(req);
381: }
382:
383: // Create implicit AND requirement if more than one
384: // Rrequirements were specified in attributes
385: if (tmpReqs.size() > 1) {
386: req = new AndRequirement(tmpReqs);
387: }
388:
389: // Apply NOT rule if requested
390: if (applyNot) {
391: req = new NotRequirement(req);
392: }
393:
394: // Add to requirements
395: requirements.addRequirement(req);
396: }
397:
398: /** Get list of all files in this theme.
399: * @return List of filenames belonging to this theme.
400: */
401: public List getFileNames() {
402: if (files == null)
403: return new LinkedList();
404: return files.getFileNames();
405: }
406:
407: /** Get list of file names matching WebBrowserType.
408: * @return list of filenames in this theme supporting the given terminal.
409: */
410: public List getFileNames(WebBrowser terminal) {
411: if (files == null)
412: return new LinkedList();
413: return this .files.getFileNames(terminal);
414: }
415:
416: /** String representation of Theme object.
417: * Used for debugging purposes only.
418: * @see java.lang.Object#toString()
419: */
420: public String toString() {
421: return this .name + " author=[" + this .author + "]"
422: + " inherits=" + parentThemes + "]" + " files={"
423: + (files != null ? files.toString() : "null") + "}";
424: }
425:
426: /** Author information class.
427: * This class represents an single author of a theme package.
428: * Authors have name and contact email address properties.
429: * @author IT Mill Ltd.
430: * @version 3.1.1
431: * @since 3.0
432: */
433: public class Author {
434:
435: private String name;
436: private String email;
437:
438: public Author(String name, String email) {
439: this .name = name;
440: this .email = email;
441: }
442:
443: /** Get the name of the author.
444: * @return Name of the author.
445: */
446: public String getName() {
447: return this .name;
448: }
449:
450: /** Get the email address of the author.
451: * @return Email address of the author.
452: */
453: public String getEmail() {
454: return this .email;
455: }
456:
457: /**
458: * @see java.lang.Object#toString()
459: */
460: public String toString() {
461: return name + "(" + email + ")";
462: }
463: }
464:
465: /** Generic requirement.
466: * Interface implemented by reuirements introducing
467: * method for checking compability with given terminal.
468: * @author IT Mill Ltd.
469: * @version 3.1.1
470: * @since 3.0
471: */
472: public interface Requirement {
473:
474: /** Check that this requirement is met by given type of browser.
475: * @param terminal type of the web browser.
476: * @return True if terminal is compatible with this rule. False otherwise.
477: */
478: public boolean isMet(WebBrowser terminal);
479:
480: }
481:
482: /** Generic requirement collection interface.
483: * Requirement collection introducing methods for
484: * combining requirements into single requirement.
485: * @author IT Mill Ltd.
486: * @version 3.1.1
487: * @since 3.0
488: */
489: public interface RequirementCollection extends Requirement {
490:
491: /** Add new requirement to this collection.
492: * @param requirement Requirement to be added.
493: */
494: public void addRequirement(Requirement requirement);
495:
496: /** Remove a requirement from this collection.
497: * @param requirement Requirement to be removed.
498: */
499: public void removeRequirement(Requirement requirement);
500: }
501:
502: /** Logical NOT requirement.
503: * Requirement implementing logical NOT operation.
504: * Wraps an another requirement and negates the meaning of it.
505: * @author IT Mill Ltd.
506: * @version 3.1.1
507: * @since 3.0
508: */
509: public class NotRequirement implements Requirement {
510: private Requirement requirement;
511:
512: /** Create new NOT requirement based on another requirement.
513: * @param requirement The requirement to ne negated.
514: */
515: public NotRequirement(Requirement requirement) {
516: this .requirement = requirement;
517: }
518:
519: /** Check that this requirement is met by given type of browser.
520: * @param terminal type of the web browser.
521: * @return True if terminal is compatible with this rule. False otherwise.
522: */
523: public boolean isMet(WebBrowser terminal) {
524: return !this .requirement.isMet(terminal);
525: }
526:
527: /**
528: * @see java.lang.Object#toString()
529: */
530: public String toString() {
531: return "not(" + requirement + ")";
532: }
533:
534: }
535:
536: /** Logical AND requirement.
537: * Implements a collection of requirements combining the
538: * included requirements using logical AND operation.
539: * @author IT Mill Ltd.
540: * @version 3.1.1
541: * @since 3.0
542: */
543: public class AndRequirement implements RequirementCollection {
544:
545: private Collection requirements = new LinkedList();
546:
547: public AndRequirement() {
548: }
549:
550: public AndRequirement(Collection requirements) {
551: this .requirements.addAll(requirements);
552: }
553:
554: public AndRequirement(Requirement req1, Requirement req2) {
555: this .addRequirement(req1);
556: this .addRequirement(req2);
557: }
558:
559: public void addRequirement(Requirement requirement) {
560: this .requirements.add(requirement);
561: }
562:
563: public void removeRequirement(Requirement requirement) {
564: this .requirements.remove(requirement);
565: }
566:
567: /** Checks that all os the requirements in this collection are met.
568: * @see Theme.Requirement#isMet(WebBrowser)
569: */
570: public boolean isMet(WebBrowser terminal) {
571: for (Iterator i = this .requirements.iterator(); i.hasNext();) {
572: if (!((Requirement) i.next()).isMet(terminal))
573: return false;
574: }
575: return true;
576: }
577:
578: public String toString() {
579: String str = "";
580: for (Iterator i = this .requirements.iterator(); i.hasNext();) {
581: if (!"".equals(str))
582: str += " AND ";
583: str += "(" + ((Requirement) i.next()).toString() + ")";
584: }
585: return str;
586: }
587:
588: }
589:
590: /** Logical OR requirement.
591: * Implements a collection of requirements combining the
592: * included requirements using logical AND operation.
593: * @author IT Mill Ltd.
594: * @version 3.1.1
595: * @since 3.0
596: */
597: public class OrRequirement implements RequirementCollection {
598:
599: private Collection requirements = new LinkedList();
600:
601: public OrRequirement() {
602: }
603:
604: public OrRequirement(Collection requirements) {
605: this .requirements.addAll(requirements);
606: }
607:
608: public OrRequirement(Requirement req1, Requirement req2) {
609: this .addRequirement(req1);
610: this .addRequirement(req2);
611: }
612:
613: public void addRequirement(Requirement requirement) {
614: this .requirements.add(requirement);
615: }
616:
617: public void removeRequirement(Requirement requirement) {
618: this .requirements.remove(requirement);
619: }
620:
621: /** Checks that some of the requirements in this collection is met.
622: * @see Theme.Requirement#isMet(WebBrowser)
623: */
624: public boolean isMet(WebBrowser terminal) {
625: for (Iterator i = this .requirements.iterator(); i.hasNext();) {
626: if (!((Requirement) i.next()).isMet(terminal))
627: return false;
628: }
629: return true;
630: }
631:
632: public String toString() {
633: String str = "";
634: for (Iterator i = this .requirements.iterator(); i.hasNext();) {
635: if (!"".equals(str))
636: str += " OR ";
637: str += "(" + ((Requirement) i.next()).toString() + ")";
638: }
639: return str;
640: }
641: }
642:
643: /** HTTP user agent requirement
644: * This requirements is used to ensure that the User-Agent string
645: * provided in HTTP request headers contains given substring.
646: * @author IT Mill Ltd.
647: * @version 3.1.1
648: * @since 3.0
649: */
650: public class AgentRequirement implements Requirement {
651:
652: private String agentSubstring;
653:
654: public AgentRequirement(String agentSubString) {
655: this .agentSubstring = agentSubString;
656: }
657:
658: public boolean isMet(WebBrowser terminal) {
659: if (terminal.getBrowserApplication().indexOf(
660: this .agentSubstring) > 0)
661: return true;
662: Log.info("Requirement: '" + this .agentSubstring
663: + "' is not met by "
664: + terminal.getBrowserApplication());
665: return false;
666: }
667:
668: /**
669: * @see java.lang.Object#toString()
670: */
671: public String toString() {
672: return this .agentSubstring;
673: }
674: }
675:
676: /** Javascript version requirement
677: * This requirement is used to ensure a certain level of
678: * JavaScript version support.
679: * @author IT Mill Ltd.
680: * @version 3.1.1
681: * @since 3.0
682: */
683: public class JavaScriptRequirement implements Requirement {
684:
685: private WebBrowser.JavaScriptVersion requiredVersion;
686:
687: public JavaScriptRequirement(
688: WebBrowser.JavaScriptVersion requiredVersion) {
689: this .requiredVersion = requiredVersion;
690: }
691:
692: public boolean isMet(WebBrowser terminal) {
693: if (terminal.getJavaScriptVersion().supports(
694: this .requiredVersion))
695: return true;
696: Log.info("Requirement: " + this .requiredVersion
697: + " is not met by "
698: + terminal.getJavaScriptVersion());
699: return false;
700: }
701:
702: /**
703: * @see java.lang.Object#toString()
704: */
705: public String toString() {
706: return this .requiredVersion.toString();
707: }
708: }
709:
710: /** Markup language version requirement
711: * This requirement is used to ensure a certain level of
712: * Markup language version support.
713: * @author IT Mill Ltd.
714: * @version 3.1.1
715: * @since 3.0
716: */
717: public class MarkupLanguageRequirement implements Requirement {
718:
719: private WebBrowser.MarkupVersion requiredVersion;
720:
721: public MarkupLanguageRequirement(
722: WebBrowser.MarkupVersion requiredVersion) {
723: this .requiredVersion = requiredVersion;
724: }
725:
726: public boolean isMet(WebBrowser terminal) {
727: if (terminal.getMarkupVersion().supports(
728: this .requiredVersion))
729: return true;
730: Log.info("Requirement: " + this .requiredVersion
731: + " is not met by " + terminal.getMarkupVersion());
732: return false;
733:
734: }
735:
736: /**
737: * @see java.lang.Object#toString()
738: */
739: public String toString() {
740: return this .requiredVersion.toString();
741: }
742:
743: }
744:
745: /** Theme XSL file description
746: * Description of a single XSL file included a theme.
747: * @author IT Mill Ltd.
748: * @version 3.1.1
749: * @since 3.0
750: */
751: public class File {
752:
753: private String name;
754:
755: /** Create new file.
756: * @param name Name of the file.
757: */
758: public File(String name) {
759: this .name = name;
760: }
761:
762: /** Get name of the file.
763: * The file name is relative and unique within a theme.
764: * @return Name of the file.
765: */
766: public String getName() {
767: return this .name;
768: }
769:
770: /** Does this file support the given terminal.
771: * Single file requirements are not supported and
772: * therefore this always returns true.
773: * @see Theme.Fileset
774: * @return Always returns true.
775: */
776: public boolean supports(WebBrowser terminal) {
777: return true;
778: }
779:
780: /**
781: * @see java.lang.Object#toString()
782: */
783: public String toString() {
784: return this .getName();
785: }
786:
787: }
788:
789: /** A recursive set of files sharing the same requirements.
790: * @author IT Mill Ltd.
791: * @version 3.1.1
792: * @since 3.0
793: */
794: public class Fileset extends File {
795:
796: private RequirementCollection requirements = new AndRequirement();
797:
798: private Collection files = new LinkedList();
799:
800: /** Create new empty fileset.
801: * @param name Name of the fileset.
802: */
803: public Fileset(String name) {
804: super (name);
805: }
806:
807: /** Create new fileset.
808: * @param name Name of the fileset.
809: * @param files Collection of files to include in the set.
810: */
811: public Fileset(String name, Collection files) {
812: super (name);
813: }
814:
815: /**Add a file into fileset. */
816: private void addFile(File file) {
817: this .files.add(file);
818: }
819:
820: /** Remove a file from fileset. */
821: private void removeFile(File file) {
822: this .files.add(file);
823: }
824:
825: /** Get requirements in this fileset. */
826: private RequirementCollection getRequirements() {
827: return this .requirements;
828: }
829:
830: /** Get list of all files in this theme.
831: * @return list of filenames.
832: */
833: public List getFileNames() {
834:
835: List list = new LinkedList();
836:
837: for (Iterator i = this .files.iterator(); i.hasNext();) {
838: File f = (File) i.next();
839:
840: // Recursively add included filesets
841: if (f instanceof Fileset) {
842: list.addAll(((Fileset) f).getFileNames());
843: } else {
844: list.add(f.getName());
845: }
846: }
847: return list;
848:
849: }
850:
851: /** Get list of file names matching WebBrowserType.
852: * @return list of filenames supporting the given terminal.
853: */
854: public List getFileNames(WebBrowser terminal) {
855:
856: List list = new LinkedList();
857:
858: if (!this .supports(terminal))
859: return list;
860:
861: for (Iterator i = this .files.iterator(); i.hasNext();) {
862: File f = (File) i.next();
863:
864: // Recursively add included filesets if they are
865: // supported
866: if (f instanceof Fileset) {
867: list.addAll(((Fileset) f).getFileNames(terminal));
868:
869: } else {
870: list.add(f.getName());
871: }
872: }
873: return list;
874: }
875:
876: /** Does this file support the given terminal.
877: * @return True if fileset supports the given browser. False otherwise.
878: */
879: public boolean supports(WebBrowser terminal) {
880: if (requirements.isMet(terminal))
881: return true;
882: Log.info("Skipped fileset " + Theme.this .getName() + "/"
883: + this .getName()
884: + " because all requirements were not met.");
885: return false;
886: }
887:
888: /**
889: * @see java.lang.Object#toString()
890: */
891: public String toString() {
892: return "name=[" + this .getName() + "] requires=["
893: + this .requirements + "] files=[" + files + "]";
894: }
895: }
896:
897: /** Returns the author of this theme.
898: * @return Author of the theme.
899: */
900: public Author getAuthor() {
901: return author;
902: }
903:
904: /** Returns the name of this theme.
905: * @return Name of the theme.
906: */
907: public String getName() {
908: return name;
909: }
910:
911: /** Returns the list of parent themes of this theme.
912: * Returns list of all inherited themes in the inheritance order.
913: * @return List of parent theme instances.
914: */
915: public List getParentThemes() {
916: return parentThemes;
917: }
918:
919: /** Returns the version of this theme.
920: * @return Version string
921: */
922: public String getVersion() {
923: return version;
924: }
925:
926: }
|