gt: 	protected void cacheMetadata() {
context: package bookshelf.apis.google;
  
  import java.awt.Image;
  import java.awt.Toolkit;
  import java.awt.image.BufferedImage;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.CookieHandler;
  import java.net.CookieManager;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import javax.imageio.ImageIO;
  
  import net.htmlparser.jericho.Element;
  import net.htmlparser.jericho.HTMLElementName;
  import net.htmlparser.jericho.Source;
  import bookshelf.AbstractBook;
  import bookshelf.ISBN;
  import bookshelf.Keyword;
  
  
  public class GoogleBook extends AbstractBook {
  	private static final long serialVersionUID = 1L;
  	private String summary;
  	private float rating = -1f;
  	private ArrayList<String> subtitles = new ArrayList<String>();
  	private ArrayList<String> pageIds = new ArrayList<String>();
  	private ArrayList<Keyword> keywords = new ArrayList<Keyword>();
  	private boolean cachedSummary;
  	private boolean cachedRating;
  	private boolean cachedTitle;
  	private boolean cachedSubtitles;
  	private boolean cachedKeywords;
  	private boolean cachedPageIds;
  	
  	public GoogleBook(String url) throws IOException {
  		CookieHandler.setDefault(new CookieManager());
  		HttpURLConnection bookCon = (HttpURLConnection)
  				(new URL(url)).openConnection();
  		bookCon.addRequestProperty("User-Agent", "Mozilla/5.0");
  		setSource(new Source(bookCon));
  		this.cacheMetadata();
  	}
  	
  	public GoogleBook(ISBN isbn) throws IOException {
  		this("http://books.google.com/books?isbn=" + isbn);
  	}
  	
  	private void addSubtitle(String subtitle) {
  		this.subtitles.add(subtitle);
  	}
  	
  	private void addKeyword(Keyword keyword) {
  		this.keywords.add(keyword);
  	}
  	
  	public URL getCoverUrl() throws IOException {
  		return new URL("http://books.google.com/books?vid=ISBN"+getISBN()+"&printsec=frontcover&img=1&zoom=1");
  	}
  	
  	/**
  	 * If you use this function you should present Google branding with the pages!
  	 * @return
  	 * @throws IOException
  	 */
  	public ArrayList<String> getPageIds() throws IOException {
  		if (!hasCachedPageIds()) {
  			ArrayList<String> result = new ArrayList<String>();
  			
  			// Get the page id's
  			String url = "http://books.google.com/books?vid=ISBN"+getISBN()+"&pg=0&jscmd=click3";
  			HttpURLConnection pageCon = (HttpURLConnection)
  					(new URL(url)).openConnection();
  			pageCon.addRequestProperty("User-Agent", "Mozilla/5.0");
  			Source pageSource = new Source(pageCon);
  			
  			Pattern pattern = Pattern.compile("\\{\"pid\":\"([^\"]+)\"\\}");
  			Matcher matcher = pattern.matcher(pageSource.getTextExtractor().setIncludeAttributes(false).toString());
  	
  			while (matcher.find()) {
  				result.add(matcher.group(1));
  			}
  
  			setPageIds(result);
  			setCachedPageIds(true);
  		}
  		
  		return this.pageIds;
  	}
  	
  	public boolean hasPagePreviews() {
  		return getSource().getElementById("preview-link") != null;
  	}
  	
  	public Image getPage(String pageId) throws IOException {
  		HttpURLConnection pageCon = (HttpURLConnection)
  			getPageUrl(pageId).openConnection();
  		pageCon.addRequestProperty("User-Agent", "Mozilla/5.0");
  		pageCon.connect();
  		
  		InputStream urlStream = pageCon.getInputStream();
          BufferedImage page = ImageIO.read(urlStream);
  
  	    Image result = Toolkit.getDefaultToolkit().createImage(page.getSource());
  	    urlStream.close();
  	    pageCon.disconnect();
  
  		return result;
  	}
  	
  	public URL getPageUrl(String pageId) throws IOException {
  		// Get the page id's
  		String url = "http://books.google.com/books?vid=ISBN"+getISBN()+"&pg="+pageId+"&jscmd=click3";
  		HttpURLConnection pageCon = (HttpURLConnection)
  				(new URL(url)).openConnection();
  		pageCon.addRequestProperty("User-agent", "Mozilla/5.0");
  		Source pageSource = new Source(pageCon);
  		
  		Pattern pattern = Pattern.compile("\"pid\":\"([^\"]+)\",\"src\":\"([^\"]+)\"");
  		Matcher matcher = pattern.matcher(pageSource.toString());
  
  		matcher.find();
  		String result = matcher.group(2);
  		result = result.replaceAll("(\\\\u0026)", "&");
  		return new URL(result);
  	}
  	
  	public String getSummary() {
  		if (!hasCachedSummary()) {
  			Element element = getSource().getElementById("synopsistext");
  			
  			if (element != null) {
  				setSummary(element.getTextExtractor().setIncludeAttributes(false).toString());
  			}
  			
  			setCachedSummary(true);
  		}
  		
  		return this.summary;
  	}
  
  	public String getTitle() {
  		if (!hasCachedTitle()) {
  			List<Element> elements = getSource().getAllElementsByClass("booktitle");
  			for (Element e : elements) {
  				e = e.getFirstElement();
  				setTitle(e.getTextExtractor().setIncludeAttributes(false).toString());
  			}
  			
  			setCachedTitle(true);
  		}
  		
  		return this.title;
  	}
  
  	public ArrayList<String> getSubtitles() {
  		if (!hasCachedSubtitles()) {
  			List<Element> elements = getSource().getAllElementsByClass("subtitle");
  			for (Element e : elements)
  				addSubtitle(e.getTextExtractor().setIncludeAttributes(false).toString());
  			
  			setCachedSubtitles(true);
  		}
  		
  		return new ArrayList<String>(this.subtitles);
  	}
  
  	public ArrayList<Keyword> getKeywords() {
  		if (!hasCachedWords()) {
  		
  			Element element = getSource().getFirstElementByClass("cloud");
  			if (element != null) {
  				List<Element> elements = element.getAllElements(HTMLElementName.A);
  				for (Element e : elements) {
  					String classValue = e.getAttributeValue("class");
  					int length = classValue.length();
  					double nbr = Integer.parseInt(classValue.substring(length-1, length));
  					String value = e.getTextExtractor().setIncludeAttributes(false).toString();
  					addKeyword(new Keyword(value, 1d/(nbr+1d)));
  				}
  			}
  			
  			setCachedKeywords(true);
  		}
  		
  		return this.keywords;
  	}
  	
  	public float getRating() {
  		if (!hasCachedRating()) {
  			List<Element> starOn = getSource().getAllElementsByClass("gb-star-on");
  			List<Element> starHalf = getSource().getAllElementsByClass("gb-star-half");
  			
  			if (starOn.size() > 0 || starHalf.size() > 0)
  				setRating(1f*starOn.size() + 0.5f*starHalf.size());
  			
  			setCachedRating(true);
  		}
  		
  		return this.rating;
  	}
  	
  