Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

Theme.java 29KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Intarfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license/license.txt. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see license/licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.terminal.web;
  19. import java.io.FileInputStream;
  20. import java.io.FileNotFoundException;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.util.Collection;
  24. import java.util.LinkedHashMap;
  25. import java.util.Iterator;
  26. import java.util.LinkedList;
  27. import java.util.List;
  28. import java.util.Stack;
  29. import javax.xml.parsers.ParserConfigurationException;
  30. import javax.xml.parsers.SAXParserFactory;
  31. import org.xml.sax.helpers.DefaultHandler;
  32. import org.xml.sax.SAXException;
  33. import org.xml.sax.InputSource;
  34. import org.xml.sax.XMLReader;
  35. import org.xml.sax.Attributes;
  36. /**
  37. * This class provides an interface to the meta-information regarding a
  38. * particular webadapter theme. This entails for instanace the inheritance tree
  39. * of the various xsl-template files, the different requirments that the theme
  40. * imposes on the client browser, etc.
  41. * <p>
  42. * The WebAdapter uses themes to convert the UIDL description into client
  43. * representation, typically HTML or XHTML. A theme consists of set of XSL
  44. * template files which are used to perform XSL transform.
  45. * </p>
  46. * <p>
  47. * XSL files are divided into sets, which can have requirements. A file set is
  48. * included in transformation only if the given requirements are met. Following
  49. * requirements are supported:
  50. * <ul>
  51. * <li>User-Agent HTTP header substring matching</li>
  52. * <li>Markup language version</li>
  53. * <li>JavaScript version</li>
  54. * </ul>
  55. * Additionally following boolean operators may be applied to above
  56. * requirements:
  57. * <ul>
  58. * <li>NOT</li>
  59. * <li>AND</li>
  60. * <li>OR</li>
  61. * </ul>
  62. * The requirements are introduced in XML description file. See example below.
  63. * </p>
  64. * <p>
  65. * The theme description is XML data, and it can be loaded from file or stream.
  66. * The default filename is specified by <code>Theme.DESCRIPTIONFILE</code>.
  67. * Example of theme description file:
  68. *
  69. * <pre>
  70. * &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  71. *
  72. * &lt;theme name=&quot;normal&quot;&gt;
  73. *
  74. * &lt;extends theme=&quot;simple&quot;/&gt;
  75. *
  76. * &lt;description&gt;The normal theme for all browsers&lt;/description&gt;
  77. * &lt;author name=&quot;IT Mill Ltd&quot; email=&quot;millstone@itmill.com&quot; /&gt;
  78. *
  79. * &lt;fileset&gt;
  80. * &lt;require&gt;
  81. * &lt;supports javascript=&quot;JavaScript 1.0&quot;/&gt;
  82. * &lt;/require&gt;
  83. *
  84. * &lt;file name=&quot;common/error.xsl&quot; /&gt;
  85. * &lt;file name=&quot;components/button.xsl&quot; /&gt;
  86. * &lt;file name=&quot;components/select.xsl&quot; /&gt;
  87. * &lt;file name=&quot;components/textfield.xsl&quot; /&gt;
  88. * &lt;file name=&quot;components/table.xsl&quot; /&gt;
  89. * &lt;/fileset&gt;
  90. * &lt;/theme&gt;
  91. * </pre>
  92. *
  93. * </p>
  94. *
  95. * @author IT Mill Ltd.
  96. * @version
  97. * @VERSION@
  98. * @since 3.0
  99. */
  100. public class Theme extends DefaultHandler {
  101. /** Default description file name. */
  102. public static final String DESCRIPTIONFILE = "description.xml";
  103. private static final String TAG_THEME = "theme";
  104. private static final String TAG_EXTENDS = "extends";
  105. private static final String TAG_DESCRIPTION = "description";
  106. private static final String TAG_FILE = "file";
  107. private static final String TAG_FILESET = "fileset";
  108. private static final String TAG_MODE = "mode";
  109. private static final String TAG_MODES = "modes";
  110. private static final String TAG_REQUIRE = "require";
  111. private static final String TAG_SUPPORTS = "supports";
  112. private static final String TAG_AUTHOR = "author";
  113. private static final String TAG_AND = "and";
  114. private static final String TAG_OR = "or";
  115. private static final String TAG_NOT = "not";
  116. private static final String ATTR_NAME = "name";
  117. private static final String ATTR_THEME = "theme";
  118. private static final String ATTR_EMAIL = "email";
  119. private static final String ATTR_MODE = "mode";
  120. private static final String ATTR_JAVASCRIPT = "javascript";
  121. private static final String ATTR_AGENT = "agent";
  122. private static final String ATTR_MARKUP = "markup";
  123. private static final String UNNAMED_FILESET = "unnamed";
  124. public static final String MODE_UIDL = "uidl";
  125. public static final String MODE_XSLT = "xslt";
  126. public static final String MODE_FALLBACK = MODE_XSLT;
  127. /** Name of the theme. */
  128. private String name;
  129. /** Theme description. */
  130. private String description;
  131. /** Author of the theme. */
  132. private Author author;
  133. /** Name of the theme, which this theme extends */
  134. private String parentTheme = null;
  135. /** Fileset of included XSL files. */
  136. private Fileset files = null;
  137. /** Stack of fileset used while parsing XML. */
  138. private Stack openFilesets = new Stack();
  139. /** Stack of string buffers used while parsing XML. */
  140. private Stack openStrings = new Stack();
  141. /** Supported modes name-to-requirements */
  142. private LinkedHashMap supportedModes = new LinkedHashMap();
  143. /** Currently open mode */
  144. private String currentlyOpenMode = null;
  145. /** Are we processing modes */
  146. private boolean modesListCurrentlyOpen = false;
  147. /** Is a NOT requirement element open. */
  148. private boolean isNOTRequirementOpen = false;
  149. /** Currently open requirements while parsing. */
  150. private Stack openRequirements = new Stack();
  151. /**
  152. * Creates a new instance using XML description file. Instantiate new theme,
  153. * by loading the description from given File.
  154. *
  155. * @param descriptionFile
  156. * Description file
  157. * @throws FileNotFoundException
  158. * Thrown if the given file is not found.
  159. */
  160. public Theme(java.io.File descriptionFile) throws FileNotFoundException {
  161. parse(new InputSource(new FileInputStream(descriptionFile)));
  162. }
  163. /**
  164. * Creates a new instance using XML description stream. Instantiate new
  165. * theme, by loading the description from given InputSource.
  166. *
  167. * @param descriptionStream
  168. * XML input to parse
  169. */
  170. public Theme(InputStream descriptionStream) {
  171. try {
  172. parse(new InputSource(descriptionStream));
  173. } finally {
  174. try {
  175. descriptionStream.close();
  176. } catch (IOException ignored) {
  177. }
  178. }
  179. }
  180. /**
  181. * Get the preferred operating mode supported by this theme for given
  182. * terminal.
  183. */
  184. public String getPreferredMode(WebBrowser terminal, ThemeSource themeSource) {
  185. // If no supported modes are declared, then we use parents preferred
  186. // mode
  187. if (parentTheme != null
  188. && supportedModes.keySet().isEmpty()) {
  189. Theme parent = themeSource.getThemeByName(parentTheme);
  190. if (parent == null)
  191. throw new IllegalStateException("Parent theme '"+parentTheme+"' is not found for theme '"+getName()+"'.");
  192. return parent.getPreferredMode(terminal, themeSource);
  193. }
  194. // Iterate and test the modes in order
  195. for (Iterator i = supportedModes.keySet().iterator(); i.hasNext();) {
  196. String mode = (String) i.next();
  197. if (supportsMode(mode, terminal,themeSource))
  198. return mode;
  199. }
  200. return null;
  201. }
  202. /** Tests if this theme suppors given mode */
  203. public boolean supportsMode(String mode, WebBrowser terminal, ThemeSource themeSource) {
  204. // Theme must explicitly support the given mode
  205. RequirementCollection rc = (RequirementCollection) supportedModes
  206. .get(mode);
  207. if (rc == null || !rc.isMet(terminal))
  208. return false;
  209. // All parents must also support the mode
  210. if (parentTheme != null) {
  211. Theme parent = themeSource.getThemeByName(parentTheme);
  212. if (parent == null)
  213. throw new IllegalStateException("Parent theme '"+parentTheme+"' is not found for theme '"+getName()+"'.");
  214. if(!parent.supportsMode(mode, terminal, themeSource))
  215. return false;
  216. }
  217. return true;
  218. }
  219. /**
  220. * Parse XML data.
  221. *
  222. * @param descriptionSource
  223. * XML input source to parse
  224. */
  225. private synchronized void parse(InputSource descriptionSource) {
  226. // Clean-up parse time data
  227. this.openStrings.clear();
  228. this.openFilesets.clear();
  229. this.openRequirements.clear();
  230. this.files = null;
  231. // Parse the Document
  232. try {
  233. XMLReader xr = SAXParserFactory.newInstance().newSAXParser()
  234. .getXMLReader();
  235. xr.setContentHandler(this);
  236. xr.setErrorHandler(this);
  237. xr.parse(descriptionSource);
  238. return;
  239. } catch (ParserConfigurationException e) {
  240. e.printStackTrace();
  241. } catch (SAXException e) {
  242. e.getException().printStackTrace();
  243. } catch (IOException e) {
  244. e.printStackTrace();
  245. } finally {
  246. }
  247. }
  248. /**
  249. * Parse start tag in XML stream.
  250. *
  251. * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
  252. * java.lang.String, java.lang.String, org.xml.sax.Attributes)
  253. */
  254. public void startElement(String uri, String local, String qName,
  255. Attributes atts) {
  256. if (TAG_THEME.equals(qName)) {
  257. this.name = atts.getValue(ATTR_NAME);
  258. } else if (TAG_DESCRIPTION.equals(qName)) {
  259. this.description = "(none)";
  260. this.openStrings.push(new StringBuffer());
  261. } else if (TAG_EXTENDS.equals(qName)) {
  262. String themeName = atts.getValue(ATTR_THEME);
  263. if (this.name.equals(themeName))
  264. throw new IllegalArgumentException("Theme " + this.name
  265. + " extends itself.");
  266. if (parentTheme != null)
  267. throw new IllegalArgumentException("Only one extends statement is allowed");
  268. this.parentTheme = themeName;
  269. } else if (TAG_FILE.equals(qName)) {
  270. File f = new File(atts.getValue(ATTR_NAME));
  271. if (this.openFilesets.isEmpty()) {
  272. throw new IllegalStateException("Element '" + TAG_FILE
  273. + "' must be within '" + TAG_FILESET + "' element.");
  274. }
  275. Fileset fs = (Fileset) this.openFilesets.peek();
  276. fs.addFile(f);
  277. } else if (TAG_FILESET.equals(qName)) {
  278. Fileset fs;
  279. String mode = atts.getValue(ATTR_MODE);
  280. if (mode != null && mode.length() == 0)
  281. mode = null;
  282. if (mode != null && !mode.equals(MODE_UIDL)
  283. && !mode.equals(MODE_XSLT))
  284. throw new IllegalStateException("Given mode '" + mode
  285. + "' is not supported. (This version only supports '"
  286. + MODE_XSLT + "' and '" + MODE_UIDL + "')");
  287. fs = new Fileset(mode);
  288. // Use the first fileset as root fileset
  289. if (this.files == null) {
  290. this.files = fs;
  291. }
  292. // Add inner filesets to parent
  293. if (!this.openFilesets.isEmpty()) {
  294. ((Fileset) this.openFilesets.peek()).addFile(fs);
  295. }
  296. this.openFilesets.push(fs);
  297. } else if (TAG_AUTHOR.equals(qName)) {
  298. this.author = new Author(atts.getValue(ATTR_NAME), atts
  299. .getValue(ATTR_EMAIL));
  300. } else if (TAG_MODES.equals(qName)) {
  301. if (modesListCurrentlyOpen)
  302. throw new IllegalStateException(
  303. "Modes element can not be inside another modes element");
  304. modesListCurrentlyOpen = true;
  305. } else if (TAG_MODE.equals(qName)) {
  306. if (!modesListCurrentlyOpen)
  307. throw new IllegalStateException(
  308. "Mode elements must be placed inside modes element");
  309. if (currentlyOpenMode != null)
  310. throw new IllegalStateException(
  311. "No mode is allowed inside mode");
  312. String name = atts.getValue(ATTR_NAME);
  313. if (name == null || name.length() == 0)
  314. throw new IllegalStateException(
  315. "Name is required for mode elements");
  316. this.currentlyOpenMode = name;
  317. RequirementCollection rc = new AndRequirement();
  318. supportedModes.put(name, rc);
  319. }
  320. // Requirements
  321. else if (TAG_REQUIRE.equals(qName)) {
  322. if (currentlyOpenMode != null) {
  323. RequirementCollection rc = (RequirementCollection) supportedModes
  324. .get(this.currentlyOpenMode);
  325. if (rc == null)
  326. throw new IllegalStateException("Tried to add requirements to mode '" + name + "', but requirements set was not properly created. (internal error)");
  327. this.openRequirements.push(rc);
  328. } else {
  329. if (this.openFilesets.isEmpty()) {
  330. throw new IllegalStateException("Element '" + TAG_REQUIRE
  331. + "' must be within '" + TAG_FILESET + "' element.");
  332. }
  333. Fileset fs = (Fileset) this.openFilesets.peek();
  334. this.openRequirements.push(fs.getRequirements());
  335. }
  336. } else if (TAG_SUPPORTS.equals(qName)) {
  337. if (this.openFilesets.isEmpty() && currentlyOpenMode == null) {
  338. throw new IllegalStateException("Element '" + TAG_REQUIRE
  339. + "' must be within '" + TAG_FILESET + "' element.");
  340. }
  341. if (this.openRequirements.isEmpty()) {
  342. throw new IllegalStateException("Element '" + TAG_SUPPORTS
  343. + "' must be within '" + TAG_REQUIRE + "' element.");
  344. }
  345. this.addRequirements(atts,
  346. (RequirementCollection) this.openRequirements.peek(),
  347. this.isNOTRequirementOpen);
  348. } else if (TAG_NOT.equals(qName)) {
  349. this.isNOTRequirementOpen = true;
  350. } else if (TAG_AND.equals(qName)) {
  351. this.openRequirements.push(new AndRequirement());
  352. } else if (TAG_OR.equals(qName)) {
  353. this.openRequirements.push(new OrRequirement());
  354. }
  355. }
  356. /**
  357. * Parse end tag in XML stream.
  358. *
  359. * @see org.xml.sax.ContentHandler#endElement(String, String, String)
  360. */
  361. public void endElement(String namespaceURI, String localName, String qName)
  362. throws SAXException {
  363. if (TAG_FILESET.equals(qName)) {
  364. this.openFilesets.pop();
  365. } else if (TAG_DESCRIPTION.equals(qName)) {
  366. this.description = ((StringBuffer) this.openStrings.pop())
  367. .toString();
  368. } else if (TAG_REQUIRE.equals(qName)) {
  369. this.openRequirements.pop();
  370. } else if (TAG_NOT.equals(qName)) {
  371. this.isNOTRequirementOpen = false;
  372. } else if (TAG_MODES.equals(qName)) {
  373. this.modesListCurrentlyOpen = false;
  374. } else if (TAG_MODE.equals(qName)) {
  375. this.currentlyOpenMode = null;
  376. } else if (TAG_OR.equals(qName) || TAG_AND.equals(qName)) {
  377. RequirementCollection r = (RequirementCollection) openRequirements.pop();
  378. if (openRequirements.size() < 1)
  379. throw new IllegalStateException();
  380. ((RequirementCollection)openRequirements.peek()).addRequirement(r);
  381. }
  382. }
  383. /**
  384. * Parse character data in XML stream.
  385. *
  386. * @see org.xml.sax.ContentHandler#characters(char[], int, int)
  387. */
  388. public void characters(char[] data, int start, int length) {
  389. // if stack is not ready, data is not content of recognized element
  390. if (!this.openStrings.isEmpty()) {
  391. ((StringBuffer) this.openStrings.peek())
  392. .append(data, start, length);
  393. } else {
  394. // read data which is not part of recognized element
  395. }
  396. }
  397. /**
  398. * Add all requirements specified in attributes to fileset.
  399. *
  400. * @param atts
  401. * Attribute set
  402. * @param requirements
  403. * Collection where to add requirement rules.
  404. * @param applyNot
  405. * Should the meaning of these requirement be negated.
  406. */
  407. private void addRequirements(Attributes atts,
  408. RequirementCollection requirements, boolean applyNot) {
  409. // Create temporary collection for requirements
  410. Collection tmpReqs = new LinkedList();
  411. Requirement req = null;
  412. for (int i = 0; i < atts.getLength(); i++) {
  413. req = null;
  414. if (ATTR_JAVASCRIPT.equals(atts.getQName(i))) {
  415. req = new JavaScriptRequirement(WebBrowser
  416. .parseJavaScriptVersion(atts.getValue(i)));
  417. } else if (ATTR_AGENT.equals(atts.getQName(i))) {
  418. req = new AgentRequirement(atts.getValue(i));
  419. } else if (ATTR_MARKUP.equals(atts.getQName(i))) {
  420. req = new MarkupLanguageRequirement(WebBrowser
  421. .parseHTMLVersion(atts.getValue(i)));
  422. }
  423. // Add to temporary requirement collection and clear reference
  424. if (req != null)
  425. tmpReqs.add(req);
  426. }
  427. // Create implicit AND requirement if more than one
  428. // Rrequirements were specified in attributes
  429. if (tmpReqs.size() > 1) {
  430. req = new AndRequirement(tmpReqs);
  431. }
  432. // Apply NOT rule if requested
  433. if (applyNot) {
  434. req = new NotRequirement(req);
  435. }
  436. // Add to requirements
  437. requirements.addRequirement(req);
  438. }
  439. /**
  440. * Get list of all files in this theme.
  441. *
  442. * @return List of filenames belonging to this theme.
  443. */
  444. public List getFileNames() {
  445. if (files == null)
  446. return new LinkedList();
  447. return files.getFileNames();
  448. }
  449. /**
  450. * Get list of file names matching WebBrowserType.
  451. *
  452. * @return list of filenames in this theme supporting the given terminal.
  453. */
  454. public List getFileNames(WebBrowser terminal, String mode) {
  455. if (files == null)
  456. return new LinkedList();
  457. return this.files.getFileNames(terminal, mode);
  458. }
  459. /**
  460. * String representation of Theme object. Used for debugging purposes only.
  461. *
  462. * @see java.lang.Object#toString()
  463. */
  464. public String toString() {
  465. return this.name + " author='" + this.author + "'" + (parentTheme != null ? " inherits='"
  466. + parentTheme + "'" : "" )+ " files={"
  467. + (files != null ? files.toString() : "null") + "}";
  468. }
  469. /**
  470. * Author information class. This class represents an single author of a
  471. * theme package. Authors have name and contact email address properties.
  472. *
  473. * @author IT Mill Ltd.
  474. * @version
  475. * @VERSION@
  476. * @since 3.0
  477. */
  478. public class Author {
  479. private String name;
  480. private String email;
  481. public Author(String name, String email) {
  482. this.name = name;
  483. this.email = email;
  484. }
  485. /**
  486. * Get the name of the author.
  487. *
  488. * @return Name of the author.
  489. */
  490. public String getName() {
  491. return this.name;
  492. }
  493. /**
  494. * Get the email address of the author.
  495. *
  496. * @return Email address of the author.
  497. */
  498. public String getEmail() {
  499. return this.email;
  500. }
  501. /**
  502. * @see java.lang.Object#toString()
  503. */
  504. public String toString() {
  505. return name + "(" + email + ")";
  506. }
  507. }
  508. /**
  509. * Generic requirement. Interface implemented by reuirements introducing
  510. * method for checking compability with given terminal.
  511. *
  512. * @author IT Mill Ltd.
  513. * @version
  514. * @VERSION@
  515. * @since 3.0
  516. */
  517. public interface Requirement {
  518. /**
  519. * Check that this requirement is met by given type of browser.
  520. *
  521. * @param terminal
  522. * type of the web browser.
  523. * @return True if terminal is compatible with this rule. False
  524. * otherwise.
  525. */
  526. public boolean isMet(WebBrowser terminal);
  527. }
  528. /**
  529. * Generic requirement collection interface. Requirement collection
  530. * introducing methods for combining requirements into single requirement.
  531. *
  532. * @author IT Mill Ltd.
  533. * @version
  534. * @VERSION@
  535. * @since 3.0
  536. */
  537. public interface RequirementCollection extends Requirement {
  538. /**
  539. * Add new requirement to this collection.
  540. *
  541. * @param requirement
  542. * Requirement to be added.
  543. */
  544. public void addRequirement(Requirement requirement);
  545. /**
  546. * Remove a requirement from this collection.
  547. *
  548. * @param requirement
  549. * Requirement to be removed.
  550. */
  551. public void removeRequirement(Requirement requirement);
  552. }
  553. /**
  554. * Logical NOT requirement. Requirement implementing logical NOT operation.
  555. * Wraps an another requirement and negates the meaning of it.
  556. *
  557. * @author IT Mill Ltd.
  558. * @version
  559. * @VERSION@
  560. * @since 3.0
  561. */
  562. public class NotRequirement implements Requirement {
  563. private Requirement requirement;
  564. /**
  565. * Create new NOT requirement based on another requirement.
  566. *
  567. * @param requirement
  568. * The requirement to ne negated.
  569. */
  570. public NotRequirement(Requirement requirement) {
  571. this.requirement = requirement;
  572. }
  573. /**
  574. * Check that this requirement is met by given type of browser.
  575. *
  576. * @param terminal
  577. * type of the web browser.
  578. * @return True if terminal is compatible with this rule. False
  579. * otherwise.
  580. */
  581. public boolean isMet(WebBrowser terminal) {
  582. return !this.requirement.isMet(terminal);
  583. }
  584. /**
  585. * @see java.lang.Object#toString()
  586. */
  587. public String toString() {
  588. return "not(" + requirement + ")";
  589. }
  590. }
  591. /**
  592. * Logical AND requirement. Implements a collection of requirements
  593. * combining the included requirements using logical AND operation.
  594. *
  595. * @author IT Mill Ltd.
  596. * @version
  597. * @VERSION@
  598. * @since 3.0
  599. */
  600. public class AndRequirement implements RequirementCollection {
  601. private Collection requirements = new LinkedList();
  602. public AndRequirement() {
  603. }
  604. public AndRequirement(Collection requirements) {
  605. this.requirements.addAll(requirements);
  606. }
  607. public AndRequirement(Requirement req1, Requirement req2) {
  608. this.addRequirement(req1);
  609. this.addRequirement(req2);
  610. }
  611. public void addRequirement(Requirement requirement) {
  612. this.requirements.add(requirement);
  613. }
  614. public void removeRequirement(Requirement requirement) {
  615. this.requirements.remove(requirement);
  616. }
  617. /**
  618. * Checks that all os the requirements in this collection are met.
  619. *
  620. * @see Theme.Requirement#isMet(WebBrowser)
  621. */
  622. public boolean isMet(WebBrowser terminal) {
  623. for (Iterator i = this.requirements.iterator(); i.hasNext();) {
  624. if (!((Requirement) i.next()).isMet(terminal))
  625. return false;
  626. }
  627. return true;
  628. }
  629. public String toString() {
  630. String str = "";
  631. for (Iterator i = this.requirements.iterator(); i.hasNext();) {
  632. if (!"".equals(str))
  633. str += " AND ";
  634. str += "(" + ((Requirement) i.next()).toString() + ")";
  635. }
  636. return str;
  637. }
  638. }
  639. /**
  640. * Logical OR requirement. Implements a collection of requirements combining
  641. * the included requirements using logical AND operation.
  642. *
  643. * @author IT Mill Ltd.
  644. * @version
  645. * @VERSION@
  646. * @since 3.0
  647. */
  648. public class OrRequirement implements RequirementCollection {
  649. private Collection requirements = new LinkedList();
  650. public OrRequirement() {
  651. }
  652. public OrRequirement(Collection requirements) {
  653. this.requirements.addAll(requirements);
  654. }
  655. public OrRequirement(Requirement req1, Requirement req2) {
  656. this.addRequirement(req1);
  657. this.addRequirement(req2);
  658. }
  659. public void addRequirement(Requirement requirement) {
  660. this.requirements.add(requirement);
  661. }
  662. public void removeRequirement(Requirement requirement) {
  663. this.requirements.remove(requirement);
  664. }
  665. /**
  666. * Checks that some of the requirements in this collection is met.
  667. *
  668. * @see Theme.Requirement#isMet(WebBrowser)
  669. */
  670. public boolean isMet(WebBrowser terminal) {
  671. for (Iterator i = this.requirements.iterator(); i.hasNext();) {
  672. if (!((Requirement) i.next()).isMet(terminal))
  673. return true;
  674. }
  675. return false;
  676. }
  677. public String toString() {
  678. String str = "";
  679. for (Iterator i = this.requirements.iterator(); i.hasNext();) {
  680. if (!"".equals(str))
  681. str += " OR ";
  682. str += "(" + ((Requirement) i.next()).toString() + ")";
  683. }
  684. return str;
  685. }
  686. }
  687. /**
  688. * HTTP user agent requirement This requirements is used to ensure that the
  689. * User-Agent string provided in HTTP request headers contains given
  690. * substring.
  691. *
  692. * @author IT Mill Ltd.
  693. * @version
  694. * @VERSION@
  695. * @since 3.0
  696. */
  697. public class AgentRequirement implements Requirement {
  698. private String agentSubstring;
  699. public AgentRequirement(String agentSubString) {
  700. this.agentSubstring = agentSubString;
  701. }
  702. public boolean isMet(WebBrowser terminal) {
  703. if (terminal.getBrowserApplication().indexOf(this.agentSubstring) > 0)
  704. return true;
  705. Log.info("Requirement: '" + this.agentSubstring
  706. + "' is not met by " + terminal.getBrowserApplication());
  707. return false;
  708. }
  709. /**
  710. * @see java.lang.Object#toString()
  711. */
  712. public String toString() {
  713. return this.agentSubstring;
  714. }
  715. }
  716. /**
  717. * Javascript version requirement This requirement is used to ensure a
  718. * certain level of JavaScript version support.
  719. *
  720. * @author IT Mill Ltd.
  721. * @version
  722. * @VERSION@
  723. * @since 3.0
  724. */
  725. public class JavaScriptRequirement implements Requirement {
  726. private WebBrowser.JavaScriptVersion requiredVersion;
  727. public JavaScriptRequirement(
  728. WebBrowser.JavaScriptVersion requiredVersion) {
  729. this.requiredVersion = requiredVersion;
  730. }
  731. public boolean isMet(WebBrowser terminal) {
  732. if (terminal.getJavaScriptVersion().supports(this.requiredVersion))
  733. return true;
  734. Log.info("Requirement: " + this.requiredVersion + " is not met by "
  735. + terminal.getJavaScriptVersion());
  736. return false;
  737. }
  738. /**
  739. * @see java.lang.Object#toString()
  740. */
  741. public String toString() {
  742. return this.requiredVersion.toString();
  743. }
  744. }
  745. /**
  746. * Markup language version requirement This requirement is used to ensure a
  747. * certain level of Markup language version support.
  748. *
  749. * @author IT Mill Ltd.
  750. * @version
  751. * @VERSION@
  752. * @since 3.0
  753. */
  754. public class MarkupLanguageRequirement implements Requirement {
  755. private WebBrowser.MarkupVersion requiredVersion;
  756. public MarkupLanguageRequirement(
  757. WebBrowser.MarkupVersion requiredVersion) {
  758. this.requiredVersion = requiredVersion;
  759. }
  760. public boolean isMet(WebBrowser terminal) {
  761. if (terminal.getMarkupVersion().supports(this.requiredVersion))
  762. return true;
  763. Log.info("Requirement: " + this.requiredVersion + " is not met by "
  764. + terminal.getMarkupVersion());
  765. return false;
  766. }
  767. /**
  768. * @see java.lang.Object#toString()
  769. */
  770. public String toString() {
  771. return this.requiredVersion.toString();
  772. }
  773. }
  774. /**
  775. * Theme XSL file description Description of a single XSL file included a
  776. * theme.
  777. *
  778. * @author IT Mill Ltd.
  779. * @version
  780. * @VERSION@
  781. * @since 3.0
  782. */
  783. public class File {
  784. private String name;
  785. /**
  786. * Create new file.
  787. *
  788. * @param name
  789. * Name of the file.
  790. */
  791. public File(String name) {
  792. this.name = name;
  793. }
  794. /**
  795. * Get name of the file. The file name is relative and unique within a
  796. * theme.
  797. *
  798. * @return Name of the file.
  799. */
  800. public String getName() {
  801. return this.name;
  802. }
  803. /**
  804. * Does this file support the given terminal. Single file requirements
  805. * are not supported and therefore this always returns true.
  806. *
  807. * @see Theme.Fileset
  808. * @return Always returns true.
  809. */
  810. public boolean supports(WebBrowser terminal) {
  811. return true;
  812. }
  813. /**
  814. * @see java.lang.Object#toString()
  815. */
  816. public String toString() {
  817. return this.getName();
  818. }
  819. }
  820. /**
  821. * A recursive set of files sharing the same requirements.
  822. *
  823. * @author IT Mill Ltd.
  824. * @version
  825. * @VERSION@
  826. * @since 3.0
  827. */
  828. public class Fileset extends File {
  829. private RequirementCollection requirements = new AndRequirement();
  830. private Collection files = new LinkedList();
  831. private String mode;
  832. /**
  833. * Create new empty fileset.
  834. *
  835. * @param name
  836. * Name of the fileset.
  837. */
  838. public Fileset(String mode) {
  839. super(null);
  840. this.mode = mode;
  841. }
  842. /** Add a file into fileset. */
  843. private void addFile(File file) {
  844. this.files.add(file);
  845. }
  846. /** Get requirements in this fileset. */
  847. private RequirementCollection getRequirements() {
  848. return this.requirements;
  849. }
  850. /**
  851. * Get list of all files in this theme.
  852. *
  853. * @return list of filenames.
  854. */
  855. public List getFileNames() {
  856. List list = new LinkedList();
  857. for (Iterator i = this.files.iterator(); i.hasNext();) {
  858. File f = (File) i.next();
  859. // Recursively add included filesets
  860. if (f instanceof Fileset) {
  861. list.addAll(((Fileset) f).getFileNames());
  862. } else {
  863. list.add(f.getName());
  864. }
  865. }
  866. return list;
  867. }
  868. /**
  869. * Get list of file names matching WebBrowserType.
  870. *
  871. * @return list of filenames supporting the given terminal.
  872. */
  873. public List getFileNames(WebBrowser terminal, String mode) {
  874. List list = new LinkedList();
  875. // If this set is not supported by the terminal or is explicitly set
  876. // into
  877. // another mode, no files are given
  878. if (!this.supports(terminal)
  879. || (this.mode != null && !this.mode.equals(mode)))
  880. return list;
  881. for (Iterator i = this.files.iterator(); i.hasNext();) {
  882. File f = (File) i.next();
  883. // Recursively add included filesets if they are
  884. // supported
  885. if (f instanceof Fileset) {
  886. list.addAll(((Fileset) f).getFileNames(terminal, mode));
  887. } else {
  888. list.add(f.getName());
  889. }
  890. }
  891. return list;
  892. }
  893. /**
  894. * Does this file support the given terminal.
  895. *
  896. * @return True if fileset supports the given browser. False otherwise.
  897. */
  898. public boolean supports(WebBrowser terminal) {
  899. if (requirements.isMet(terminal))
  900. return true;
  901. Log.info("Skipped fileset " + Theme.this.getName() + "/"
  902. + this.getName()
  903. + " because all requirements were not met.");
  904. return false;
  905. }
  906. /**
  907. * @see java.lang.Object#toString()
  908. */
  909. public String toString() {
  910. return "name=[" + this.getName() + "] requires=["
  911. + this.requirements + "] files=[" + files + "]";
  912. }
  913. }
  914. /**
  915. * Returns the author of this theme.
  916. *
  917. * @return Author of the theme.
  918. */
  919. public Author getAuthor() {
  920. return author;
  921. }
  922. /**
  923. * Returns the name of this theme.
  924. *
  925. * @return Name of the theme.
  926. */
  927. public String getName() {
  928. return name;
  929. }
  930. /**
  931. * Returns the name of the parent theme.
  932. *
  933. */
  934. public String getParent() {
  935. return parentTheme;
  936. }
  937. /** Get theme description */
  938. public String getDescription() {
  939. return description;
  940. }
  941. }