Coverage Report - net.sourceforge.pebble.permalink.Latin1SeoPermalinkProvider
 
Classes in this File Line Coverage Branch Coverage Complexity
Latin1SeoPermalinkProvider
98%
125/127
90%
29/32
2.917
 
 1  
 /*
 2  
  * Copyright (c) 2003-2011, Simon Brown
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions are met:
 7  
  *
 8  
  *   - Redistributions of source code must retain the above copyright
 9  
  *     notice, this list of conditions and the following disclaimer.
 10  
  *
 11  
  *   - Redistributions in binary form must reproduce the above copyright
 12  
  *     notice, this list of conditions and the following disclaimer in
 13  
  *     the documentation and/or other materials provided with the
 14  
  *     distribution.
 15  
  *
 16  
  *   - Neither the name of Pebble nor the names of its contributors may
 17  
  *     be used to endorse or promote products derived from this software
 18  
  *     without specific prior written permission.
 19  
  *
 20  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 21  
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22  
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 23  
  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 24  
  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 25  
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 26  
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 27  
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 28  
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 29  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 30  
  * POSSIBILITY OF SUCH DAMAGE.
 31  
  */
 32  
 
 33  
 package net.sourceforge.pebble.permalink;
 34  
 
 35  
 import net.sourceforge.pebble.api.permalink.PermalinkProvider;
 36  
 import net.sourceforge.pebble.domain.*;
 37  
 
 38  
 import java.text.SimpleDateFormat;
 39  
 import java.util.HashMap;
 40  
 import java.util.Iterator;
 41  
 import java.util.List;
 42  
 
 43  
 /**
 44  
  * Generates permalinks based upon the blog entry title. This implementation
 45  
  * retains characters from the latin1 character by converting
 46  
  * them to suitable "url-friendly" counterparts.
 47  
  * <p/>
 48  
  * It also uses dashes instead of underscores for whitespace as this is
 49  
  * what Google recommends.
 50  
  * <p/>
 51  
  * For titles without characters from the latin1 character set
 52  
  * the blog entry ID is used for the permalink instead.
 53  
  *
 54  
  * @author Mattias Reichel
 55  
  */
 56  52
 public class Latin1SeoPermalinkProvider implements PermalinkProvider {
 57  
 
 58  
   /**
 59  
    * the regex used to check for a day request
 60  
    */
 61  
   private static final String DAY_PERMALINK_REGEX = "/\\d\\d\\d\\d/\\d\\d/\\d\\d";
 62  
 
 63  
   /**
 64  
    * the regex used to check for a monthly blog request
 65  
    */
 66  
   private static final String MONTH_PERMALINK_REGEX = "/\\d\\d\\d\\d/\\d\\d";
 67  
 
 68  
   /**
 69  
    * the regex used to check for a blog entry permalink
 70  
    */
 71  
   private static final String BLOG_ENTRY_PERMALINK_REGEX = "/[\\w-]*";
 72  
 
 73  
   /**
 74  
    * the Blog associated with this provider instance
 75  
    */
 76  
   private Blog blog;
 77  
 
 78  
   /**
 79  
    * Gets the blog associated with this provider instance.
 80  
    *
 81  
    * @return a Blog instance
 82  
    */
 83  
   public Blog getBlog() {
 84  252
     return this.blog;
 85  
   }
 86  
 
 87  
   /**
 88  
    * Sets the blog associated with this provider instance.
 89  
    *
 90  
    * @param blog a Blog instance
 91  
    */
 92  
   public void setBlog(Blog blog) {
 93  52
     this.blog = blog;
 94  52
   }
 95  
 
 96  
   /**
 97  
    * Gets the permalink for a blog entry.
 98  
    *
 99  
    * @return a URI as a String
 100  
    */
 101  
   public synchronized String getPermalink(BlogEntry blogEntry) {
 102  188
     if (blogEntry.getTitle() == null || blogEntry.getTitle().length() == 0) {
 103  8
       return buildPermalink(blogEntry);
 104  
     } else {
 105  180
       BlogService service = new BlogService();
 106  180
       List<BlogEntry> entries = getBlog().getBlogEntries();
 107  180
       int count = 0;
 108  300
       for (int i = entries.size() - 1; i >= 0 && !entries.get(i).getId().equals(blogEntry.getId()); i--) {
 109  120
         if (entries.get(i).getTitle().equals(blogEntry.getTitle())) {
 110  16
           count++;
 111  
         }
 112  
       }
 113  
 
 114  180
       if (count == 0) {
 115  168
         return buildPermalink(blogEntry);
 116  
       } else {
 117  12
         return buildPermalink(blogEntry) + "_" + blogEntry.getId();
 118  
       }
 119  
     }
 120  
   }
 121  
 
 122  
   private String buildPermalink(BlogEntry blogEntry) {
 123  188
     String title = blogEntry.getTitle();
 124  188
     if (title == null || title.length() == 0) {
 125  8
       title = "" + blogEntry.getId();
 126  
     } else {
 127  180
       title = title.toLowerCase();
 128  180
       title = title.replaceAll("[\\. ,;/\\\\_]", "-"); // Change whitespace and punctuation marks to dashes
 129  180
       for (String search : characterSubstitutions.keySet()) {
 130  11700
         title = title.replaceAll(search, characterSubstitutions.get(search));
 131  
       }
 132  180
       title = title.replaceAll("[^a-z0-9-]", "");
 133  180
       title = title.replaceAll("-+", "-");
 134  180
       title = title.replaceAll("^-*", "");
 135  180
       title = title.replaceAll("-*$", "");
 136  
     }
 137  
 
 138  
     // if the title has been blanked out, use the blog entry instead
 139  188
     if (title == null || title.length() == 0) {
 140  24
       title = "" + blogEntry.getId();
 141  
     }
 142  
 
 143  188
     return "/" + title;
 144  
   }
 145  
 
 146  
 
 147  
   public boolean isBlogEntryPermalink(String uri) {
 148  16
     if (uri != null) {
 149  12
       return uri.matches(BLOG_ENTRY_PERMALINK_REGEX);
 150  
     } else {
 151  4
       return false;
 152  
     }
 153  
   }
 154  
 
 155  
   public BlogEntry getBlogEntry(String uri) {
 156  20
     BlogService service = new BlogService();
 157  20
     Iterator it = getBlog().getBlogEntries().iterator();
 158  44
     while (it.hasNext()) {
 159  
       try {
 160  44
         BlogEntry blogEntry = service.getBlogEntry(getBlog(), "" + ((BlogEntry) it.next()).getId());
 161  
         // use the local permalink, just in case the entry has been aggregated
 162  
         // and an original permalink assigned
 163  44
         if (blogEntry.getLocalPermalink().endsWith(uri)) {
 164  20
           return blogEntry;
 165  
         }
 166  0
       } catch (BlogServiceException e) {
 167  
         // do nothing
 168  24
       }
 169  
     }
 170  
 
 171  0
     return null;
 172  
   }
 173  
 
 174  
   /**
 175  
    * Gets the permalink for a monthly blog.
 176  
    *
 177  
    * @param month a Month instance
 178  
    * @return a URI as a String
 179  
    */
 180  
   public String getPermalink(Month month) {
 181  4
     SimpleDateFormat format = new SimpleDateFormat("'/'yyyy'/'MM");
 182  4
     format.setTimeZone(blog.getTimeZone());
 183  4
     return format.format(month.getDate());
 184  
   }
 185  
 
 186  
   /**
 187  
    * Determines whether the specified URI is a monthly blog permalink.
 188  
    *
 189  
    * @param uri a relative URI
 190  
    * @return true if the URI represents a permalink to a monthly blog,
 191  
    *         false otherwise
 192  
    */
 193  
   public boolean isMonthPermalink(String uri) {
 194  20
     if (uri != null) {
 195  16
       return uri.matches(MONTH_PERMALINK_REGEX);
 196  
     } else {
 197  4
       return false;
 198  
     }
 199  
   }
 200  
 
 201  
   /**
 202  
    * Gets the monthly blog referred to by the specified URI.
 203  
    *
 204  
    * @param uri a relative URI
 205  
    * @return a Month instance, or null if one can't be found
 206  
    */
 207  
   public Month getMonth(String uri) {
 208  4
     String year = uri.substring(1, 5);
 209  4
     String month = uri.substring(6, 8);
 210  
 
 211  4
     return getBlog().getBlogForMonth(Integer.parseInt(year), Integer.parseInt(month));
 212  
   }
 213  
 
 214  
   /**
 215  
    * Gets the permalink for a day.
 216  
    *
 217  
    * @param day a Day instance
 218  
    * @return a URI as a String
 219  
    */
 220  
   public String getPermalink(Day day) {
 221  4
     SimpleDateFormat format = new SimpleDateFormat("'/'yyyy'/'MM'/'dd");
 222  4
     format.setTimeZone(blog.getTimeZone());
 223  4
     return format.format(day.getDate());
 224  
   }
 225  
 
 226  
   /**
 227  
    * Determines whether the specified URI is a day permalink.
 228  
    *
 229  
    * @param uri a relative URI
 230  
    * @return true if the URI represents a permalink to a day,
 231  
    *         false otherwise
 232  
    */
 233  
   public boolean isDayPermalink(String uri) {
 234  20
     if (uri != null) {
 235  16
       return uri.matches(DAY_PERMALINK_REGEX);
 236  
     } else {
 237  4
       return false;
 238  
     }
 239  
   }
 240  
 
 241  
   /**
 242  
    * Gets the day referred to by the specified URI.
 243  
    *
 244  
    * @param uri a relative URI
 245  
    * @return a Day instance, or null if one can't be found
 246  
    */
 247  
   public Day getDay(String uri) {
 248  4
     String year = uri.substring(1, 5);
 249  4
     String month = uri.substring(6, 8);
 250  4
     String day = uri.substring(9, 11);
 251  
 
 252  4
     return getBlog().getBlogForDay(Integer.parseInt(year),
 253  
             Integer.parseInt(month), Integer.parseInt(day));
 254  
   }
 255  
 
 256  
 
 257  
   /**
 258  
    * the List of characters that will be substituted
 259  
    */
 260  
   private static final HashMap<String, String> characterSubstitutions;
 261  
 
 262  
   static {
 263  
 
 264  4
     characterSubstitutions = new HashMap<String, String>();
 265  
 
 266  4
     characterSubstitutions.put("\u00B2", "2");
 267  4
     characterSubstitutions.put("\u00B3", "3");
 268  
 
 269  4
     characterSubstitutions.put("\u00C0", "A");
 270  4
     characterSubstitutions.put("\u00C1", "A");
 271  4
     characterSubstitutions.put("\u00C2", "A");
 272  4
     characterSubstitutions.put("\u00C3", "A");
 273  4
     characterSubstitutions.put("\u00C4", "A");
 274  4
     characterSubstitutions.put("\u00C5", "A");
 275  4
     characterSubstitutions.put("\u00C6", "AE");
 276  4
     characterSubstitutions.put("\u00C7", "C");
 277  4
     characterSubstitutions.put("\u00C8", "E");
 278  4
     characterSubstitutions.put("\u00C9", "E");
 279  4
     characterSubstitutions.put("\u00CA", "E");
 280  4
     characterSubstitutions.put("\u00CB", "E");
 281  4
     characterSubstitutions.put("\u00CC", "I");
 282  4
     characterSubstitutions.put("\u00CD", "I");
 283  4
     characterSubstitutions.put("\u00CE", "I");
 284  4
     characterSubstitutions.put("\u00CF", "I");
 285  
 
 286  4
     characterSubstitutions.put("\u00D0", "D");
 287  4
     characterSubstitutions.put("\u00D1", "N");
 288  4
     characterSubstitutions.put("\u00D2", "O");
 289  4
     characterSubstitutions.put("\u00D3", "O");
 290  4
     characterSubstitutions.put("\u00D4", "O");
 291  4
     characterSubstitutions.put("\u00D5", "O");
 292  4
     characterSubstitutions.put("\u00D6", "O");
 293  4
     characterSubstitutions.put("\u00D7", "x");
 294  4
     characterSubstitutions.put("\u00D8", "O");
 295  4
     characterSubstitutions.put("\u00D9", "U");
 296  4
     characterSubstitutions.put("\u00DA", "U");
 297  4
     characterSubstitutions.put("\u00DB", "U");
 298  4
     characterSubstitutions.put("\u00DC", "U");
 299  4
     characterSubstitutions.put("\u00DD", "Y");
 300  4
     characterSubstitutions.put("\u00DE", "P");
 301  4
     characterSubstitutions.put("\u00DF", "ss");
 302  
 
 303  4
     characterSubstitutions.put("\u00E0", "a");
 304  4
     characterSubstitutions.put("\u00E1", "a");
 305  4
     characterSubstitutions.put("\u00E2", "a");
 306  4
     characterSubstitutions.put("\u00E3", "a");
 307  4
     characterSubstitutions.put("\u00E4", "a");
 308  4
     characterSubstitutions.put("\u00E5", "a");
 309  4
     characterSubstitutions.put("\u00E6", "ae");
 310  4
     characterSubstitutions.put("\u00E7", "c");
 311  4
     characterSubstitutions.put("\u00E8", "e");
 312  4
     characterSubstitutions.put("\u00E9", "e");
 313  4
     characterSubstitutions.put("\u00EA", "e");
 314  4
     characterSubstitutions.put("\u00EB", "e");
 315  4
     characterSubstitutions.put("\u00EC", "i");
 316  4
     characterSubstitutions.put("\u00ED", "i");
 317  4
     characterSubstitutions.put("\u00EE", "i");
 318  4
     characterSubstitutions.put("\u00EF", "i");
 319  
 
 320  4
     characterSubstitutions.put("\u00F0", "d");
 321  4
     characterSubstitutions.put("\u00F1", "n");
 322  4
     characterSubstitutions.put("\u00F2", "o");
 323  4
     characterSubstitutions.put("\u00F3", "o");
 324  4
     characterSubstitutions.put("\u00F4", "o");
 325  4
     characterSubstitutions.put("\u00F5", "o");
 326  4
     characterSubstitutions.put("\u00F6", "o");
 327  
     //"\u00F7", // division sign (ignore)
 328  4
     characterSubstitutions.put("\u00F8", "o");
 329  4
     characterSubstitutions.put("\u00F9", "u");
 330  4
     characterSubstitutions.put("\u00FA", "u");
 331  4
     characterSubstitutions.put("\u00FB", "u");
 332  4
     characterSubstitutions.put("\u00FC", "u");
 333  4
     characterSubstitutions.put("\u00FD", "y");
 334  4
     characterSubstitutions.put("\u00FE", "p");
 335  4
     characterSubstitutions.put("\u00FF", "y");
 336  4
   }
 337  
 }