Package org.apache.myfaces.trinidadinternal.style.cache

Source Code of org.apache.myfaces.trinidadinternal.style.cache.FileSystemStyleCache$Key

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.apache.myfaces.trinidadinternal.style.cache;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.context.AccessibilityProfile;
import org.apache.myfaces.trinidad.context.LocaleContext;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidad.skin.Skin;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.renderkit.core.CoreRenderKit;
import org.apache.myfaces.trinidadinternal.renderkit.core.CoreRenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.StyleSheetRenderer;
import org.apache.myfaces.trinidadinternal.share.io.CachingNameResolver;
import org.apache.myfaces.trinidadinternal.share.io.DefaultNameResolver;
import org.apache.myfaces.trinidadinternal.share.io.InputStreamProvider;
import org.apache.myfaces.trinidadinternal.share.io.NameResolver;
import org.apache.myfaces.trinidadinternal.share.xml.JaxpXMLProvider;
import org.apache.myfaces.trinidadinternal.share.xml.XMLProvider;
import org.apache.myfaces.trinidadinternal.style.CSSStyle;
import org.apache.myfaces.trinidadinternal.style.Style;
import org.apache.myfaces.trinidadinternal.style.StyleContext;
import org.apache.myfaces.trinidadinternal.style.StyleMap;
import org.apache.myfaces.trinidadinternal.style.StyleProvider;
import org.apache.myfaces.trinidadinternal.style.util.CSSGenerationUtils;
import org.apache.myfaces.trinidadinternal.style.util.NameUtils;
import org.apache.myfaces.trinidadinternal.style.xml.StyleSheetDocumentUtils;
import org.apache.myfaces.trinidadinternal.style.xml.parse.IconNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.PropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.SkinPropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetDocument;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetNode;
import org.apache.myfaces.trinidadinternal.util.nls.LocaleUtils;
import org.xml.sax.SAXException;

/**
* The FileSystemStyleCache is a StyleProvider implementation which
* caches generated CSS style sheets on the file system.
*
* Note that StyleProviders are responsible for providing access
* both to style information (eg. getStyleSheetURI(), getStyleMap()) as
* well as to icons registered via style sheets (see getIcons()).
*
* @see org.apache.myfaces.trinidadinternal.style.StyleProvider
* @see org.apache.myfaces.trinidadinternal.skin.SkinStyleProvider
*
* @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/style/cache/FileSystemStyleCache.java#0 $) $Date: 10-nov-2005.18:58:54 $
*/
// -= Simon Lessard =-
// TODO: Synchronization does not seem to be needed since there's
//       synchronized blocks in the code, using HashMap hence
//       looks like a better choice than Hashtable.
public class FileSystemStyleCache implements StyleProvider
{
  /**
   * Returns the mime type for the styles provided by this
   * FileSystemStyleCache - "text/css".
   */
  public String getContentStyleType(StyleContext context)
  {
    return "text/css";
  }


  /**
   * Creates a FileSystemStyleCache.
   *
   * @param source The path of the source XSS document.  The
   *   specified file must be a valid XSS document.  If the specified
   *   file does not exists, an IllegalArgumentException is thrown.
   * @param target The path of the target directory.  Generated
   *   CSS files are stored in this directory.  If the directory
   *   does not exist and can not be created, an IllegalArgumentException
   *   is thrown.
   */
  protected FileSystemStyleCache(String source, String target)
  {
    // The source arg may actually be null if we are using a
    // SkinStyleProvider with no custom style sheet.
    if (source != null)
    {
      File sourceFile = new File(source);

      if (!sourceFile.exists())
        throw new IllegalArgumentException(
        "\nRequired XSS file " +
        source +
        " does not exist.");
      _sourceFile = sourceFile;

      // Get the base file name: name minus the extension
      String baseName = sourceFile.getName();
      int dotIndex = baseName.indexOf('.');
      if (dotIndex != -1)
        baseName = baseName.substring(0, dotIndex);
      _baseName = baseName;
    }

    // If the target directory does not exist, create it now.
    // Note: If we can't create the target directory, we just
    // plug along anyway instead of throwing an IllegalArgumentException.
    // That way, we can still use the StyleMap for this style sheet
    // even if the style sheet isn't generated.
    File targetDirectory = new File(target);
    if (!targetDirectory.exists())
      targetDirectory.mkdirs();

    _targetPath = target;
  }

  /**
   * Implementation of StyleCache.getStyleSheetURI().
   */
  public String getStyleSheetURI(StyleContext context)
  {

    Entry entry = _getEntry(context);

    if (entry == null)
      return null;

    return entry.uri;
  }

  /**
   * Implementation of StyleProvider.getStyleMap().
   */
  public StyleMap getStyleMap(StyleContext context)
  {

    Entry entry = _getEntry(context);

    if (entry == null)
      return null;

    return entry.map;
  }
 
  /**
   * Implementation of StyleProvider.getSkinProperties()
   */
  public ConcurrentMap<Object, Object> getSkinProperties(StyleContext context)
  {

    Entry entry = _getEntry(context);

    if (entry == null)
      return null;

    return entry.skinProperties;
  }
 
  /**
   * Implementation of StyleProvider.getIcons()
   */
  public ConcurrentMap<String, Icon> getIcons(StyleContext context)
  {

    Entry entry = _getEntry(context);

    if (entry == null)
      return null;

    return entry.icons;
  }

