### Question:
ManifestDescriptor { public static ManifestDescriptor parse( final String manifest ) throws IllegalArgumentException { final String[] lines = manifest.split( "\n" ); if ( 0 == lines.length || !lines[ 0 ].equals( "CACHE MANIFEST" ) ) { throw new IllegalArgumentException( "Manifest header not present" ); } final ManifestDescriptor descriptor = new ManifestDescriptor(); final int cacheMode = 1; final int networkMode = 2; final int fallbackMode = 3; int mode = cacheMode; for ( int i = 1; i < lines.length; i++ ) { final String line = lines[ i ].trim(); if ( line.startsWith( "#" ) || 0 == line.length() ) { continue; } else if ( "CACHE:".equals( line ) ) { mode = cacheMode; } else if ( "NETWORK:".equals( line ) ) { mode = networkMode; } else if ( "FALLBACK:".equals( line ) ) { mode = fallbackMode; } else if ( networkMode == mode ) { descriptor.getNetworkResources().add( urlDecode( line ) ); } else if ( cacheMode == mode ) { descriptor.getCachedResources().add( urlDecode( line ) ); } else { final String[] elements = line.split( " +" ); if ( 2 != elements.length ) { final String message = "Fallback line '" + line + "' should have two url paths separated by whitespace"; throw new IllegalArgumentException( message ); } descriptor.getFallbackResources().put( urlDecode( elements[ 0 ] ), urlDecode( elements[ 1 ] ) ); } } return descriptor; }  @Nonnull List<String> getCachedResources(); @Nonnull List<String> getNetworkResources(); @Nonnull Map<String, String> getFallbackResources(); void merge( @Nonnull final ManifestDescriptor other ); @Override String toString(); static ManifestDescriptor parse( final String manifest );  }### Answer:
@Test public void parse() throws Exception { final String manifest = "CACHE MANIFEST\n" + "# Unique id #1388291771388.0.1896967523302424\n" + "\n" + "CACHE:\n" + "# Static app files\n" + "index.html\n" + "./\n" + "\n" + "# GWT compiled files\n" + "example/7AA66459135045C9848EE97F9163D226.cache.js\n" + "example/example.nocache.js\n" + "example/bonsai%20tree.jpg\n" + "example/clear.cache.gif\n" + "\n" + "\n" + "NETWORK:\n" + "some/backend%20service.json\n" + "*\n" + "\n" + "\n" + "FALLBACK:\n" + "online%20image.png offline%20image.png\n" + "data.json nodata.json\n"; final ManifestDescriptor descriptor = ManifestDescriptor.parse( manifest ); final List<String> networkResources = descriptor.getNetworkResources(); assertEquals( networkResources.size(), 2 ); assertTrue( networkResources.contains( "*" ) ); assertTrue( networkResources.contains( "some/backend service.json" ) ); final List<String> cachedResources = descriptor.getCachedResources(); assertEquals( cachedResources.size(), 6 ); assertTrue( cachedResources.contains( "index.html" ) ); assertTrue( cachedResources.contains( "./" ) ); assertTrue( cachedResources.contains( "example/7AA66459135045C9848EE97F9163D226.cache.js" ) ); assertTrue( cachedResources.contains( "example/example.nocache.js" ) ); assertTrue( cachedResources.contains( "example/bonsai tree.jpg" ) ); assertTrue( cachedResources.contains( "example/clear.cache.gif" ) ); final Map<String, String> fallbackResources = descriptor.getFallbackResources(); assertEquals( fallbackResources.size(), 2 ); assertEquals( fallbackResources.get( "online image.png" ), "offline image.png" ); assertEquals( fallbackResources.get( "data.json" ), "nodata.json" ); }