From 5f8b151629c59312feaf5bed68cfbed3bbd71fac Mon Sep 17 00:00:00 2001
From: emmanue1 <emmanue1@users.noreply.github.com>
Date: Wed, 13 May 2015 19:27:20 +0200
Subject: [PATCH] Adds support for WAR files

---
 .../model/container/GenericContainer.groovy   |   4 +-
 .../gui/model/container/WarContainer.groovy   |  22 +++
 .../GenericContainerFactoryProvider.groovy    |   3 +-
 .../JarContainerFactoryProvider.groovy        |  29 +--
 .../WarContainerFactoryProvider.groovy        |  37 ++++
 .../AbstractFileLoaderProvider.groovy         |   2 +-
 .../fileloader/WarFileLoaderProvider.groovy   |  13 ++
 .../indexer/TextFileIndexerProvider.groovy    |   4 +-
 .../indexer/WebXmlFileIndexerProvider.groovy  |  40 ++++
 .../indexer/ZipFileIndexerProvider.groovy     |   2 +-
 .../PackageSourceSaverProvider.groovy         |   2 +-
 .../ZipFileSourceSaverProvider.groovy         |   4 +-
 .../CssFileTreeNodeFactoryProvider.groovy     |  42 ++++
 .../HtmlFileTreeNodeFactoryProvider.groovy    |   2 +-
 .../JspFileTreeNodeFactoryProvider.groovy     |  42 ++++
 ...ManifestFileTreeNodeFactoryProvider.groovy |   5 +-
 ...infDirectoryTreeNodeFactoryProvider.groovy |  10 +-
 .../TextFileTreeNodeFactoryProvider.groovy    |   2 +-
 .../WarFileTreeNodeFactoryProvider.groovy     |  29 +++
 .../WarPackageTreeNodeFactoryProvider.groovy  |  16 ++
 .../WebXmlFileTreeNodeFactoryProvider.groovy  |  38 ++++
 ...sesDirectoryTreeNodeFactoryProvider.groovy |  18 ++
 ...LibDirectoryTreeNodeFactoryProvider.groovy |  17 ++
 .../XmlFileTreeNodeFactoryProvider.groovy     |   4 +-
 .../gui/view/component/WebXmlFilePage.groovy  | 187 ++++++++++++++++++
 .../gui/util/xml/AbstractXmlPathFinder.java   |  86 ++++++++
 .../services/jd.gui.spi.ContainerFactory      |   2 +
 .../META-INF/services/jd.gui.spi.FileLoader   |   1 +
 .../META-INF/services/jd.gui.spi.Indexer      |   1 +
 .../services/jd.gui.spi.TreeNodeFactory       |   8 +-
 .../resources/images/archivefolder_obj.png    | Bin 0 -> 3017 bytes
 .../src/main/resources/images/css_obj.png     | Bin 0 -> 2972 bytes
 .../src/main/resources/images/inf_obj.png     | Bin 0 -> 2954 bytes
 .../resources/images/packagefolder_obj.png    | Bin 0 -> 421 bytes
 34 files changed, 632 insertions(+), 40 deletions(-)
 create mode 100644 services/src/main/groovy/jd/gui/model/container/WarContainer.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/container/WarContainerFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/fileloader/WarFileLoaderProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/indexer/WebXmlFileIndexerProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/CssFileTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/JspFileTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/WarFileTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/WarPackageTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/WebXmlFileTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/WebinfClassesDirectoryTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/service/treenode/WebinfLibDirectoryTreeNodeFactoryProvider.groovy
 create mode 100644 services/src/main/groovy/jd/gui/view/component/WebXmlFilePage.groovy
 create mode 100644 services/src/main/java/jd/gui/util/xml/AbstractXmlPathFinder.java
 create mode 100644 services/src/main/resources/images/archivefolder_obj.png
 create mode 100644 services/src/main/resources/images/css_obj.png
 create mode 100644 services/src/main/resources/images/inf_obj.png
 create mode 100644 services/src/main/resources/images/packagefolder_obj.png