  /**
   * Returns a Map which maps style class names to
   * equivalent shorter names.
   * <p>
   * FileSystemStyleCache automatically generates short versions
   * of every style class that is found the the underlying XSS
   * document.  FileSystemStyleCache clients can reduce the
   * size of generated content by using this method to obtain
   * short versions of any rendered style classes.
   * <p>
   * Note: The returned Map uses String keys to represent
   * the full class names.  However, the short style class values
   * may not necessarily be type java.lang.String.  Clients must
   * avoid explicitly casting the values contained in the Map
   * to type String.  Instead, such values should be passed directly
   * to the ResponseWriter API to be rendered.  Or, if the String
   * representation is required, toString() should be called on
   * the value.
   *
   * @param context The StyleContext
   *
   * @return A Map which maps the full style class names to
   *   the shorter equivalents.
   */
  public Map<String, String> getShortStyleClasses(StyleContext context)
  {
    return _shortStyleClassMap;
  }

  /**
   * Creates the StyleSheetDocument for this StyleProvider.
   * @param context The StyleContext
   *                (not needed here, but is needed in  subclass)
   * @return The StyleSheetDocument which defines the styles
   *         for this StyleProvider.
   */
  protected StyleSheetDocument createStyleSheetDocument(
    StyleContext context
    )
  {
    // If we don't have a sourceFile, we don't have a StyleSheetDocument
    if (_sourceFile == null)
      return null;

    StyleSheetDocument document = null;

    // Get the XML Provider
    XMLProvider provider = new JaxpXMLProvider();

    try
    {
      document = StyleSheetDocumentUtils.createStyleSheetDocument(
                                                        provider,
                                                        _resolver,
                                                        _sourceFile.getPath());
    }
    catch (SAXException e)
    {
      // The error is logged by the TreeBuilder
      ;
    }
    catch (IOException e)
    {
      if (_LOG.isWarning())
        _LOG.warning("IOEXCEPTION_IN_PHASE", _sourceFile);
        _LOG.warning(e);
    }

    return document;
  }

  /**
   * Tests whether the source style sheet files have been modified
   * since the last call to createStyleSheetDocument().
   * @return true if the underlying source style sheets have been
   *              modified, false otherwise.
   */
  protected boolean hasSourceDocumentChanged(StyleContext context)
  {
    // If we haven't parsed yet, don't bother checking the time stamp
    if (_document == null)
      return true;

    InputStreamProvider provider = _getInputStreamProvider();
    if (provider != null)
      return provider.hasSourceChanged();

    // Couldn't get an InputStreamProvider, what to do?
    return false;
  }

  /**
   * Returns the name to use for the generated style sheet file .
   *
   * @param context The StyleContext
   * @param document The StyleSheetDocument which provides the styles
   */
  protected String getTargetStyleSheetName(
    StyleContext       context,
    StyleSheetDocument document
    )
  {
    StringBuffer buffer = new StringBuffer();

    String baseName = _baseName;
    if (baseName != null)
      buffer.append(baseName);

    String contextName = NameUtils.getContextName(context, document);
    if ((contextName != null) && contextName.length() > 0)
    {
      if (baseName != null)
        buffer.append(_NAME_SEPARATOR);

      buffer.append(contextName);
    }
    if (_isCompressStyles(null))
    {
      if (baseName != null || contextName != null)
        buffer.append(_NAME_SEPARATOR);
      buffer.append(_COMPRESSED);
    }

    buffer.append(_CSS_EXTENSION);

    return buffer.toString();
  }
 
  // Returns the current StyleSheetDocument - used by StyleMapImpl only
  StyleSheetDocument __getStyleSheetDocument()
  {
    return _document;
  }

  // Gets the entry for the specified StyleContext, creating it if
  // necessary. Part of creating an entry is creating the style sheet file itself.
  // And Entry contains the style sheet URI.
  private Entry _getEntry(StyleContext context)
  {
    Hashtable<Key, Entry> cache = null;
    Hashtable<Object, Entry> entryCache = null;
    StyleSheetDocument document = null;
    Map<String, String> shortStyleClassMap = null;
    String[] namespacePrefixes = null;

    boolean checkModified  = context.checkStylesModified();

    // Synchronize while set up the _cache, _entryCache, _document, etc...
    synchronized (this)
    {
      // Before we do anything, set up the NameResolver and
      // InputStreamProvider.   _getEntry() is the single entry point
      // through which all calls into the FileSystemStyleCache flow.
      // So, by initializing the NameResolver/InputStreamProvider
      // here, we know they will always be available to other code.
      _initResolver();

      // If checking for modified files, then check to see if the XSS or CSS
      // document has been modified.  If so, we dump our in-memory style cache.
      if (checkModified && hasSourceDocumentChanged(context))
      {
        _cache = null;
        _entryCache = null;
        _document = null;
        _shortStyleClassMap = null;
        _namespacePrefixes  = null;
      }

      // We get references to our two caches (the "normal" cache,
      // and the cache of shared Entry objects) up front.  We do
      // this because the actual caches could change at any time.
      // (The caches get reallocated if the source document is
      // modified.)  We need to use a consistent set of caches
      // throughout the entire request, to avoid adding bogus entries
      // to a new re-allocated cache.
      // Note: It would probably make sense to use Map for the
      // cache type in our vars and method prototypes.  We explicitly
      // use Hashtable, because our implementation relies on the
      // synchronization provided by Hashtable.  If we change the
      // cache data structure, we might need to re-code to add
      // synchronization.  Thus the somewhat ugly explicit references
      // to Hashtable everywhere.
      if (_cache == null)
        _cache = new Hashtable<Key, Entry>();
      if (_entryCache == null)
        _entryCache = new Hashtable<Object, Entry>(19);

      cache = _cache;
      entryCache = _entryCache;

      // Get the document up front too.
      // Returns the StyleSheetDocument, parsing the source file if necessary
      // this sets up _shortStyleClassMap and _namespacePrefixes
      document = _getStyleSheetDocument(context);
      if (document == null)
        return null;

      shortStyleClassMap = _shortStyleClassMap;
      namespacePrefixes = _namespacePrefixes;
    }

    // Look up the style sheet
    // The Key class is a private static class that is used for hashing. It implements
    // hashCode and equals which are based on locale, direction, browser, version, platform.
    Key key = new Key(context);
    Entry entry = _getEntry(cache, key, checkModified);
    if (entry != null)
      return entry;

    // Next see if this is an entry which is compatible with this request
    entry = _getCompatibleEntry( context,
                                 document,
                                 cache,
                                 key,
                                 entryCache,
                                 checkModified);
    if (entry != null)
      return entry;

    // If we didn't find an entry in the cache, create a new entry
    // This generates the CSS file.
    return _createEntry(context,
                        document,
                        cache,
                        key,
                        entryCache,
                        shortStyleClassMap,
                        namespacePrefixes,
                        checkModified);
  }

