Coverage Report - sk.baka.webvm.analyzer.classloader.ResourceLink
 
Classes in this File Line Coverage Branch Coverage Complexity
DirResourceLink
96%
26/27
85%
12/14
2.17
DirResourceLink$1
100%
2/2
100%
4/4
2.17
JarResourceLink
84%
71/84
84%
37/44
2.17
ResourceLink
82%
37/45
76%
20/26
2.17
ResourceLinkGroup
38%
5/13
N/A
2.17
 
 1  
 /**
 2  
  * Copyright 2009 Martin Vysny.
 3  
  *
 4  
  * This file is part of WebVM.
 5  
  *
 6  
  * WebVM is free software: you can redistribute it and/or modify
 7  
  * it under the terms of the GNU General Public License as published by
 8  
  * the Free Software Foundation, either version 3 of the License, or
 9  
  * (at your option) any later version.
 10  
  *
 11  
  * WebVM is distributed in the hope that it will be useful,
 12  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  
  * GNU General Public License for more details.
 15  
  *
 16  
  * You should have received a copy of the GNU General Public License
 17  
  * along with WebVM.  If not, see <http://www.gnu.org/licenses/>.
 18  
  */
 19  
 package sk.baka.webvm.analyzer.classloader;
 20  
 
 21  
 import java.io.File;
 22  
 import java.io.FileFilter;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.IOException;
 25  
 import java.io.InputStream;
 26  
 import java.io.Serializable;
 27  
 import java.util.ArrayList;
 28  
 import java.util.Collection;
 29  
 import java.util.Enumeration;
 30  
 import java.util.HashSet;
 31  
 import java.util.List;
 32  
 import java.util.Set;
 33  
 import java.util.logging.Level;
 34  
 import java.util.logging.Logger;
 35  
 import java.util.zip.ZipEntry;
 36  
 import java.util.zip.ZipFile;
 37  
 import static sk.baka.webvm.misc.Constants.*;
 38  
 
 39  
 /**
 40  
  * Represents an on-disk package or a package item.
 41  
  * @author Martin Vysny
 42  
  */
 43  105
 public abstract class ResourceLink implements Serializable {
 44  
 
 45  
     /**
 46  
      * Opens given directory or jar file and allows package and resource enumeration.
 47  
      * @param file the resource directory or a jar file
 48  
      * @return new resource enumerator.
 49  
      */
 50  
     public static ResourceLink newFor(final File file) {
 51  40
         if (file.isDirectory()) {
 52  20
             return new DirResourceLink(file, true);
 53  
         } else {
 54  20
             return new JarResourceLink(file, "", true);
 55  
         }
 56  
     }
 57  
 
 58  
     /**
 59  
      * Returns a list of names from given list of links
 60  
      * @param links not null
 61  
      * @return never null
 62  
      */
 63  
     public static List<String> getNames(final List<ResourceLink> links) {
 64  15
         final List<String> result = new ArrayList<String>(links.size());
 65  15
         for (final ResourceLink link : links) {
 66  23
             result.add(link.getName());
 67  
         }
 68  15
         return result;
 69  
     }
 70  
 
 71  
     /**
 72  
      * Finds first link with given name.
 73  
      * @param links a list of links, must not be null
 74  
      * @param name the name of the resource.
 75  
      * @return the link with given name, never null
 76  
      * @throws IllegalArgumentException if no such link exists
 77  
      */
 78  
     public static ResourceLink findFirstByName(final List<ResourceLink> links, final String name) {
 79  22
         for (final ResourceLink link : links) {
 80  30
             if (link.getName().equals(name)) {
 81  22
                 return link;
 82  
             }
 83  
         }
 84  0
         throw new IllegalArgumentException("No such link: " + name);
 85  
     }
 86  
 
 87  
     /**
 88  
      * Performs a search for given substring. The substring matching must be performed in the last resource path item only - i.e.
 89  
      * the function will match the following for "a": /a, /a/a, /b/a, but not /a/b.
 90  
      * @param substring a string to search, must not be null.
 91  
      * @return non-null list of matched resources, may be empty.
 92  
      * @throws IOException on i/o error
 93  
      */
 94  
     public abstract List<ResourceLink> search(final String substring) throws IOException;
 95  
 
 96  
     /**
 97  
      * Returns length of underlying resource.
 98  
      * @return the length or -1 if not known or invoked on a package.
 99  
      * @throws IOException on i/o error
 100  
      */
 101  
     public abstract long getLength() throws IOException;
 102  
 
 103  
     /**
 104  
      * Lists all direct child packages and items of this package. It is invalid to call this method on a non-package resource. Groups single-package-child
 105  
      * names together.
 106  
      * @return list of all children.
 107  
      * @throws IOException on i/o error
 108  
      */
 109  
     public final List<ResourceLink> listAndGroup() throws IOException {
 110  2
         assertPackage();
 111  2
         final List<ResourceLink> result = list();
 112  6
         for (int i = 0; i < result.size(); i++) {
 113  4
             result.set(i, groupIfNecessary(result.get(i)));
 114  
         }
 115  2
         return result;
 116  
     }
 117  
 
 118  
     private ResourceLink groupIfNecessary(final ResourceLink child) throws IOException {
 119  4
         if (!child.isPackage()) {
 120  0
             return child;
 121  
         }
 122  4
         ResourceLink singlePackage = child;
 123  4
         ResourceLink prevPackage = null;
 124  4
         final StringBuilder sb = new StringBuilder(child.getName());
 125  
         do {
 126  10
             final List<ResourceLink> children = singlePackage.list();
 127  10
             prevPackage = singlePackage;
 128  10
             singlePackage = getSinglePackage(children);
 129  10
             if (singlePackage != null) {
 130  6
                 sb.append('.');
 131  6
                 sb.append(singlePackage.getName());
 132  
             }
 133  10
         } while (singlePackage != null);
 134  4
         if (prevPackage == child) {
 135  2
             return child;
 136  
         }
 137  2
         return new ResourceLinkGroup(sb.toString(), prevPackage);
 138  
     }
 139  
 
 140  
     private static ResourceLink getSinglePackage(final Collection<? extends ResourceLink> contents) {
 141  10
         if (contents.size() != 1) {
 142  0
             return null;
 143  
         }
 144  10
         final ResourceLink link = contents.iterator().next();
 145  10
         if (!link.isPackage()) {
 146  4
             return null;
 147  
         }
 148  6
         return link;
 149  
     }
 150  
 
 151  
     /**
 152  
      * Lists all direct child packages and items of this package. It is invalid to call this method on a non-package resource.
 153  
      * @return list of all children.
 154  
      * @throws IOException on i/o error
 155  
      */
 156  
     public abstract List<ResourceLink> list() throws IOException;
 157  
 
 158  
     /**
 159  
      * Checks if resource denoted by this object is actually a package, or just a resource file.
 160  
      * @return true if this is a package, false otherwise. {@link #root() Root link} is always a package.
 161  
      */
 162  
     public abstract boolean isPackage();
 163  
 
 164  
     /**
 165  
      * Opens a stream to the file denoted by this link. It is invalid to call this method on a package resource.
 166  
      * @return the file contents.
 167  
      * @throws IOException on i/o error
 168  
      */
 169  
     public abstract InputStream open() throws IOException;
 170  
 
 171  
     /**
 172  
      * Returns the package/resource name. Does not include names of the parent packages nor any slash characters. Name of the root package is a full directory/jar file name.
 173  
      * @return the name of the resource denoted by this link.
 174  
      */
 175  
     public abstract String getName();
 176  
 
 177  
     /**
 178  
      * Returns a full name, including parent packages and full path to the root. Must not end with a slash.
 179  
      * @return a full name.
 180  
      */
 181  
     public abstract String getFullName();
 182  
 
 183  
     /**
 184  
      * Checks if this resource link denotes a root of a jar/directory.
 185  
      * @return true if this is a jar/directory root, false otherwise.
 186  
      */
 187  
     public abstract boolean isRoot();
 188  
 
 189  
     /**
 190  
      * Returns a file link to the resource container containing these links. Required to be valid only for {@link #isRoot() root} links.
 191  
      * @return file denoting the container or null.
 192  
      */
 193  
     public abstract File getContainer();
 194  
 
 195  
     @Override
 196  
     public String toString() {
 197  0
         return getName();
 198  
     }
 199  
 
 200  
     /**
 201  
      * Asserts that this is a package.
 202  
      */
 203  
     protected void assertPackage() {
 204  49
         if (!isPackage()) {
 205  0
             throw new IllegalArgumentException(this + " must be a package");
 206  
         }
 207  49
     }
 208  
 
 209  
     /**
 210  
      * Asserts that this is not a package.
 211  
      */
 212  
     protected void assertNotPackage() {
 213  0
         if (isPackage()) {
 214  0
             throw new IllegalArgumentException(this + " must not be a package");
 215  
         }
 216  0
     }
 217  
 }
 218  
 
 219  
 /**
 220  
  * Provides package information for a directory containing classpath items.
 221  
  * @author Martin Vysny
 222  
  */
 223  
 final class DirResourceLink extends ResourceLink {
 224  
 
 225  
     private static final long serialVersionUID = 1L;
 226  
     private final File file;
 227  
     private final boolean isRoot;
 228  
 
 229  52
     DirResourceLink(final File file, final boolean isRoot) {
 230  52
         this.file = file;
 231  52
         this.isRoot = isRoot;
 232  52
     }
 233  
 
 234  
     @Override
 235  
     public List<ResourceLink> list() {
 236  18
         assertPackage();
 237  18
         final File[] children = file.listFiles();
 238  18
         final List<ResourceLink> result = new ArrayList<ResourceLink>(children.length);
 239  42
         for (final File child : children) {
 240  24
             result.add(new DirResourceLink(child, false));
 241  
         }
 242  18
         return result;
 243  
     }
 244  
 
 245  
     @Override
 246  
     public boolean isPackage() {
 247  35
         return file.isDirectory();
 248  
     }
 249  
 
 250  
     @Override
 251  
     public InputStream open() throws IOException {
 252  0
         return new FileInputStream(file);
 253  
     }
 254  
 
 255  
     @Override
 256  
     public String getName() {
 257  33
         return isRoot ? file.getAbsolutePath() : file.getName();
 258  
     }
 259  
 
 260  
     @Override
 261  
     public long getLength() {
 262  1
         return isPackage() ? -1 : file.length();
 263  
     }
 264  
 
 265  
     @Override
 266  
     public boolean isRoot() {
 267  2
         return isRoot;
 268  
     }
 269  
 
 270  
     @Override
 271  
     public File getContainer() {
 272  1
         return isRoot ? file : null;
 273  
     }
 274  
 
 275  
     @Override
 276  
     public List<ResourceLink> search(String substring) {
 277  5
         assertPackage();
 278  5
         final List<ResourceLink> result = new ArrayList<ResourceLink>();
 279  5
         searchRecurse(result, substring.toLowerCase(), file);
 280  5
         return result;
 281  
     }
 282  
 
 283  
     private void searchRecurse(final List<ResourceLink> result, final String substring, final File file) {
 284  27
         for (final File f : file.listFiles(new FileFilter() {
 285  
 
 286  
             public boolean accept(File pathname) {
 287  31
                 return pathname.isDirectory() || pathname.getName().toLowerCase().contains(substring);
 288  
             }
 289  
         })) {
 290  24
             if (f.getName().toLowerCase().contains(substring)) {
 291  8
                 result.add(new DirResourceLink(f, false));
 292  
             }
 293  24
             if (f.isDirectory()) {
 294  22
                 searchRecurse(result, substring, f);
 295  
             }
 296  
         }
 297  27
     }
 298  
 
 299  
     @Override
 300  
     public String getFullName() {
 301  2
         return file.getAbsolutePath();
 302  
     }
 303  
 }
 304  
 
 305  
 /**
 306  
  * Provides package information for an on-disk jar file.
 307  
  * @author Martin Vysny
 308  
  */
 309  
 final class JarResourceLink extends ResourceLink {
 310  
 
 311  
     private static final long serialVersionUID = 1L;
 312  
     private final File jarFile;
 313  
     private final String fullEntryName;
 314  
     private final boolean isRoot;
 315  
 
 316  51
     JarResourceLink(final File jarFile, final String fullEntryName, final boolean isRoot) {
 317  51
         this.jarFile = jarFile;
 318  51
         this.fullEntryName = fullEntryName;
 319  51
         this.isRoot = isRoot;
 320  51
     }
 321  
 
 322  
     @Override
 323  
     public List<ResourceLink> list() throws IOException {
 324  18
         assertPackage();
 325  18
         final List<ResourceLink> result = new ArrayList<ResourceLink>();
 326  18
         final ZipFile zfile = new ZipFile(jarFile);
 327  
         try {
 328  18
             final Set<String> resultNames = new HashSet<String>();
 329  18
             for (final Enumeration<? extends ZipEntry> e = zfile.entries(); e.hasMoreElements();) {
 330  72
                 final ZipEntry entry = e.nextElement();
 331  72
                 final String name = entry.getName();
 332  72
                 if (!name.startsWith(fullEntryName) || name.equals(fullEntryName)) {
 333  3
                     continue;
 334  
                 }
 335  45
                 String itemname = name.substring(fullEntryName.length());
 336  45
                 final int slash = itemname.indexOf('/');
 337  45
                 if (slash >= 0) {
 338  42
                     itemname = itemname.substring(0, slash + 1);
 339  
                 }
 340  45
                 if (resultNames.add(itemname)) {
 341  24
                     final ResourceLink link = new JarResourceLink(jarFile, fullEntryName + itemname, false);
 342  24
                     result.add(link);
 343  
                 }
 344  45
             }
 345  18
             return result;
 346  
         } finally {
 347  18
             closeQuietly(zfile);
 348  
         }
 349  
     }
 350  
 
 351  
     private static void closeQuietly(final ZipFile zf) {
 352  
         try {
 353  24
             zf.close();
 354  0
         } catch (IOException ex) {
 355  0
             Logger.getLogger(JarResourceLink.class.getName()).log(Level.SEVERE, null, ex);
 356  24
         }
 357  24
     }
 358  
 
 359  
     @Override
 360  
     public boolean isPackage() {
 361  36
         return fullEntryName.length() == 0 || fullEntryName.endsWith("/");
 362  
     }
 363  
 
 364  
     @Override
 365  
     public InputStream open() throws IOException {
 366  0
         final ZipFile zfile = new ZipFile(jarFile);
 367  0
         final ZipEntry entry = zfile.getEntry(fullEntryName);
 368  0
         return zfile.getInputStream(entry);
 369  
     }
 370  
 
 371  
     @Override
 372  
     public String getName() {
 373  32
         if (isRoot) {
 374  1
             return jarFile.getAbsolutePath();
 375  
         }
 376  31
         String fullName = fullEntryName;
 377  31
         if (fullEntryName.endsWith("/")) {
 378  28
             fullName = fullName.substring(0, fullEntryName.length() - 1);
 379  
         }
 380  31
         final int lastSlash = fullName.lastIndexOf('/');
 381  31
         return fullName.substring(lastSlash + 1, fullName.length());
 382  
     }
 383  
 
 384  
     @Override
 385  
     public long getLength() throws IOException {
 386  1
         if (isPackage()) {
 387  1
             return -1;
 388  
         }
 389  0
         final ZipFile zfile = new ZipFile(jarFile);
 390  0
         final ZipEntry entry = zfile.getEntry(fullEntryName);
 391  0
         if (entry == null) {
 392  0
             throw new IOException("No such entry: " + fullEntryName);
 393  
         }
 394  0
         return entry.getSize();
 395  
     }
 396  
 
 397  
     @Override
 398  
     public boolean isRoot() {
 399  4
         return isRoot;
 400  
     }
 401  
 
 402  
     @Override
 403  
     public File getContainer() {
 404  1
         return jarFile;
 405  
     }
 406  
 
 407  
     @Override
 408  
     public String toString() {
 409  0
         if (isRoot()) {
 410  0
             return getName() + " [" + (jarFile.length() / KIBIBYTES) + "K]";
 411  
         }
 412  0
         return super.toString();
 413  
     }
 414  
 
 415  
     @Override
 416  
     public List<ResourceLink> search(String substring) throws IOException {
 417  6
         assertPackage();
 418  6
         final String substr = substring.toLowerCase();
 419  6
         final List<ResourceLink> result = new ArrayList<ResourceLink>();
 420  6
         final Set<String> ignorePrefixes = new HashSet<String>();
 421  6
         final ZipFile zfile = new ZipFile(jarFile);
 422  
         try {
 423  6
             for (final Enumeration<? extends ZipEntry> e = zfile.entries(); e.hasMoreElements();) {
 424  24
                 final ZipEntry entry = e.nextElement();
 425  24
                 final String name = entry.getName();
 426  24
                 if (!isAccepted(name, ignorePrefixes, substr)) {
 427  7
                     continue;
 428  
                 }
 429  17
                 final String itemname = name.substring(fullEntryName.length());
 430  17
                 if (!itemname.toLowerCase().contains(substr)) {
 431  10
                     continue;
 432  
                 }
 433  7
                 ignorePrefixes.add(name);
 434  7
                 final ResourceLink link = new JarResourceLink(jarFile, name, false);
 435  7
                 result.add(link);
 436  7
             }
 437  6
             return result;
 438  
         } finally {
 439  6
             closeQuietly(zfile);
 440  
         }
 441  
     }
 442  
 
 443  
     private boolean isAccepted(String name, Set<String> ignorePrefixes, String substring) {
 444  24
         if (!name.startsWith(fullEntryName) || name.equals(fullEntryName)) {
 445  4
             return false;
 446  
         }
 447  20
         for (final String prefix : ignorePrefixes) {
 448  7
             if (name.startsWith(prefix)) {
 449  
                 // check if the rest of the string contains given substring
 450  5
                 if (!name.substring(prefix.length()).contains(substring)) {
 451  3
                     return false;
 452  
                 }
 453  
             }
 454  
         }
 455  17
         return true;
 456  
     }
 457  
 
 458  
     @Override
 459  
     public String getFullName() {
 460  2
         if (isRoot()) {
 461  1
             return jarFile.getAbsolutePath();
 462  
         }
 463  1
         String fullName = fullEntryName;
 464  1
         if (fullEntryName.endsWith("/")) {
 465  1
             fullName = fullName.substring(0, fullEntryName.length() - 1);
 466  
         }
 467  1
         return jarFile.getAbsolutePath() + "!/" + fullName;
 468  
     }
 469  
 }
 470  
 
 471  
 /**
 472  
  * A delegate for a real resource link. Serves for grouping of multiple package names. Always a package.
 473  
  * @author Martin Vysny
 474  
  */
 475  
 final class ResourceLinkGroup extends ResourceLink {
 476  
 
 477  
     private static final long serialVersionUID = 1L;
 478  
     private final String name;
 479  
     private final ResourceLink delegate;
 480  
 
 481  2
     public ResourceLinkGroup(final String name, final ResourceLink delegate) {
 482  2
         this.name = name;
 483  2
         this.delegate = delegate;
 484  
 
 485  2
     }
 486  
 
 487  
     @Override
 488  
     public List<ResourceLink> list() throws IOException {
 489  0
         return delegate.list();
 490  
     }
 491  
 
 492  
     @Override
 493  
     public boolean isPackage() {
 494  0
         return true;
 495  
     }
 496  
 
 497  
     @Override
 498  
     public InputStream open() throws IOException {
 499  0
         throw new IOException("Not a file");
 500  
     }
 501  
 
 502  
     @Override
 503  
     public String getName() {
 504  2
         return name;
 505  
     }
 506  
 
 507  
     @Override
 508  
     public long getLength() {
 509  0
         return -1;
 510  
     }
 511  
 
 512  
     @Override
 513  
     public boolean isRoot() {
 514  0
         return false;
 515  
     }
 516  
 
 517  
     @Override
 518  
     public File getContainer() {
 519  0
         return delegate.getContainer();
 520  
     }
 521  
 
 522  
     @Override
 523  
     public List<ResourceLink> search(String substring) throws IOException {
 524  0
         return delegate.search(substring);
 525  
     }
 526  
 
 527  
     @Override
 528  
     public String getFullName() {
 529  0
         return delegate.getFullName();
 530  
     }
 531  
 }