diff --git a/services/src/main/groovy/jd/gui/model/container/GenericContainer.groovy b/services/src/main/groovy/jd/gui/model/container/GenericContainer.groovy
index 365cb254..90b4f394 100644
--- a/services/src/main/groovy/jd/gui/model/container/GenericContainer.groovy
+++ b/services/src/main/groovy/jd/gui/model/container/GenericContainer.groovy
@@ -114,7 +114,7 @@ class GenericContainer implements Container {
         }
 
         protected Collection<Container.Entry> loadChildrenFromFileEntry() {
-            def tmpFile = File.createTempFile('jd-gui.', '.tmp.zip')
+            def tmpFile = File.createTempFile('jd-gui.', '.tmp.' + fsPath.fileName.toString())
             def tmpPath = Paths.get(tmpFile.toURI())
 
             tmpFile.withOutputStream { OutputStream os ->
@@ -130,7 +130,7 @@ class GenericContainer implements Container {
                     tmpFile.deleteOnExit()
 
                     def rootPath = rootDirectories.next()
-                    def container = api.getContainerFactory(subFileSystem)?.make(api, this, rootPath)
+                    def container = api.getContainerFactory(rootPath)?.make(api, this, rootPath)
                     if (container) {
                         return container.root.children
                     }
diff --git a/services/src/main/groovy/jd/gui/model/container/WarContainer.groovy b/services/src/main/groovy/jd/gui/model/container/WarContainer.groovy
new file mode 100644
index 00000000..9e6c944d
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/model/container/WarContainer.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.model.container
+
+import groovy.transform.CompileStatic
+import jd.gui.api.API
+import jd.gui.api.model.Container
+
+import java.nio.file.Path
+
+@CompileStatic
+class WarContainer extends GenericContainer {
+
+    WarContainer(API api, Container.Entry parentEntry, Path rootPath) {
+        super(api, parentEntry, rootPath)
+    }
+
+    String getType() { 'war' }
+}
diff --git a/services/src/main/groovy/jd/gui/service/container/GenericContainerFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/container/GenericContainerFactoryProvider.groovy
index eb15f726..816e2453 100644
--- a/services/src/main/groovy/jd/gui/service/container/GenericContainerFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/container/GenericContainerFactoryProvider.groovy
@@ -10,14 +10,13 @@ import jd.gui.api.model.Container
 import jd.gui.model.container.GenericContainer
 import jd.gui.spi.ContainerFactory
 
-import java.nio.file.FileSystem
 import java.nio.file.Path
 
 class GenericContainerFactoryProvider implements ContainerFactory {
 
 	String getType() { 'generic' }
 	
-	boolean accept(API api, FileSystem fileSystem) { true }
+	boolean accept(API api, Path rootPath) { true }
 
     Container make(API api, Container.Entry parentEntry, Path rootPath) {
 		return new GenericContainer(api, parentEntry, rootPath)
diff --git a/services/src/main/groovy/jd/gui/service/container/JarContainerFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/container/JarContainerFactoryProvider.groovy
index 16e75e02..ca1b4515 100644
--- a/services/src/main/groovy/jd/gui/service/container/JarContainerFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/container/JarContainerFactoryProvider.groovy
@@ -10,7 +10,6 @@ import jd.gui.api.model.Container
 import jd.gui.model.container.JarContainer
 import jd.gui.spi.ContainerFactory
 
-import java.nio.file.FileSystem
 import java.nio.file.Files
 import java.nio.file.InvalidPathException
 import java.nio.file.Path
@@ -19,25 +18,17 @@ class JarContainerFactoryProvider implements ContainerFactory {
 
 	String getType() { 'jar' }
 
-	boolean accept(API api, FileSystem fileSystem) {
-        def rootDirectories = fileSystem.rootDirectories.iterator()
-
-        if (rootDirectories.hasNext()) {
-            def rootPath = rootDirectories.next()
-
-            if (rootPath.toUri().toString().toLowerCase().endsWith('.jar!/')) {
-                // Specification: http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html
-                return true
-            } else {
-                // Extension: accept uncompressed JAR file containing a folder 'META-INF'
-                try {
-                    return Files.exists(fileSystem.getPath('/META-INF'))
-                } catch (InvalidPathException e) {
-                    return false
-                }
-            }
+	boolean accept(API api, Path rootPath) {
+        if (rootPath.toUri().toString().toLowerCase().endsWith('.jar!/')) {
+            // Specification: http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html
+            return true
         } else {
-            return false
+            // Extension: accept uncompressed JAR file containing a folder 'META-INF'
+            try {
+                return rootPath.fileSystem.provider().scheme.equals('file') && Files.exists(rootPath.resolve('META-INF'))
+            } catch (InvalidPathException e) {
+                return false
+            }
         }
     }
 
diff --git a/services/src/main/groovy/jd/gui/service/container/WarContainerFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/container/WarContainerFactoryProvider.groovy
new file mode 100644
index 00000000..d1466f36
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/container/WarContainerFactoryProvider.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.container
+
+import jd.gui.api.API
+import jd.gui.api.model.Container
+import jd.gui.model.container.WarContainer
+import jd.gui.spi.ContainerFactory
+
+import java.nio.file.Files
+import java.nio.file.InvalidPathException
+import java.nio.file.Path
+
+class WarContainerFactoryProvider implements ContainerFactory {
+
+    String getType() { 'war' }
+
+    boolean accept(API api, Path rootPath) {
+        if (rootPath.toUri().toString().toLowerCase().endsWith('.war!/')) {
+            return true
+        } else {
+            // Extension: accept uncompressed JAR file containing a folder 'WEB-INF'
+            try {
+                return rootPath.fileSystem.provider().scheme.equals('file') && Files.exists(rootPath.resolve('WEB-INF'))
+            } catch (InvalidPathException e) {
+                return false
+            }
+        }
+    }
+
+    Container make(API api, Container.Entry parentEntry, Path rootPath) {
+        return new WarContainer(api, parentEntry, rootPath)
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/fileloader/AbstractFileLoaderProvider.groovy b/services/src/main/groovy/jd/gui/service/fileloader/AbstractFileLoaderProvider.groovy
index 9b44b1b6..4a53f668 100644
--- a/services/src/main/groovy/jd/gui/service/fileloader/AbstractFileLoaderProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/fileloader/AbstractFileLoaderProvider.groovy
@@ -34,7 +34,7 @@ abstract class AbstractFileLoaderProvider implements FileLoader {
             InputStream getInputStream() { null }
             Collection<Container.Entry> getChildren() { children }
         }
-        def container = api.getContainerFactory(rootPath.fileSystem)?.make(api, parentEntry, rootPath)
+        def container = api.getContainerFactory(rootPath)?.make(api, parentEntry, rootPath)
 
         if (container) {
             parentEntry.children = container.root.children
diff --git a/services/src/main/groovy/jd/gui/service/fileloader/WarFileLoaderProvider.groovy b/services/src/main/groovy/jd/gui/service/fileloader/WarFileLoaderProvider.groovy
new file mode 100644
index 00000000..d7241e1b
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/fileloader/WarFileLoaderProvider.groovy
@@ -0,0 +1,13 @@
+package jd.gui.service.fileloader
+
+import jd.gui.api.API
+
+class WarFileLoaderProvider extends ZipFileLoaderProvider {
+
+    String[] getExtensions() { ['war'] }
+    String getDescription() { 'War files (*.war)' }
+
+    boolean accept(API api, File file) {
+        return file.exists() && file.canRead() && file.name.toLowerCase().endsWith('.war')
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/indexer/TextFileIndexerProvider.groovy b/services/src/main/groovy/jd/gui/service/indexer/TextFileIndexerProvider.groovy
index db19f3c3..bdd43d67 100644
--- a/services/src/main/groovy/jd/gui/service/indexer/TextFileIndexerProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/indexer/TextFileIndexerProvider.groovy
@@ -15,8 +15,8 @@ import java.util.regex.Pattern
 
 class TextFileIndexerProvider implements Indexer {
     String[] getTypes() { [
-        '*:file:*.txt', '*:file:*.html', '*:file:*.js', '*:file:*.jsp', '*:file:*.xml',
-        '*:file:*.xsl', '*:file:*.xslt', '*:file:*.xsd', '*:file:*.properties', '*:file:*.sql'] }
+        '*:file:*.txt', '*:file:*.html', '*:file:*.xhtml', '*:file:*.js', '*:file:*.jsp', '*:file:*.jspf',
+        '*:file:*.xml', '*:file:*.xsl', '*:file:*.xslt', '*:file:*.xsd', '*:file:*.properties', '*:file:*.sql'] }
 
     Pattern getPathPattern() { null }
 
diff --git a/services/src/main/groovy/jd/gui/service/indexer/WebXmlFileIndexerProvider.groovy b/services/src/main/groovy/jd/gui/service/indexer/WebXmlFileIndexerProvider.groovy
new file mode 100644
index 00000000..d4c51fe6
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/indexer/WebXmlFileIndexerProvider.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.indexer
+
+import jd.gui.api.API
+import jd.gui.api.model.Container
+import jd.gui.api.model.Indexes
+import jd.gui.util.xml.AbstractXmlPathFinder
+
+class WebXmlFileIndexerProvider extends XmlFileIndexerProvider {
+    String[] getTypes() { ['*:file:WEB-INF/web.xml'] }
+
+    void index(API api, Container.Entry entry, Indexes indexes) {
+        super.index(api, entry, indexes)
+
+        new WebXmlPathFinder(entry, indexes).find(entry.inputStream.text)
+    }
+
+    static class WebXmlPathFinder extends AbstractXmlPathFinder {
+        Container.Entry entry
+        Map<String, Collection> index
+
+        WebXmlPathFinder(Container.Entry entry, Indexes indexes) {
+            super([
+                'web-app/filter/filter-class',
+                'web-app/listener/listener-class',
+                'web-app/servlet/servlet-class'
+            ])
+            this.entry = entry
+            this.index = indexes.getIndex('typeReferences');
+        }
+
+        void handle(String path, String text, int position) {
+            index.get(text.replace('.', '/')).add(entry);
+        }
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/indexer/ZipFileIndexerProvider.groovy b/services/src/main/groovy/jd/gui/service/indexer/ZipFileIndexerProvider.groovy
index 70ee268f..c6320edc 100644
--- a/services/src/main/groovy/jd/gui/service/indexer/ZipFileIndexerProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/indexer/ZipFileIndexerProvider.groovy
@@ -14,7 +14,7 @@ import jd.gui.spi.Indexer
 import java.util.regex.Pattern
 
 class ZipFileIndexerProvider implements Indexer {
-    String[] getTypes() { ['*:file:*.zip', '*:file:*.jar'] }
+    String[] getTypes() { ['*:file:*.zip', '*:file:*.jar', '*:file:*.war'] }
 
     Pattern getPathPattern() { null }
 
diff --git a/services/src/main/groovy/jd/gui/service/sourcesaver/PackageSourceSaverProvider.groovy b/services/src/main/groovy/jd/gui/service/sourcesaver/PackageSourceSaverProvider.groovy
index 656bd995..c9be02ec 100644
--- a/services/src/main/groovy/jd/gui/service/sourcesaver/PackageSourceSaverProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/sourcesaver/PackageSourceSaverProvider.groovy
@@ -13,7 +13,7 @@ import jd.gui.util.JarContainerEntryUtil
 import java.nio.file.Path
 
 class PackageSourceSaverProvider extends DirectorySourceSaverProvider {
-    String[] getTypes() { ['jar:dir:*'] }
+    String[] getTypes() { ['jar:dir:*', 'war:dir:*'] }
 
     void save(API api, SourceSaver.Controller controller, SourceSaver.Listener listener, Path path, Container.Entry entry) {
         save(api, controller, listener, path, JarContainerEntryUtil.removeInnerTypeEntries(entry.children))
diff --git a/services/src/main/groovy/jd/gui/service/sourcesaver/ZipFileSourceSaverProvider.groovy b/services/src/main/groovy/jd/gui/service/sourcesaver/ZipFileSourceSaverProvider.groovy
index 4c643d9f..f817ee3d 100644
--- a/services/src/main/groovy/jd/gui/service/sourcesaver/ZipFileSourceSaverProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/sourcesaver/ZipFileSourceSaverProvider.groovy
@@ -17,11 +17,11 @@ import java.nio.file.Paths
 
 class ZipFileSourceSaverProvider extends DirectorySourceSaverProvider {
 
-    String[] getTypes() { ['*:file:*.zip', '*:file:*.jar'] }
+    String[] getTypes() { ['*:file:*.zip', '*:file:*.jar', '*:file:*.war'] }
 
     String getSourcePath(Container.Entry entry) {
         def path = entry.path
-        return path.substring(0, path.length()-3) + 'src.zip'
+        return path + '.src.zip'
     }
 
     @CompileStatic
diff --git a/services/src/main/groovy/jd/gui/service/treenode/CssFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/CssFileTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..87e460d6
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/CssFileTreeNodeFactoryProvider.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import jd.gui.api.API
+import jd.gui.api.feature.UriGettable
+import jd.gui.api.model.Container
+import jd.gui.view.data.TreeNodeBean
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants
+
+import javax.swing.ImageIcon
+import javax.swing.JComponent
+import javax.swing.tree.DefaultMutableTreeNode
+
+class CssFileTreeNodeFactoryProvider extends TextFileTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(HtmlFileTreeNodeFactoryProvider.class.classLoader.getResource('images/css_obj.png'))
+
+    String[] getTypes() { ['*:file:*.css'] }
+
+    public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
+        int lastSlashIndex = entry.path.lastIndexOf('/')
+        def name = entry.path.substring(lastSlashIndex+1)
+        return new TreeNode(entry, new TreeNodeBean(label:name, icon:icon, tip:"Location: $entry.uri.path"))
+    }
+
+    static class TreeNode extends TextFileTreeNodeFactoryProvider.TreeNode {
+        TreeNode(Container.Entry entry, Object userObject) {
+            super(entry, userObject)
+        }
+
+        public <T extends JComponent & UriGettable> T createPage(API api) {
+            return new TextFileTreeNodeFactoryProvider.Page(entry) {
+                String getSyntaxStyle() {
+                    SyntaxConstants.SYNTAX_STYLE_CSS
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/src/main/groovy/jd/gui/service/treenode/HtmlFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/HtmlFileTreeNodeFactoryProvider.groovy
index 9f44da45..895e54b6 100644
--- a/services/src/main/groovy/jd/gui/service/treenode/HtmlFileTreeNodeFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/treenode/HtmlFileTreeNodeFactoryProvider.groovy
@@ -17,7 +17,7 @@ import javax.swing.tree.DefaultMutableTreeNode
 class HtmlFileTreeNodeFactoryProvider extends TextFileTreeNodeFactoryProvider {
     static final ImageIcon icon = new ImageIcon(HtmlFileTreeNodeFactoryProvider.class.classLoader.getResource('images/html_obj.gif'))
 
-    String[] getTypes() { ['*:file:*.html'] }
+    String[] getTypes() { ['*:file:*.html', '*:file:*.xhtml'] }
 
     public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
         int lastSlashIndex = entry.path.lastIndexOf('/')
diff --git a/services/src/main/groovy/jd/gui/service/treenode/JspFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/JspFileTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..1b95abfb
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/JspFileTreeNodeFactoryProvider.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import jd.gui.api.API
+import jd.gui.api.feature.UriGettable
+import jd.gui.api.model.Container
+import jd.gui.view.data.TreeNodeBean
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants
+
+import javax.swing.ImageIcon
+import javax.swing.JComponent
+import javax.swing.tree.DefaultMutableTreeNode
+
+class JspFileTreeNodeFactoryProvider extends TextFileTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(HtmlFileTreeNodeFactoryProvider.class.classLoader.getResource('images/html_obj.gif'))
+
+    String[] getTypes() { ['*:file:*.jsp', '*:file:*.jspf'] }
+
+    public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
+        int lastSlashIndex = entry.path.lastIndexOf('/')
+        def name = entry.path.substring(lastSlashIndex+1)
+        return new TreeNode(entry, new TreeNodeBean(label:name, icon:icon, tip:"Location: $entry.uri.path"))
+    }
+
+    static class TreeNode extends TextFileTreeNodeFactoryProvider.TreeNode {
+        TreeNode(Container.Entry entry, Object userObject) {
+            super(entry, userObject)
+        }
+
+        public <T extends JComponent & UriGettable> T createPage(API api) {
+            return new TextFileTreeNodeFactoryProvider.Page(entry) {
+                String getSyntaxStyle() {
+                    SyntaxConstants.SYNTAX_STYLE_JSP
+                }
+            }
+        }
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/ManifestFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/ManifestFileTreeNodeFactoryProvider.groovy
index 6b4d4a8d..bda5ed70 100644
--- a/services/src/main/groovy/jd/gui/service/treenode/ManifestFileTreeNodeFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/treenode/ManifestFileTreeNodeFactoryProvider.groovy
@@ -16,13 +16,12 @@ import javax.swing.*
 import javax.swing.tree.DefaultMutableTreeNode
 
 class ManifestFileTreeNodeFactoryProvider extends FileTreeNodeFactoryProvider {
-    static
-    final ImageIcon icon = new ImageIcon(ManifestFileTreeNodeFactoryProvider.class.classLoader.getResource('images/manifest_obj.png'))
+    static final ImageIcon icon = new ImageIcon(ManifestFileTreeNodeFactoryProvider.class.classLoader.getResource('images/manifest_obj.png'))
 
     String[] getTypes() { ['*:file:META-INF/MANIFEST.MF'] }
 
     public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
-        return new TreeNode(entry, new TreeNodeBean(label: 'MANIFEST.MF', icon: icon, tip:"Location: $entry.uri.path"))
+        return new TreeNode(entry, new TreeNodeBean(label:'MANIFEST.MF', icon:icon, tip:"Location: $entry.uri.path"))
     }
 
     static class TreeNode extends FileTreeNodeFactoryProvider.TreeNode implements PageCreator {
diff --git a/services/src/main/groovy/jd/gui/service/treenode/MetainfDirectoryTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/MetainfDirectoryTreeNodeFactoryProvider.groovy
index 767f1524..7d612d94 100644
--- a/services/src/main/groovy/jd/gui/service/treenode/MetainfDirectoryTreeNodeFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/treenode/MetainfDirectoryTreeNodeFactoryProvider.groovy
@@ -5,12 +5,18 @@
 
 package jd.gui.service.treenode
 
+import javax.swing.ImageIcon
 import java.util.regex.Pattern
 
 class MetainfDirectoryTreeNodeFactoryProvider extends DirectoryTreeNodeFactoryProvider {
-    Pattern pattern = ~/META-IN(F|F\/.*)/
+    static final ImageIcon icon = new ImageIcon(MetainfDirectoryTreeNodeFactoryProvider.class.classLoader.getResource('images/inf_obj.png'))
 
-    String[] getTypes() { ['jar:dir:*'] }
+    Pattern pattern = ~/(WEB-INF|(WEB-INF\/classes\/)?META-IN(F|F\/.*))/
+
+    String[] getTypes() { ['jar:dir:*', 'war:dir:*'] }
 
     Pattern getPathPattern() { pattern }
+
+    ImageIcon getIcon() { icon }
+    ImageIcon getOpenIcon() { null }
 }
diff --git a/services/src/main/groovy/jd/gui/service/treenode/TextFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/TextFileTreeNodeFactoryProvider.groovy
index 2111f0cb..b9ad3da8 100644
--- a/services/src/main/groovy/jd/gui/service/treenode/TextFileTreeNodeFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/treenode/TextFileTreeNodeFactoryProvider.groovy
@@ -28,7 +28,7 @@ class TextFileTreeNodeFactoryProvider extends FileTreeNodeFactoryProvider {
         Theme.load(TextFileTreeNodeFactoryProvider.class.classLoader.getResourceAsStream('rsyntaxtextarea/themes/eclipse.xml'))
     }
 
-    String[] getTypes() { ['*:file:*.txt'] }
+    String[] getTypes() { ['*:file:*.txt', '*:file:*.md', '*:file:*.SF', '*:file:*.policy'] }
 
     public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
         int lastSlashIndex = entry.path.lastIndexOf('/')
diff --git a/services/src/main/groovy/jd/gui/service/treenode/WarFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/WarFileTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..4afa7f9c
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/WarFileTreeNodeFactoryProvider.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import jd.gui.api.API
+import jd.gui.api.feature.UriGettable
+import jd.gui.api.model.Container
+import jd.gui.view.data.TreeNodeBean
+
+import javax.swing.ImageIcon
+import javax.swing.tree.DefaultMutableTreeNode
+
+class WarFileTreeNodeFactoryProvider extends ZipFileTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(JarFileTreeNodeFactoryProvider.class.classLoader.getResource('images/war_obj.gif'))
+
+    String[] getTypes() { ['*:file:*.war'] }
+
+    public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
+        int lastSlashIndex = entry.path.lastIndexOf('/')
+        def name = entry.path.substring(lastSlashIndex+1)
+        def node = new TreeNode(entry, 'war', new TreeNodeBean(label:name, icon:icon))
+        // Add dummy node
+        node.add(new DefaultMutableTreeNode())
+        return node
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/WarPackageTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/WarPackageTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..67c2019c
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/WarPackageTreeNodeFactoryProvider.groovy
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import java.util.regex.Pattern
+
+class WarPackageTreeNodeFactoryProvider extends PackageTreeNodeFactoryProvider {
+    Pattern pattern = ~/WEB-INF\/classes\/.*/
+
+    String[] getTypes() { ['war:dir:*'] }
+
+    Pattern getPathPattern() { pattern }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/WebXmlFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/WebXmlFileTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..b9324511
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/WebXmlFileTreeNodeFactoryProvider.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import jd.gui.api.API
+import jd.gui.api.feature.PageCreator
+import jd.gui.api.feature.UriGettable
+import jd.gui.api.model.Container
+import jd.gui.view.component.WebXmlFilePage
+import jd.gui.view.data.TreeNodeBean
+
+import javax.swing.ImageIcon
+import javax.swing.JComponent
+import javax.swing.tree.DefaultMutableTreeNode
+
+
+class WebXmlFileTreeNodeFactoryProvider extends FileTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(ManifestFileTreeNodeFactoryProvider.class.classLoader.getResource('images/xml_obj.gif'))
+
+    String[] getTypes() { ['war:file:WEB-INF/web.xml'] }
+
+    public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
+        return new TreeNode(entry, new TreeNodeBean(label:'web.xml', icon:icon, tip:"Location: $entry.uri.path"))
+    }
+
+    static class TreeNode extends FileTreeNodeFactoryProvider.TreeNode implements PageCreator {
+        TreeNode(Container.Entry entry, Object userObject) {
+            super(entry, userObject)
+        }
+        // --- PageCreator --- //
+        public <T extends JComponent & UriGettable> T createPage(API api) {
+            return new WebXmlFilePage(api, entry)
+        }
+    }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/WebinfClassesDirectoryTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/WebinfClassesDirectoryTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..ff09139e
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/WebinfClassesDirectoryTreeNodeFactoryProvider.groovy
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import javax.swing.*
+import java.util.regex.Pattern
+
+class WebinfClassesDirectoryTreeNodeFactoryProvider extends DirectoryTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(WebinfClassesDirectoryTreeNodeFactoryProvider.class.classLoader.getResource('images/packagefolder_obj.png'))
+
+    String[] getTypes() { ['war:dir:WEB-INF/classes'] }
+
+    ImageIcon getIcon() { icon }
+    ImageIcon getOpenIcon() { null }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/WebinfLibDirectoryTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/WebinfLibDirectoryTreeNodeFactoryProvider.groovy
new file mode 100644
index 00000000..7beb1e59
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/service/treenode/WebinfLibDirectoryTreeNodeFactoryProvider.groovy
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.service.treenode
+
+import javax.swing.*
+
+class WebinfLibDirectoryTreeNodeFactoryProvider extends DirectoryTreeNodeFactoryProvider {
+    static final ImageIcon icon = new ImageIcon(WebinfLibDirectoryTreeNodeFactoryProvider.class.classLoader.getResource('images/archivefolder_obj.png'))
+
+    String[] getTypes() { ['war:dir:WEB-INF/lib'] }
+
+    ImageIcon getIcon() { icon }
+    ImageIcon getOpenIcon() { null }
+}
diff --git a/services/src/main/groovy/jd/gui/service/treenode/XmlFileTreeNodeFactoryProvider.groovy b/services/src/main/groovy/jd/gui/service/treenode/XmlFileTreeNodeFactoryProvider.groovy
index c8290e56..90cc9fc2 100644
--- a/services/src/main/groovy/jd/gui/service/treenode/XmlFileTreeNodeFactoryProvider.groovy
+++ b/services/src/main/groovy/jd/gui/service/treenode/XmlFileTreeNodeFactoryProvider.groovy
@@ -17,7 +17,7 @@ import javax.swing.tree.DefaultMutableTreeNode
 class XmlFileTreeNodeFactoryProvider extends TextFileTreeNodeFactoryProvider {
     static final ImageIcon icon = new ImageIcon(XmlFileTreeNodeFactoryProvider.class.classLoader.getResource('images/xml_obj.gif'))
 
-    String[] getTypes() { ['*:file:*.xml', '*:file:*.xsl', '*:file:*.xslt', '*:file:*.xsd'] }
+    String[] getTypes() { ['*:file:*.xml', '*:file:*.xsl', '*:file:*.xslt', '*:file:*.xsd', '*:file:*.tld', '*:file:*.wsdl'] }
 
     public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry) {
         int lastSlashIndex = entry.path.lastIndexOf('/')
@@ -38,4 +38,4 @@ class XmlFileTreeNodeFactoryProvider extends TextFileTreeNodeFactoryProvider {
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/src/main/groovy/jd/gui/view/component/WebXmlFilePage.groovy b/services/src/main/groovy/jd/gui/view/component/WebXmlFilePage.groovy
new file mode 100644
index 00000000..0998ba4d
--- /dev/null
+++ b/services/src/main/groovy/jd/gui/view/component/WebXmlFilePage.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.view.component
+
+import jd.gui.api.API
+import jd.gui.api.feature.ContentSavable
+import jd.gui.api.feature.IndexesChangeListener
+import jd.gui.api.feature.UriGettable
+import jd.gui.api.model.Container
+import jd.gui.api.model.Indexes
+import jd.gui.util.xml.AbstractXmlPathFinder
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants
+
+import java.awt.Point
+
+
+class WebXmlFilePage extends HyperlinkPage implements UriGettable, ContentSavable, IndexesChangeListener {
+    protected API api
+    protected Container.Entry entry
+    protected Collection<Indexes> collectionOfIndexes
+
+    WebXmlFilePage(API api, Container.Entry entry) {
+        this.api = api
+        this.entry = entry
+        // Load content file
+        def text = entry.inputStream.text
+        // Create hyperlinks
+        new PathFinder().find(text)
+        // Display
+        setText(text)
+        // Show hyperlinks
+        indexesChanged(api.collectionOfIndexes)
+    }
+
+    String getSyntaxStyle() { SyntaxConstants.SYNTAX_STYLE_XML }
+
+    protected boolean isHyperlinkEnabled(HyperlinkData hyperlinkData) { hyperlinkData.enabled }
+
+    protected void openHyperlink(int x, int y, HyperlinkData hyperlinkData) {
+        if (hyperlinkData.enabled) {
+            // Save current position in history
+            def location = textArea.getLocationOnScreen()
+            int offset = textArea.viewToModel(new Point(x-location.x as int, y-location.y as int))
+            def uri = entry.uri
+            api.addURI(new URI(uri.scheme, uri.authority, uri.path, 'position=' + offset, null))
+
+            // Open link
+            if (hyperlinkData instanceof TypeHyperlinkData) {
+                def internalTypeName = hyperlinkData.internalTypeName
+                def entries = collectionOfIndexes?.collect { it.getIndex('typeDeclarations')?.get(internalTypeName) }.flatten().grep { it!=null }
+                def rootUri = entry.container.root.uri.toString()
+                def sameContainerEntries = entries?.grep { it.uri.toString().startsWith(rootUri) }
+
+                if (sameContainerEntries) {
+                    api.openURI(x, y, sameContainerEntries, null, hyperlinkData.internalTypeName)
+                } else if (entries) {
+                    api.openURI(x, y, entries, null, hyperlinkData.internalTypeName)
+                }
+            } else {
+                String path = hyperlinkData.path
+                def entry = searchEntry(this.entry.container.root, path)
+                if (entry) {
+                    api.openURI(x, y, [entry], null, path)
+                }
+            }
+        }
+    }
+
+    static Container.Entry searchEntry(Container.Entry parent, String path) {
+        if (path.charAt(0) == '/')
+            path = path.substring(1)
+        return recursiveSearchEntry(parent, path)
+    }
+
+    static Container.Entry recursiveSearchEntry(Container.Entry parent, String path) {
+        def entry = parent.children.find { path.equals(it.path) }
+
+        if (entry) {
+            return entry
+        } else {
+            entry = parent.children.find { path.startsWith(it.path + '/') }
+            return entry ? searchEntry(entry, path) : null
+        }
+    }
+
+    // --- UriGettable --- //
+    URI getUri() { entry.uri }
+
+    // --- SourceSavable --- //
+    String getFileName() {
+        def path = entry.path
+        int index = path.lastIndexOf('/')
+        return path.substring(index+1)
+    }
+
+    void save(API api, OutputStream os) {
+        os << textArea.text
+    }
+
+    // --- IndexesChangeListener --- //
+    void indexesChanged(Collection<Indexes> collectionOfIndexes) {
+        // Update the list of containers
+        this.collectionOfIndexes = collectionOfIndexes
+        // Refresh links
+        boolean refresh = false
+
+        for (def entry : hyperlinks.entrySet()) {
+            def data = entry.value
+            boolean enabled
+
+            if (data instanceof TypeHyperlinkData) {
+                def internalTypeName = data.internalTypeName
+                enabled = collectionOfIndexes.find { it.getIndex('typeDeclarations')?.get(internalTypeName) } != null
+            } else {
+                enabled = searchEntry(this.entry.container.root, data.path) != null
+            }
+
+            if (data.enabled != enabled) {
+                data.enabled = enabled
+                refresh = true
+            }
+        }
+
+        if (refresh) {
+            textArea.repaint()
+        }
+    }
+
+    static class TypeHyperlinkData extends HyperlinkPage.HyperlinkData {
+        boolean enabled
+        String internalTypeName
+
+        TypeHyperlinkData(int startPosition, int endPosition, String internalTypeName) {
+            super(startPosition, endPosition)
+            this.enabled = false
+            this.internalTypeName = internalTypeName
+        }
+    }
+
+    static class PathHyperlinkData extends HyperlinkPage.HyperlinkData {
+        boolean enabled
+        String path
+
+        PathHyperlinkData(int startPosition, int endPosition, String path) {
+            super(startPosition, endPosition)
+            this.enabled = false
+            this.path = path
+        }
+    }
+
+    class PathFinder extends AbstractXmlPathFinder {
+        static HashSet<String> typeHyperlinkPaths = [
+            'web-app/filter/filter-class',
+            'web-app/listener/listener-class',
+            'web-app/servlet/servlet-class']
+
+        static HashSet<String> pathHyperlinkPaths = [
+            'web-app/jsp-config/taglib/taglib-location',
+            'web-app/welcome-file-list/welcome-file',
+            'web-app/login-config/form-login-config/form-login-page',
+            'web-app/login-config/form-login-config/form-error-page',
+            'web-app/jsp-config/jsp-property-group/include-prelude',
+            'web-app/jsp-config/jsp-property-group/include-coda']
+
+        PathFinder() {
+            super(typeHyperlinkPaths + pathHyperlinkPaths)
+        }
+
+        void handle(String path, String text, int position) {
+            def trim = text.trim()
+            if (trim) {
+                int startIndex = position + text.indexOf(trim)
+                int endIndex = startIndex + trim.length()
+
+                if (pathHyperlinkPaths.contains(path)) {
+                    addHyperlink(new PathHyperlinkData(startIndex, endIndex, trim))
+                } else {
+                    def internalTypeName = trim.replace('.', '/')
+                    addHyperlink(new TypeHyperlinkData(startIndex, endIndex, internalTypeName))
+                }
+            }
+        }
+    }
+}
diff --git a/services/src/main/java/jd/gui/util/xml/AbstractXmlPathFinder.java b/services/src/main/java/jd/gui/util/xml/AbstractXmlPathFinder.java
new file mode 100644
index 00000000..50de9b67
--- /dev/null
+++ b/services/src/main/java/jd/gui/util/xml/AbstractXmlPathFinder.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2008-2015 Emmanuel Dupuy
+ * This program is made available under the terms of the GPLv3 License.
+ */
+
+package jd.gui.util.xml;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.StringReader;
+import java.util.*;
+
+public abstract class AbstractXmlPathFinder {
+
+    protected HashMap<String, HashSet<String>> tagNameToPaths = new HashMap<>();
+    protected StringBuffer sb = new StringBuffer(200);
+
+    public AbstractXmlPathFinder(Collection<String> paths) {
+        for (String path : paths) {
+            if ((path != null) && (path.length() > 0)) {
+                // Normalize path
+                path = '/' + path;
+                int lastIndex = path.lastIndexOf('/');
+                String lastTagName = path.substring(lastIndex+1);
+
+                // Add tag names to map
+                HashSet<String> setOfPaths = tagNameToPaths.get(lastTagName);
+                if (setOfPaths == null) {
+                    tagNameToPaths.put(lastTagName, setOfPaths = new HashSet<>());
+                }
+                setOfPaths.add(path);
+            }
+        }
+    }
+
+    public void find(String text) {
+        sb.setLength(0);
+
+        try {
+            XMLInputFactory factory = XMLInputFactory.newInstance();
+            XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(text));
+
+            String tagName = "";
+            int offset = 0;
+
+            while (reader.hasNext()) {
+                reader.next();
+
+                switch (reader.getEventType())
+                {
+                case XMLStreamReader.START_ELEMENT:
+                    sb.append('/').append(tagName = reader.getLocalName());
+                    offset = reader.getLocation().getCharacterOffset();
+                    break;
+                case XMLStreamReader.END_ELEMENT:
+                    sb.setLength(sb.length() - reader.getLocalName().length() - 1);
+                    break;
+                case XMLStreamReader.CHARACTERS:
+                    HashSet<String> setOfPaths = tagNameToPaths.get(tagName);
+
+                    if (setOfPaths != null) {
+                        String path = sb.toString();
+
+                        if (setOfPaths.contains(path)) {
+                            // Search start offset
+                            while (offset > 0) {
+                                if (text.charAt(offset) == '>') {
+                                    break;
+                                } else {
+                                    offset--;
+                                }
+                            }
+
+                            handle(path.substring(1), reader.getText(), offset+1);
+                        }
+                    }
+                    break;
+                }
+            }
+        } catch (XMLStreamException ignore) {
+        }
+    }
+
+    public abstract void handle(String path, String text, int position);
+}
diff --git a/services/src/main/resources/META-INF/services/jd.gui.spi.ContainerFactory b/services/src/main/resources/META-INF/services/jd.gui.spi.ContainerFactory
index 09008cf8..769ebe19 100644
--- a/services/src/main/resources/META-INF/services/jd.gui.spi.ContainerFactory
+++ b/services/src/main/resources/META-INF/services/jd.gui.spi.ContainerFactory
@@ -1,2 +1,4 @@
+# Order is important
+jd.gui.service.container.WarContainerFactoryProvider
 jd.gui.service.container.JarContainerFactoryProvider
 jd.gui.service.container.GenericContainerFactoryProvider
diff --git a/services/src/main/resources/META-INF/services/jd.gui.spi.FileLoader b/services/src/main/resources/META-INF/services/jd.gui.spi.FileLoader
index 98254020..64c280a8 100644
--- a/services/src/main/resources/META-INF/services/jd.gui.spi.FileLoader
+++ b/services/src/main/resources/META-INF/services/jd.gui.spi.FileLoader
@@ -1,4 +1,5 @@
 jd.gui.service.fileloader.ClassFileLoaderProvider
 jd.gui.service.fileloader.JarFileLoaderProvider
 jd.gui.service.fileloader.LogFileLoaderProvider
+jd.gui.service.fileloader.WarFileLoaderProvider
 jd.gui.service.fileloader.ZipFileLoaderProvider
diff --git a/services/src/main/resources/META-INF/services/jd.gui.spi.Indexer b/services/src/main/resources/META-INF/services/jd.gui.spi.Indexer
index c8d5c7c9..cfe3ccd1 100644
--- a/services/src/main/resources/META-INF/services/jd.gui.spi.Indexer
+++ b/services/src/main/resources/META-INF/services/jd.gui.spi.Indexer
@@ -2,4 +2,5 @@ jd.gui.service.indexer.DirectoryIndexerProvider
 jd.gui.service.indexer.ClassFileIndexerProvider
 jd.gui.service.indexer.MetainfServiceFileIndexerProvider
 jd.gui.service.indexer.TextFileIndexerProvider
+jd.gui.service.indexer.WebXmlFileIndexerProvider
 jd.gui.service.indexer.ZipFileIndexerProvider
diff --git a/services/src/main/resources/META-INF/services/jd.gui.spi.TreeNodeFactory b/services/src/main/resources/META-INF/services/jd.gui.spi.TreeNodeFactory
index ff7f4d20..76b760a5 100644
--- a/services/src/main/resources/META-INF/services/jd.gui.spi.TreeNodeFactory
+++ b/services/src/main/resources/META-INF/services/jd.gui.spi.TreeNodeFactory
@@ -1,4 +1,5 @@
 jd.gui.service.treenode.ClassFileTreeNodeFactoryProvider
+jd.gui.service.treenode.CssFileTreeNodeFactoryProvider
 jd.gui.service.treenode.DirectoryTreeNodeFactoryProvider
 jd.gui.service.treenode.DtdFileTreeNodeFactoryProvider
 jd.gui.service.treenode.FileTreeNodeFactoryProvider
@@ -6,7 +7,7 @@ jd.gui.service.treenode.HtmlFileTreeNodeFactoryProvider
 jd.gui.service.treenode.JarFileTreeNodeFactoryProvider
 #jd.gui.service.treenode.JavaFileTreeNodeFactoryProvider
 jd.gui.service.treenode.JavascriptFileTreeNodeFactoryProvider
-#jd.gui.service.treenode.JspFileTreeNodeFactoryProvider
+jd.gui.service.treenode.JspFileTreeNodeFactoryProvider
 jd.gui.service.treenode.ManifestFileTreeNodeFactoryProvider
 jd.gui.service.treenode.MetainfDirectoryTreeNodeFactoryProvider
 jd.gui.service.treenode.MetainfServiceFileTreeNodeFactoryProvider
@@ -14,5 +15,10 @@ jd.gui.service.treenode.PackageTreeNodeFactoryProvider
 jd.gui.service.treenode.PropertiesFileTreeNodeFactoryProvider
 jd.gui.service.treenode.SqlFileTreeNodeFactoryProvider
 jd.gui.service.treenode.TextFileTreeNodeFactoryProvider
+jd.gui.service.treenode.WarFileTreeNodeFactoryProvider
+jd.gui.service.treenode.WarPackageTreeNodeFactoryProvider
+jd.gui.service.treenode.WebinfClassesDirectoryTreeNodeFactoryProvider
+jd.gui.service.treenode.WebinfLibDirectoryTreeNodeFactoryProvider
+jd.gui.service.treenode.WebXmlFileTreeNodeFactoryProvider
 jd.gui.service.treenode.XmlFileTreeNodeFactoryProvider
 jd.gui.service.treenode.ZipFileTreeNodeFactoryProvider
diff --git a/services/src/main/resources/images/archivefolder_obj.png b/services/src/main/resources/images/archivefolder_obj.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d653f69d1f31a363cef2c946c4d31db01b18668
GIT binary patch
literal 3017
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
LNkvXXu0mjf0A0qh

literal 0
HcmV?d00001

diff --git a/services/src/main/resources/images/css_obj.png b/services/src/main/resources/images/css_obj.png
new file mode 100644
index 0000000000000000000000000000000000000000..4caa409cdb4288ea619113dad80274997f3d41cd
GIT binary patch
literal 2972
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
S`_*j#0000<MNUMnLSTYkg2%)F

literal 0
HcmV?d00001

diff --git a/services/src/main/resources/images/inf_obj.png b/services/src/main/resources/images/inf_obj.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9f9c42d6f82e0af8e7f42aab584c799a4c84fef
GIT binary patch
literal 2954
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
A`~Uy|

literal 0
HcmV?d00001

diff --git a/services/src/main/resources/images/packagefolder_obj.png b/services/src/main/resources/images/packagefolder_obj.png
new file mode 100644
index 0000000000000000000000000000000000000000..93053b772f5529aae3d02f2e27314b9ffa1f440f
GIT binary patch
literal 421
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMffdHQn*Z&Ov*B0qFxl8`I(J?Pg
z{l|rh9}i~yxX_ShXY}IOlpilPytvTu<HL#n|NkHEHv9E$*N+Dae%zSQ6YKWl#f(d<
z8~%K||M>jQE%inZ4pgiy(*AH`!qOa-N`HrW@v>`kRQiJ?_tYD`zkh5`tI^?BGoYPR
z2fwa!#Q_avED7=pW^j0RBMrn!@^*Kzw@WrC1#&nGJR*yMv<Dcwoy@iaG73Fi977~7
zPd$HIsL6nb<$|x)gf1rCits%j{{Qz#(mr1?Z<F=pGb}&a|FpR^3ZH%)C%g0O#;;*M
zwe@Sy=YMPdaj@(BxjU9!9w~c2-<s9D&LhgQv`g%OYKuc^SaxA?Urd^0f77Zrvp+6T
h@|L`zbFJ2bnd1m+(w)h=$AOkJc)I$ztaD0e0sz5$mJt8|

literal 0
HcmV?d00001