  // Returns the cache entry with the specified key
  private Entry _getEntry(
    Hashtable<Key, Entry> cache,
    Key                   key,
    boolean               checkModified)
  {
    Entry entry = cache.get(key);
    if (entry == null)
      return null;

    // Make sure the entry's file exists.  If it no longer
    // exists, we remove the entry from the cache
    if (checkModified &&
         (entry.uri != null) &&
         !(new File(_targetPath, entry.uri).exists()))
    {
      synchronized (cache)
      {
        if (cache.get(key) == entry)
          cache.remove(key);
      }

      return null;
    }

    return entry;
  }

  // Creates and caches an Entry for the specified StyleContext
  // This generates a style sheet for the specific StyleContext
  // (locale, direction, etc), and puts that style sheet's uri in the Entry.
  // It also caches it in the "normal" cache (the one that is based on the StyleContext),
  // and the entry cache (the one that is based on the StyleSheetNodes)
  private Entry _createEntry(
    StyleContext             context,
    StyleSheetDocument       document,
    Hashtable<Key, Entry>    cache,
    Key                      key,
    Hashtable<Object, Entry> entryCache,
    Map<String, String>      shortStyleClassMap,
    String[]                 namespacePrefixes,
    boolean                  checkModified
    )
  {
    // Next, get the fully resolved styles for this context. This will be
    // those StyleNodes that match the locale, direction, browser, etc -- the
    // info that is in the StyleContext.
    StyleNode[] styles = _getStyleContextResolvedStyles(context, document);
    if (styles == null)
      return null;

    // Generate the style sheet file, if it isn't already generated,
    // and return the uri.
    String uri = _createStyleSheetFile(context,
                                       document,
                                       styles,
                                       shortStyleClassMap,
                                       namespacePrefixes,
                                       checkModified);

    _LOG.fine("Finished processing stylesheet {0}", uri);
   
   
    // Next, get the fully resolved icons and skin properties for this context.
    // This will be those Icons and Skin Properties that match the locale, direction,
    // browser, etc -- the info that is in the StyleContext
    ConcurrentMap<String, Icon> icons =
      _getStyleContextResolvedIcons(context, document);
    ConcurrentMap<Object, Object> skinProperties =
      _getStyleContextResolvedSkinProperties(context, document);

    // Create a new entry and cache it in the "normal" cache. The "normal" cache is one
    // where the key is the Key object which is built based on information from the StyleContext,
    // like browser, agent, locale, direction.
    Entry entry = new Entry(uri, new StyleMapImpl(), icons, skinProperties);
    cache.put(key, entry);

    // Also, cache the new entry in the entry cache
    DerivationKey derivationKey = _getDerivationKey(context, document);
    entryCache.put(derivationKey, entry);

    return entry;
  }

  // Look in the entry cache for a compatible entry.
  // A compatible entry is one with the same DerivationKey, which is essentially the
  // same StyleSheetNodes.
  private Entry _getCompatibleEntry(
    StyleContext             context,
    StyleSheetDocument       document,
    Hashtable<Key, Entry>    cache,
    Key                      key,
    Hashtable<Object, Entry> entryCache,
    boolean                  checkModified
    )
  {
    DerivationKey derivationKey = _getDerivationKey(context, document);
    Entry entry = entryCache.get(derivationKey);
    if (entry == null)
      return null;

    // Make sure the entry's file exists.  If the file no
    // longer exists, we remove the entry from the entry cache.
    if (checkModified &&
         (entry.uri != null) &&
         !(new File(_targetPath, entry.uri).exists()))
    {
      synchronized (this)
      {
        if (entryCache.get(derivationKey) == entry)
          entryCache.remove(derivationKey);
      }

      return null;
    }

    // If we've got a compatible entry, cache it
    cache.put(key, entry);

    return entry;
  }

  // Returns the derivation key that would be used for entries
  // based on the provided context
  private DerivationKey _getDerivationKey(
    StyleContext context,
    StyleSheetDocument document
    )
  {
    // Entries with the same style sheet derivation are compatible.
    // Get the style sheet derivation list.
    Iterator<StyleSheetNode> e = document.getStyleSheets(context);
    // -= Simon Lessard =-
    // TODO: Check if synchronization is truly required
    Vector<StyleSheetNode> v = _copyIterator(e);
    StyleSheetNode[] styleSheets;
    if (v == null)
    {
      styleSheets = new StyleSheetNode[0];
    }
    else
    {
      styleSheets= new StyleSheetNode[v.size()];
      v.copyInto(styleSheets);
    }

    // Create a key out of the style sheet derivation list
    return new DerivationKey(context, styleSheets);
  }

