SpriteDirectiveOccurrenceCollector.java
/*
* SmartSprites Project
*
* Copyright (C) 2007-2009, Stanisław Osiński.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* - Neither the name of the SmartSprites Project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* - We kindly request that you include in the end-user documentation provided with
* the redistribution and/or in the software itself an acknowledgement equivalent
* to the following: "This product includes software developed by the SmartSprites
* Project."
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.carrot2.labs.smartsprites;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.carrot2.labs.smartsprites.css.CssProperty;
import org.carrot2.labs.smartsprites.css.CssSyntaxUtils;
import org.carrot2.labs.smartsprites.message.Message.MessageType;
import org.carrot2.labs.smartsprites.message.MessageLog;
import org.carrot2.labs.smartsprites.resource.ResourceHandler;
/**
* Methods for collecting SmartSprites directives from CSS files.
*/
public class SpriteDirectiveOccurrenceCollector {
/** A regular expression for extracting sprite image directives. */
private static final Pattern SPRITE_IMAGE_DIRECTIVE = Pattern.compile("/\\*+\\s+(sprite:[^*]*)\\*+/");
/** A regular expression for extracting sprite reference directives. */
private static final Pattern SPRITE_REFERENCE_DIRECTIVE = Pattern.compile("/\\*+\\s+(sprite-ref:[^*]*)\\*+/");
/** This builder's message log. */
private final MessageLog messageLog;
/** The resource handler. */
private final ResourceHandler resourceHandler;
/**
* Creates a {@link SpriteDirectiveOccurrenceCollector} with the provided parameters and log.
*
* @param messageLog
* the message log
* @param resourceHandler
* the resource handler
*/
SpriteDirectiveOccurrenceCollector(MessageLog messageLog, ResourceHandler resourceHandler) {
this.resourceHandler = resourceHandler;
this.messageLog = messageLog;
}
/**
* Collects {@link SpriteImageOccurrence}s from a single CSS file.
*
* @param cssFile
* the css file
*
* @return the collection
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
Collection<SpriteImageOccurrence> collectSpriteImageOccurrences(String cssFile) throws IOException {
final Collection<SpriteImageOccurrence> occurrences = new ArrayList<>();
messageLog.setCssFile(null);
messageLog.info(MessageType.READING_SPRITE_IMAGE_DIRECTIVES, cssFile);
messageLog.setCssFile(cssFile);
int lineNumber = -1;
String line;
try (BufferedReader reader = new BufferedReader(resourceHandler.getResourceAsReader(cssFile))) {
while ((line = reader.readLine()) != null) {
lineNumber++;
messageLog.setLine(lineNumber);
final String spriteImageDirectiveString = extractSpriteImageDirectiveString(line);
if (spriteImageDirectiveString == null) {
continue;
}
final SpriteImageDirective directive = SpriteImageDirective.parse(spriteImageDirectiveString,
messageLog);
if (directive == null) {
continue;
}
occurrences.add(new SpriteImageOccurrence(directive, cssFile, lineNumber));
}
}
return occurrences;
}
/**
* Collects {@link SpriteReferenceOccurrence}s from a single CSS file.
*
* @param cssFile
* the css file
* @param spriteImageDirectives
* the sprite image directives
*
* @return the collection
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
Collection<SpriteReferenceOccurrence> collectSpriteReferenceOccurrences(String cssFile,
Map<String, SpriteImageDirective> spriteImageDirectives) throws IOException {
final Collection<SpriteReferenceOccurrence> directives = new ArrayList<>();
messageLog.setCssFile(null);
messageLog.info(MessageType.READING_SPRITE_REFERENCE_DIRECTIVES, cssFile);
messageLog.setCssFile(cssFile);
int lineNumber = -1;
String line;
try (BufferedReader reader = new BufferedReader(resourceHandler.getResourceAsReader(cssFile))) {
while ((line = reader.readLine()) != null) {
lineNumber++;
messageLog.setLine(lineNumber);
final String directiveString = extractSpriteReferenceDirectiveString(line);
if (directiveString == null) {
continue;
}
final CssProperty backgroundProperty = extractSpriteReferenceCssProperty(line);
final String imageUrl = CssSyntaxUtils.unpackUrl(backgroundProperty.value, messageLog);
if (imageUrl == null) {
continue;
}
final SpriteReferenceDirective directive = SpriteReferenceDirective.parse(directiveString,
spriteImageDirectives, messageLog);
if (directive == null) {
continue;
}
directives.add(new SpriteReferenceOccurrence(directive, imageUrl, cssFile, lineNumber,
backgroundProperty.important));
}
}
return directives;
}
/**
* Collects {@link SpriteImageOccurrence}s from the provided CSS files.
*
* @param filePaths
* the file paths
*
* @return the multimap
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
Multimap<String, SpriteImageOccurrence> collectSpriteImageOccurrences(Collection<String> filePaths)
throws IOException {
final Multimap<String, SpriteImageOccurrence> spriteImageOccurrencesByFile = LinkedListMultimap.create();
for (final String cssFile : filePaths) {
messageLog.setCssFile(cssFile);
final Collection<SpriteImageOccurrence> spriteImageOccurrences = collectSpriteImageOccurrences(cssFile);
spriteImageOccurrencesByFile.putAll(cssFile, spriteImageOccurrences);
}
return spriteImageOccurrencesByFile;
}
/**
* Collects {@link SpriteReferenceOccurrence}s from the provided CSS files.
*
* @param files
* the files
* @param spriteImageDirectivesBySpriteId
* the sprite image directives by sprite id
*
* @return the multimap
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
Multimap<String, SpriteReferenceOccurrence> collectSpriteReferenceOccurrences(Collection<String> files,
final Map<String, SpriteImageDirective> spriteImageDirectivesBySpriteId) throws IOException {
final Multimap<String, SpriteReferenceOccurrence> spriteEntriesByFile = LinkedListMultimap.create();
for (final String cssFile : files) {
messageLog.setCssFile(cssFile);
final Collection<SpriteReferenceOccurrence> spriteReferenceOccurrences = collectSpriteReferenceOccurrences(
cssFile, spriteImageDirectivesBySpriteId);
spriteEntriesByFile.putAll(cssFile, spriteReferenceOccurrences);
}
return spriteEntriesByFile;
}
/**
* Groups {@link SpriteImageDirective}s by sprite id.
*
* @param spriteImageOccurrencesByFile
* the sprite image occurrences by file
*
* @return the map
*/
Map<String, SpriteImageOccurrence> mergeSpriteImageOccurrences(
final Multimap<String, SpriteImageOccurrence> spriteImageOccurrencesByFile) {
final Map<String, SpriteImageOccurrence> spriteImageDirectivesBySpriteId = new LinkedHashMap<>();
for (final Map.Entry<String, SpriteImageOccurrence> entry : spriteImageOccurrencesByFile.entries()) {
final String cssFile = entry.getKey();
final SpriteImageOccurrence spriteImageOccurrence = entry.getValue();
messageLog.setCssFile(cssFile);
// Add to the global map, checking for duplicates
if (spriteImageDirectivesBySpriteId.containsKey(spriteImageOccurrence.spriteImageDirective.spriteId)) {
messageLog.warning(MessageType.IGNORING_SPRITE_IMAGE_REDEFINITION);
} else {
spriteImageDirectivesBySpriteId.put(spriteImageOccurrence.spriteImageDirective.spriteId,
spriteImageOccurrence);
}
}
return spriteImageDirectivesBySpriteId;
}
/**
* Groups {@link SpriteReferenceOccurrence}s by sprite id.
*
* @param spriteEntriesByFile
* the sprite entries by file
*
* @return the multimap
*/
static Multimap<String, SpriteReferenceOccurrence> mergeSpriteReferenceOccurrences(
final Multimap<String, SpriteReferenceOccurrence> spriteEntriesByFile) {
final Multimap<String, SpriteReferenceOccurrence> spriteReferenceOccurrencesBySpriteId = LinkedListMultimap
.create();
for (final SpriteReferenceOccurrence spriteReferenceOccurrence : spriteEntriesByFile.values()) {
spriteReferenceOccurrencesBySpriteId.put(spriteReferenceOccurrence.spriteReferenceDirective.spriteRef,
spriteReferenceOccurrence);
}
return spriteReferenceOccurrencesBySpriteId;
}
/**
* Extract the sprite image directive string to be parsed.
*
* @param cssLine
* the css line
*
* @return the string
*/
static String extractSpriteImageDirectiveString(String cssLine) {
final Matcher matcher = SPRITE_IMAGE_DIRECTIVE.matcher(cssLine);
if (matcher.find()) {
return matcher.group(1).trim();
}
return null;
}
/**
* Extract the sprite reference directive string to be parsed.
*
* @param css
* the css
*
* @return the string
*/
static String extractSpriteReferenceDirectiveString(String css) {
final Matcher matcher = SPRITE_REFERENCE_DIRECTIVE.matcher(css);
if (matcher.find()) {
return matcher.group(1).trim();
}
return null;
}
/**
* Extract the url to the image to be added to a sprite.
*
* @param css
* the css
*
* @return the css property
*/
CssProperty extractSpriteReferenceCssProperty(String css) {
final Matcher matcher = SPRITE_REFERENCE_DIRECTIVE.matcher(css);
// Remove the directive
final String noDirective = matcher.replaceAll("").trim();
final Collection<CssProperty> rules = CssSyntaxUtils.extractProperties(noDirective);
if (rules.isEmpty()) {
messageLog.warning(MessageType.NO_BACKGROUND_IMAGE_RULE_NEXT_TO_SPRITE_REFERENCE_DIRECTIVE, css);
return null;
}
if (rules.size() > 1) {
messageLog.warning(MessageType.MORE_THAN_ONE_RULE_NEXT_TO_SPRITE_REFERENCE_DIRECTIVE, css);
return null;
}
final CssProperty backgroundImageRule = rules.iterator().next();
if (!backgroundImageRule.rule.equals("background-image")) {
messageLog.warning(MessageType.NO_BACKGROUND_IMAGE_RULE_NEXT_TO_SPRITE_REFERENCE_DIRECTIVE, css);
return null;
}
return backgroundImageRule;
}
}