  // Returns the StyleSheetDocument, parsing the source file if necessary
  private StyleSheetDocument _getStyleSheetDocument(StyleContext context)
  {
    StyleSheetDocument document = _document;

    // If we have a StyleSheetDocument already, just return it.
    if (document != null)
      return document;

    // Otherwise, we create the StyleSheetDocument now
    document = createStyleSheetDocument(context);

    // If we weren't able to create the StyleSheetDocument,
    // use a non-null placeholder
    if (document == null)
      document = _EMPTY_DOCUMENT;

    // Save the document
    if (_document == null)
      _document = document;

    // Re-initialize our Array of namespace prefixes that are in the selectors
    // Re-initialize our Map of short style class names
    _namespacePrefixes = _getNamespacePrefixes(context, _document);
    _shortStyleClassMap = _getShortStyleClassMap(context, _document, _namespacePrefixes);

    return document;
  }

  // Returns an array of fully resolved StyleNodes for the
  // specified StyleContext  and StyleSheetDocument.
  // This will be those StyleNodes that match the locale, direction, browser, etc -- the
  // info that is in the StyleContext.
  private StyleNode[] _getStyleContextResolvedStyles(
    StyleContext context,
    StyleSheetDocument document
    )
  {
    Iterator<StyleNode> e = document.getStyles(context);
    if ((e == null) || !e.hasNext())
    {
      if (_LOG.isWarning())
        _LOG.warning("NO_STYLES_FOUND_CONTEXT", context);
      return null;
    }

    List<StyleNode> v = new ArrayList<StyleNode>();
    while (e.hasNext())
      v.add(e.next());

    return v.toArray(new StyleNode[v.size()]);
  }

  // Returns a Map of skin property names to values for the specified
  // styleSheetNodes that have been filtered from the StyleContext and StyleSheetDocument.
  private ConcurrentMap<Object, Object> _getStyleContextResolvedSkinProperties(
    StyleContext       context,
    StyleSheetDocument document
    )
  {
    Iterator<StyleSheetNode> styleSheetNodes = document.getStyleSheets(context);

    ConcurrentMap<Object, Object> skinProperties = new ConcurrentHashMap<Object, Object>();
    while (styleSheetNodes.hasNext())
    {
      StyleSheetNode styleSheetNode = styleSheetNodes.next();
      Collection<SkinPropertyNode> skinPropertyNodes = styleSheetNode.getSkinProperties();
     
      if (skinPropertyNodes != null)
      {
        for (SkinPropertyNode skinPropertyNode : skinPropertyNodes)
        {
          skinProperties.put(skinPropertyNode.getKey(), skinPropertyNode.getValue());
        }
      }
    }

    return skinProperties;
  }

  // Returns a Map of icon names to Icons for the specified
  // styleSheetNodes that have been filtered from the StyleContext and StyleSheetDocument.
  private ConcurrentMap<String, Icon> _getStyleContextResolvedIcons(
    StyleContext       context,
    StyleSheetDocument document
    )
  {
    Iterator<StyleSheetNode> styleSheetNodes = document.getStyleSheets(context);

    ConcurrentMap<String, Icon> icons = new ConcurrentHashMap<String, Icon>();
    while (styleSheetNodes.hasNext())
    {
      StyleSheetNode styleSheetNode = styleSheetNodes.next();
      Collection<IconNode> iconNodes = styleSheetNode.getIcons();
     
      if (iconNodes != null)
      {
        for (IconNode iconNode : iconNodes)
        {
          icons.put(iconNode.getIconName(), iconNode.getIcon());
        }
      }
    }

    return icons;
  }

  // Generates the CSS file for the specified context and styles.
  // Returns the name of the generated CSS file.
  private String        _createStyleSheetFile(
    StyleContext        context,
    StyleSheetDocument  document,
    StyleNode[]         styles,
    Map<String, String> shortStyleClassMap,
    String[]            namespacePrefixes,
    boolean             checkModified)
  {
    // Get a name for the new style sheet
    File outputFile = _getOutputFile(context, document);
    String name = outputFile.getName();

    // If the output file already exists, check the last modified time.
    if (outputFile.exists())
    {
      if (checkModified)
      {
        if (!_checkSourceModified(document, outputFile))
          return name;

        // If the output file is older than the source file, we
        // need to regenerate the output file.  But first we
        // need to delete the old output file before we attempt to
        // create a new version.
        outputFile.delete();
      }
      else
        return name;
    }

    // Now, try to create the file
    boolean created = false;

    try
    {
      // Make sure the output directory exists in case it's been
      // blown away since the creation of the cache
      File outputDir = outputFile.getParentFile();
      if (!outputDir.exists())
        outputDir.mkdirs();

      // If we can't create the new file, bail
      created = outputFile.createNewFile();
    }
    catch (IOException e)
    {
      if (_LOG.isWarning())
        _LOG.warning("IOEXCEPTION_CREATING_FILE", outputFile);
        _LOG.warning(e);
    }

    if (!created)
    {
      if (_LOG.isWarning())
        _LOG.warning("\nUnable to generate the style sheet "
                     + outputFile.getName() + " in cache directory\n"
                     + outputFile.getParent() + ".\n"
                     + "Please make sure that the cache directory exists "
                     + "and is writable.\n" );
      return null;
    }

    // Get the Writer to output to
    PrintWriter out = _getWriter(outputFile);
    if (out == null)
      return null;

    // Write out the style sheet
    // First figure out whether or not we need to compress the style classes.
    // We don't compress the style classes if the content compression flag is disabled or
    // if the skin is a portlet skin.
    Skin skin = RenderingContext.getCurrentInstance().getSkin();
    boolean compressStyles = _isCompressStyles(skin);

    CSSGenerationUtils.writeCSS(context,
                                skin.getStyleSheetName(),
                                styles,
                                out,
                                compressStyles,
                                shortStyleClassMap,
                                namespacePrefixes,
                                _STYLE_KEY_MAP
                                );

    out.close();

    // We never want to do anything other then read it or delete it:
    outputFile.setReadOnly();

    // Return the name of the new style sheet
    return name;
  }
 
  // First figure out whether or not we need to compress the style classes.
  // We don't compress the style classes if the content compression flag is disabled or
  // if the skin is a portlet skin.
  private boolean _isCompressStyles(Skin skin)
  {
    if (skin == null)
      skin = RenderingContext.getCurrentInstance().getSkin();
     

    String disableContentCompression =
      FacesContext.getCurrentInstance().getExternalContext().
      getInitParameter(StyleSheetRenderer.DISABLE_CONTENT_COMPRESSION);
    // we do not compress if it is a portlet skin
    boolean isPortletSkin =
    CoreRenderKit.OUTPUT_MODE_PORTLET.equals(skin.getRenderKitId());

    return (!"true".equals(disableContentCompression)) &&
                                             !isPortletSkin;
  }

  // Returns the name of the output file to use for the
  // style sheet associated with the specified context
  private File _getOutputFile(
    StyleContext context,
    StyleSheetDocument document
    )
  {
    String name = getTargetStyleSheetName(context, document);

    return new File(_targetPath, name);
  }

  // Returns the PrintWriter to use for the specified file
  private PrintWriter _getWriter(File file)
  {
    PrintWriter out = null;

    try
    {
      File parentFile = file.getParentFile();
      if (parentFile != null)
        parentFile.mkdirs();

      FileOutputStream fos = new FileOutputStream(file);
      OutputStreamWriter writer = null;

      // Use UTF8 encoding for output, in case font names have non-ascii
      // characters.
      try
      {
        writer = new OutputStreamWriter(fos, _UTF8_ENCODING);
      }
      catch (UnsupportedEncodingException e)
      {
        // UTF-8 should always be supported!
        assert false;

        // Just use default encoding instead
        writer = new OutputStreamWriter(fos);
      }

      out = new PrintWriter(new BufferedWriter(writer));
    }
    catch (IOException e)
    {
      if (_LOG.isWarning())
        _LOG.warning("IOEXCEPTION_OPENNING_FILE", file);
        _LOG.warning(e);
    }

    return out;
  }

  // Checks to see whether the source file has been modified
  // since the specified output file was generated.  If so,
  // we need to regenerate the output file.
  private boolean _checkSourceModified(
    StyleSheetDocument document,
    File               outputFile
    )
  {
    assert (outputFile != null);

    return (document.getDocumentTimestamp() > outputFile.lastModified());
  }

  // Gets the InputStreamProvider for the source file
  private InputStreamProvider _getInputStreamProvider()
  {
    // Source file may be null
    File sourceFile = _sourceFile;
    if (sourceFile == null)
      return null;

    assert (_resolver != null);

    try
    {
      return _resolver.getProvider(sourceFile.getPath());
    }
    catch (IOException e)
    {
      if (_LOG.isWarning())
        _LOG.warning(e);
    }

    return null;
  }

  // Initiializes the NameResolver
  private void _initResolver()
  {
    // Synchronize just in case two different threads hit this
    // at the same time
    synchronized (this)
    {
      if (_resolver == null)
      {
        NameResolver resolver = new DefaultNameResolver(_sourceFile, null);

        // We explicitly wrap the NameResolver in a CachingNameResolver
        // since that conveniently handles checking for modifications to
        // dependent (imported) files.
        // The default storage for cached files is a bit large,
        // so we use a smaller hash table.  Also, always enable
        // modification checking.
        // FIXME: Should probably be a ConcurrentHashMap
        resolver = new CachingNameResolver(resolver,
                                           new Hashtable<Object, InputStreamProvider>(17),
                                           true);
     
        _resolver = resolver;
      }
    }
  }

  // Copies an enumeration into a Vector
  private <T> Vector<T> _copyIterator(Iterator<T> e)
  {
    if (e == null)
      return null;

    Vector<T> v = new Vector<T>();
    while (e.hasNext())
      v.addElement(e.next());

    return v;
  }

  // Create an array of all the namespace prefixes in the xss/css file. E.g., "af|" or "tr|"
  private static String[] _getNamespacePrefixes(
    StyleContext       context,
    StyleSheetDocument document)
  {

    assert (document != null);
    Iterator<StyleSheetNode> styleSheets = document.getStyleSheets(context);
    assert (styleSheets != null);
    Set<String> namespacePrefixesSet = new HashSet<String>();
    while (styleSheets.hasNext())
    {
      StyleSheetNode styleSheet = styleSheets.next();
      Iterable<StyleNode> styles = styleSheet.getStyles();
      assert (styles != null);
      for (StyleNode style : styles)
      {
        String selector = style.getSelector();

        if (selector != null)
        {
          CSSGenerationUtils.getNamespacePrefixes(namespacePrefixesSet, selector);
        }
      }
    }
    return namespacePrefixesSet.toArray(_EMPTY_STRING_ARRAY);
  }

  // Create the map of full style classes to short style classes
  // Do not shorten styleclasses that start with SkinSelectors.STATE_PREFIX
  private static Map<String, String> _getShortStyleClassMap(
    StyleContext       context,
    StyleSheetDocument document,
    String[]           namespacePrefixes)
  {
    // Use a HashMap to avoid unnecessary synchronization of Hashtable
    Map<String, String> map = new HashMap<String, String>();

    assert (document != null);

    Iterator<StyleSheetNode> styleSheets = document.getStyleSheets(context);
    assert (styleSheets != null);

    Set<String> emptySelectors = new HashSet<String>();
    Set<String> nonEmptySelectors = new HashSet<String>(512);

    while (styleSheets.hasNext())
    {
      StyleSheetNode styleSheet = styleSheets.next();
      Iterable<StyleNode> styles = styleSheet.getStyles();
      assert (styles != null);
      for (StyleNode style : styles)
      {
        String selector = style.getSelector();

        if (selector != null)
        {
          // If we've got a single style class selector, add it
          // to the map. Otherwise, we need to search the selector
          // for style classes.
          if (CSSGenerationUtils.isSingleStyleClassSelector(selector))
          {
            String styleClass = selector.substring(1);
            _putStyleClassInShortMap(styleClass, map);
            // don't shorten styleclasses that are states since they are likely to be added
            // and removed on the client.
            if (styleClass != null && !styleClass.startsWith(SkinSelectors.STATE_PREFIX))
              if (!map.containsKey(styleClass))
                map.put(styleClass, _getShortStyleClass(map.size()));

            if (style.isEmpty())
              emptySelectors.add(styleClass);
            else
              nonEmptySelectors.add(styleClass);
          }
          else
          {
            Iterator<String> styleClasses =
              CSSGenerationUtils.getStyleClasses(selector);

            if (styleClasses != null)
            {
              while (styleClasses.hasNext())
              {
                String styleClass = styleClasses.next();
                _putStyleClassInShortMap(styleClass, map);
                // Don't remove any styleclass that is referred to
                nonEmptySelectors.add(styleClass);
              }
            }

            // now search for the selectors that have namespaces and add those to the map
            int length = namespacePrefixes.length;

            for (int i=0; i < length; i++)
            {
              String nsPrefix = namespacePrefixes[i];
              Iterator<String> afSelectors =
                CSSGenerationUtils.getNamespacedSelectors(selector,
                                                          nsPrefix,
                                                          _STYLE_KEY_MAP);
              if (afSelectors != null)
              {
                boolean isFirst = true;
                while (afSelectors.hasNext())
                {
                  String styleClass = afSelectors.next();
                  _putStyleClassInShortMap(styleClass, map);
                  if (isFirst && !afSelectors.hasNext() && style.isEmpty())
                  {
                    emptySelectors.add(styleClass);
                  }
                  else
                  {
                    nonEmptySelectors.add(styleClass);
                  }

                  isFirst = false;
                }
              }
            }
          }
        }
      }
    }

    emptySelectors.removeAll(nonEmptySelectors);

    // Replace all empty keys with an empty string as the selector
    for (String emptyKey : emptySelectors)
      map.put(emptyKey, CoreRenderingContext.EMPTY_STYLE_CLASS);

    // We actually need a Map, since this object is exposed
    // via public APIs.  Also, we need the Map to be immutable,
    // or else we would need to create a new copy of the Map
    // each time it is requested.
    return Collections.unmodifiableMap(map);
  }
 
  // Method to put styleclasses in the shortened map.
  // We don't put 'state' styleclasses in the shortened map. Those are styleclasses
  // that start with SkinSelectors.STATE_PREFIX. The reason is that those
  // are likely to be added and removed on the client as the state changes, and
  // we don't want to require the shortened map on the client.
  private static void _putStyleClassInShortMap(String styleClass, Map map)
  {
    if (styleClass != null &&
        !styleClass.startsWith(SkinSelectors.STATE_PREFIX) &&
        !map.containsKey(styleClass))
    {
      map.put(styleClass, _getShortStyleClass(map.size()));
    }
  }

  // Helper method used by _getShortStyleClassMap().  Returns a new
  // short style class selector.  The count is the number of style
  // classes seen so far.
  private static String _getShortStyleClass(int count)
  {
    // At the moment the short style class is just based on the nubmer
    // of style classes and not the style class itself.
    return _SHORT_CLASS_PREFIX + Integer.toString(count, Character.MAX_RADIX);
  }


  // Key class used for hashing style sheet URIs
  private static class Key
  {
    public Key(StyleContext context)
    {
      TrinidadAgent agent = context.getAgent();
      LocaleContext localeContext = context.getLocaleContext();
      AccessibilityProfile accProfile = context.getAccessibilityProfile();

      _init(
       localeContext.getTranslationLocale(),
       LocaleUtils.getReadingDirection(localeContext),
       agent.getAgentApplication(),
       agent.getAgentVersion(),
       agent.getAgentOS(),
       true,
       accProfile);
    }

    @Override
    public int hashCode()
    {
      int shortHash = (_short ? 1 : 0);

      return (_locale.hashCode()           ^
               (_direction)                ^
               (_browser  << 2)            ^
               (_version.hashCode())       ^
               (_platform << 8)            ^
               shortHash                   ^
               _accProfile.hashCode());
    }

    @Override
    public boolean equals(Object o)
    {
      if (this == o)
        return true;

      if (o instanceof Key)
      {
        Key key = (Key)o;

        // Check the easy stuff first
        if ((key._short != _short)         ||
            (_direction != key._direction) ||
            (_browser != key._browser)     ||
            (_version != key._version)     ||
            (_platform != key._platform)   ||
            !_locale.equals(key._locale)   ||
            !_accProfile.equals(key._accProfile))
        {
          return false;
        }
      }

      return true;
    }

    private void _init(
      Locale locale,
      int direction,
      int browser,
      String version,
      int platform,
      boolean useShort,
      AccessibilityProfile accessibilityProfile
      )
    {
      // Make sure direction is non-null
      _locale = (locale == null) ? Locale.getDefault() : locale;

      // Make sure direction is not default
      _direction =
        (direction == LocaleUtils.DIRECTION_DEFAULT) ?
          LocaleUtils.getReadingDirectionForLocale(_locale) :
          direction;

      _browser = browser;
      _version = version;
      _platform = platform;
      _short = useShort;
      _accProfile = accessibilityProfile;
    }

    private Locale         _locale;
    private int            _direction;
    private int            _browser;
    private String         _version;
    private int            _platform;
    private boolean        _short;  // Do we use short style classes?
    private AccessibilityProfile _accProfile;
  }

  // Cache entry class
  private static class Entry
  {
    public final String uri;
    public final StyleMap map;
    public final ConcurrentMap<String, Icon> icons;
    public final ConcurrentMap<Object, Object> skinProperties;

    public Entry(
      String uri,
      StyleMap map,
      ConcurrentMap<String, Icon> icons,
      ConcurrentMap<Object, Object> skinProperties)
    {
      this.uri = uri;
      this.map = map;
      this.icons = icons;
      this.skinProperties = skinProperties;
    }
  }

  // A key object which is used to hash Entrys in the
  // entry cache.  The key for the entry cache is the
  // style sheet derivation list - that is a list of
  // StyleSheetNodes, sorted by specficity.
  private static class DerivationKey
  {
    public DerivationKey(StyleContext context, StyleSheetNode[] styleSheets)
    {
      _styleSheets = new StyleSheetNode[styleSheets.length];
      System.arraycopy(styleSheets, 0, _styleSheets, 0, styleSheets.length);
      _short = true;
    }

    @Override
    public boolean equals(Object o)
    {
      if (o == this)
        return true;

      if (!(o instanceof DerivationKey))
        return false;

      DerivationKey key = (DerivationKey)o;

      if ((_short != key._short) ||
          (_styleSheets.length != key._styleSheets.length))
        return false;

      // Test each StyleSheetNode for equality.  We start
      // at the end of the list, as the last style sheet
      // is more likely to be different.  (The first entry
      // is probably the common base style sheet.)
      for (int i = _styleSheets.length - 1; i >= 0; i--)
      {
        if (!_styleSheets[i].equals(key._styleSheets[i]))
          return false;
      }

      return true;
    }

    @Override
    public int hashCode()
    {
      int hashCode = 0;

      for (int i = 0; i < _styleSheets.length; i++)
        hashCode ^= _styleSheets[i].hashCode();

      int shortHash = (_short ? 1 : 0);

      return hashCode ^ shortHash;
    }

    private StyleSheetNode[] _styleSheets;
    private boolean          _short;   // Do we use short style classes?
  }

  // A StyleMap implemenation which creates Style objects as needed
  private class StyleMapImpl implements StyleMap
  {
    // Implementation of StyleMap.getStyleBySelector()
    public Style getStyleBySelector(
      StyleContext context,
      String       selector
      )
    {
      if (_selectorMap == null)
        _selectorMap = _createMap();

      return _getStyle(context, _selectorMap, selector, "", false);
    }

    // Implementation of StyleMap.getStyleByClass()
    public Style getStyleByClass(
      StyleContext context,
      String       styleClass
      )
    {
      if (_classMap == null)
        _classMap = _createMap();
      String prefix = (styleClass.indexOf('|') > -1) ? "" : ".";
      return _getStyle(context, _classMap, styleClass, prefix , false);
    }

    // Implementation of StyleMap.getStyleByClass()
    public Style getStyleByName(
      StyleContext context,
      String       name
      )
    {
      if (_nameMap == null)
        _nameMap = _createMap();

      return _getStyle(context, _nameMap, name, "", true);
    }

    // Do all of the real work
    private Style _getStyle(
      StyleContext       context,
      Map<String, Style> map,
      String             id,
      String             prefix,
      boolean            isName
      )
    {
      CSSStyle style = (CSSStyle)map.get(id);

      if (style == _MISS)
        return null;
      if (style != null)
        return style;

      // Next, try getting the Style from the StyleSheetDocument
      StyleSheetDocument document = __getStyleSheetDocument();
      if (document == null)
        return null;

      StyleNode styleNode = null;

      if (isName)
      {
        styleNode = document.getStyleByName(context, id);
      }
      else
      {
        styleNode = document.getStyleBySelector(context, prefix + id);
      }

      if (styleNode == null)
      {
        map.put(id, _MISS);
        return null;
      }

      // Convert the styleNode into a Style
      style = new CSSStyle();

      // Add in the properties for the style
      Iterable<PropertyNode> propertyNodeList = styleNode.getProperties();
      for (PropertyNode property : propertyNodeList)
      {
        String name = property.getName();
        String value = property.getValue();

        style.setProperty(name, value);
      }

      map.put(id, style);
      return style;
    }

    // Creates a map of the specified size
    private Hashtable<String, Style> _createMap()
    {
      return new Hashtable<String, Style>(19);
    }


    // Our local Style maps
    // -= Simon Lessard =-
    // TODO: Check if synchronization is truly needed
    private Hashtable<String, Style> _selectorMap;
    private Hashtable<String, Style> _classMap;
    private Hashtable<String, Style> _nameMap;
  }

  private File   _sourceFile; // The source XSS file
  private String _targetPath; // The location of the cache
  private String _baseName;   // The base file name for generated CSS files

  // The NameResolver and InputStreamProvider we use to
  // resolve/load all files.  We also use the InputStreamProvider
  // to check for modifications to any dependent files.
  private NameResolver        _resolver;

  // The parsed StyleSheetDocument
  private StyleSheetDocument _document;

  // The cache of style sheet URIs
  private Hashtable<Key, Entry> _cache;

  // We cache Entry objects, hashed by DerivationKey (ie.
  // hashed based on the StyleSheetNode derivation list).
  private Hashtable<Object, Entry> _entryCache;

  // Map which maps from full style class names to
  // our compressed names.
  private Map<String, String> _shortStyleClassMap;
  private String[]            _namespacePrefixes;

  // Constants

  // Separator for variants in file names
  private static final char _NAME_SEPARATOR = '-';
  private static final String _COMPRESSED = "cmp";

  // Extension for CSS files
  private static final String _CSS_EXTENSION = ".css";

  // Java name for UTF8 encoding
  private static String _UTF8_ENCODING = "UTF8";

  // Stub StyleSheetDocument instance
  private static final StyleSheetDocument _EMPTY_DOCUMENT =
    new StyleSheetDocument(null, null, StyleSheetDocument.UNKNOWN_TIMESTAMP);

  // Style used to represent misses in the StyleMap.
  // Package private to allow access from nested StyleMapImpl class
          static final Style _MISS = new CSSStyle();

  // Prefix to use for short style classes
  private static final String _SHORT_CLASS_PREFIX = "x";

  private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(FileSystemStyleCache.class);


  // use this map to map from the public style selector names to
  // our internal style selector names. The public style selector
  // names do not contain html, whereas our internal style selector
  // names may. We write out the shortened version of the mapped
  // selector names to the css file.
  // jmw. @todo Need to find a better spot for this, like the skin?
  private static final Map<String, String> _STYLE_KEY_MAP;

  static
  {
    _STYLE_KEY_MAP =  new HashMap<String, String>();
    // we don't use a styleClass on tr:body. Instead we use the html element
    // BODY to style it. This makes it easier for users to use an external
    // stylesheet and not have to know our styleClass names.
    _STYLE_KEY_MAP.put("af|body", "BODY");

    _STYLE_KEY_MAP.put("af|panelHeader::level-one",
                 "H1.af|panelHeader");
    _STYLE_KEY_MAP.put("af|panelHeader::level-two",
                 "H2.af|panelHeader");
    _STYLE_KEY_MAP.put("af|panelHeader::level-three",
                 "H3.af|panelHeader");
    _STYLE_KEY_MAP.put("af|panelHeader::level-four",
                 "H4.af|panelHeader");
    _STYLE_KEY_MAP.put("af|panelHeader::level-five",
                 "H5.af|panelHeader");
    _STYLE_KEY_MAP.put("af|panelHeader::level-six",
                 "H6.af|panelHeader");

    // showDetailHeader
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-one",
                 "H1.af|showDetailHeader");
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-two",
                 "H2.af|showDetailHeader");
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-three",
                 "H3.af|showDetailHeader");
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-four",
                 "H4.af|showDetailHeader");
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-five",
                 "H5.af|showDetailHeader");
    _STYLE_KEY_MAP.put("af|showDetailHeader::level-six",
                 "H6.af|showDetailHeader");

    _STYLE_KEY_MAP.put("af|menuTabs::selected-link",
                 "af|menuTabs::selected A");
    _STYLE_KEY_MAP.put("af|menuTabs::enabled-link",
                 "af|menuTabs::enabled A");

    _STYLE_KEY_MAP.put("af|menuBar::enabled-link",
                 "af|menuBar::enabled A");
    _STYLE_KEY_MAP.put("af|menuBar::selected-link",
                 "af|menuBar::selected A");

    _STYLE_KEY_MAP.put("OraLinkEnabledLink",
                 "OraLinkEnabled A:link");
    _STYLE_KEY_MAP.put("OraLinkSelectedLink",
                 "OraLinkSelected A:link");
    _STYLE_KEY_MAP.put("OraLinkEnabledActive",
                 "OraLinkEnabled A:active");
    _STYLE_KEY_MAP.put("OraLinkSelectedActive",
                 "OraLinkSelected A:active");
    _STYLE_KEY_MAP.put("OraLinkEnabledVisited",
                 "OraLinkEnabled A:visited");
    _STYLE_KEY_MAP.put("OraLinkSelectedVisited",
                 "OraLinkSelected A:visited");

    _STYLE_KEY_MAP.put("af|panelPage::about-link",
                 "af|panelPage::about A");
    _STYLE_KEY_MAP.put("af|panelPage::copyright-link",
                 "af|panelPage::copyright A");
    _STYLE_KEY_MAP.put("af|panelPage::privacy-link",
                 "af|panelPage::privacy A");

    _STYLE_KEY_MAP.put("af|panelList::link",
                 "af|panelList A");
    _STYLE_KEY_MAP.put("af|panelList::unordered-list",
                 "af|panelList UL");

    _STYLE_KEY_MAP.put("af|inputDate::nav-link",
                 "af|inputDate::nav A");
    _STYLE_KEY_MAP.put("af|inputDate::content-link",
                 "af|inputDate::content A");
    _STYLE_KEY_MAP.put("af|inputDate::disabled-link",
                 "af|inputDate::disabled A");
    _STYLE_KEY_MAP.put("af|chooseDate::nav-link",
                 "af|chooseDate::nav A");
    _STYLE_KEY_MAP.put("af|chooseDate::content-link",
                 "af|chooseDate::content A");

    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_SHOWMANYACCORDION_TITLE_LINK_STYLE_CLASS,
        "A.af|showManyAccordion::title-link");
    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_SHOWMANYACCORDION_TITLE_LINK_DISABLED_STYLE_CLASS,
        "A.af|showManyAccordion::title-disabled-link");

    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_SHOWONEACCORDION_TITLE_LINK_STYLE_CLASS,
        "A.af|showOneAccordion::title-link");
    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_SHOWONEACCORDION_TITLE_LINK_DISABLED_STYLE_CLASS,
        "A.af|showOneAccordion::title-disabled-link");

    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_PANELACCORDION_TITLE_LINK_STYLE_CLASS,
        "A.af|panelAccordion::title-link");
    _STYLE_KEY_MAP.put(
        SkinSelectors.AF_PANELACCORDION_TITLE_LINK_DISABLED_STYLE_CLASS,
        "A.af|panelAccordion::title-disabled-link");

    _STYLE_KEY_MAP.put("af|panelTabbed::tab-link",
        "af|panelTabbed::tab A");
    _STYLE_KEY_MAP.put("af|panelTabbed::tab-selected-link",
        "af|panelTabbed::tab-selected A");

  }

  private static final String[] _EMPTY_STRING_ARRAY = new String[0];

}
TOP

Related Classes of org.apache.myfaces.trinidadinternal.style.cache.FileSystemStyleCache$Key

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.