blob: 10c7cd42150ea468ff7a901c8f663090f56d801f [file] [log] [blame]
/*
*
* 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.royale.compiler.internal.caches;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipFile;
import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.ANTLRInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.caches.ICSSDocumentCache;
import org.apache.royale.compiler.config.Configuration;
import org.apache.royale.compiler.css.ICSSDocument;
import org.apache.royale.compiler.css.ICSSFontFace;
import org.apache.royale.compiler.css.ICSSNamespaceDefinition;
import org.apache.royale.compiler.css.ICSSNode;
import org.apache.royale.compiler.css.ICSSRule;
import org.apache.royale.compiler.internal.css.CSSDocument;
import org.apache.royale.compiler.internal.css.CSSModelTreeType;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.swc.ISWC;
import org.apache.royale.swc.io.SWCReader;
import org.apache.royale.utils.FilenameNormalization;
import com.google.common.collect.ImmutableList;
/**
* Cache for {@link ICSSDocument} at workspace level. The CSS model can be a
* "defaults.css" file inside a SWC library, or a CSS file on the disk.
* <p>
* The cache key is normalized path to the SWC file (optional) and the CSS file
* name inside the SWC. The cache value is an {@link ICSSDocument}.
*/
public class CSSDocumentCache extends ConcurrentCacheStoreBase<ICSSDocument> implements ICSSDocumentCache
{
/**
* Since {@link ConcurrentCacheStoreBase#get} doesn't return compiler
* problems, when there's problem parsing CSS file in
* {@link #createEntryValue}, we have to throw a runtime exception to pass
* the compiler problems to the caller of the cache store.
*/
public static class ProblemParsingCSSRuntimeException extends RuntimeException
{
private static final long serialVersionUID = 156921800741800866L;
public ProblemParsingCSSRuntimeException(final Collection<ICompilerProblem> problems)
{
super();
this.cssParserProblems = problems;
}
/**
* A collection of compiler problems from parsing the CSS file.
*/
public final Collection<ICompilerProblem> cssParserProblems;
}
/**
* Since {@link ConcurrentCacheStoreBase} does not allow null values, when a
* SWC library does not have a "defaults.css" file, this dummy value is
* used.
*/
public static final ICSSDocument EMPTY_CSS_DOCUMENT = new ICSSDocument()
{
@Override
public ImmutableList<ICSSRule> getRules()
{
return ImmutableList.of();
}
@Override
public ICSSNamespaceDefinition getNamespaceDefinition(String prefix)
{
return null;
}
@Override
public ImmutableList<ICSSFontFace> getFontFaces()
{
return ImmutableList.of();
}
@Override
public ICSSNamespaceDefinition getDefaultNamespaceDefinition()
{
return null;
}
@Override
public ImmutableList<ICSSNamespaceDefinition> getAtNamespaces()
{
return ImmutableList.of();
}
@Override
public String toStringTree()
{
return null;
}
@Override
public int getArity()
{
return 0;
}
@Override
public ICSSNode getNthChild(int index)
{
throw new IllegalStateException();
}
@Override
public CSSModelTreeType getOperator()
{
throw new IllegalStateException();
}
@Override
public String getSourcePath()
{
// TODO Auto-generated method stub
return null;
}
@Override
public int getStart()
{
return 0;
}
@Override
public int getEnd()
{
return 0;
}
@Override
public int getLine()
{
return 0;
}
@Override
public int getColumn()
{
return 0;
}
@Override
public int getEndLine()
{
return 0;
}
@Override
public int getEndColumn()
{
return 0;
}
@Override
public int getAbsoluteStart()
{
return 0;
}
@Override
public int getAbsoluteEnd()
{
return 0;
}
};
private abstract static class CSSDocumentCacheKeyBase extends CacheStoreKeyBase
{
abstract ICSSDocument parse() throws IOException;
}
/**
* Key object for {@code CSSDocumentCache}. It the combination of a
* normalized SWC file path and the CSS file inside the SWC. If the
* {@code swcFile} is null, the {@code cssFileName} points to a CSS disk
* file.
*/
public static class CSSDocumentCacheKey extends CSSDocumentCacheKeyBase
{
public final ISWC swc;
public final String cssFileName;
public CSSDocumentCacheKey(final ISWC swc, final String cssFileName)
{
assert cssFileName != null : "CSS file name can't be null.";
this.swc = swc;
this.cssFileName = cssFileName;
}
@Override
public String generateKey()
{
return String.format(
"%s:%s",
FilenameNormalization.normalize(swc.getSWCFile()).getAbsolutePath(),
cssFileName);
}
/**
* Parse a CSS file in a SWC library into {@link ICSSDocument} model. If the
* CSS file does not exist, returns {@link #EMPTY_CSS_DOCUMENT} dummy
* object.
*
* @throws IOException IO error.
*/
@Override
ICSSDocument parse() throws IOException
{
final ZipFile zipFile = new ZipFile(swc.getSWCFile(), ZipFile.OPEN_READ);
ICSSDocument result = EMPTY_CSS_DOCUMENT;
InputStream input = null;
try
{
input = SWCReader.getInputStream(zipFile, cssFileName);
if (input != null)
{
final ANTLRInputStream in = new ANTLRInputStream(input);
in.name = String.format("%s:%s", swc.getSWCFile().getName(), cssFileName);
final List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
result = CSSDocument.parse(in, problems);
if (!problems.isEmpty())
throw new ProblemParsingCSSRuntimeException(problems);
}
}
finally
{
IOUtils.closeQuietly(input);
zipFile.close();
}
return result;
}
}
/**
* Key object for {@code CSSDocumentCache}. It the combination of a
* normalized SWC file path and the CSS file inside the SWC. If the
* {@code swcFile} is null, the {@code cssFileName} points to a CSS disk
* file.
*/
protected static class CSSDocumentCacheKey2 extends CSSDocumentCacheKeyBase
{
protected final String cssFileName; // non-null
public CSSDocumentCacheKey2(final String cssFileName)
{
assert cssFileName != null : "CSS file name can't be null.";
this.cssFileName = cssFileName;
}
@Override
public String generateKey()
{
return cssFileName;
}
/**
* parse a bare CSS file on the file system.
*/
@Override
ICSSDocument parse() throws IOException
{
final List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
final CSSDocument css = CSSDocument.parse(new ANTLRFileStream(cssFileName), problems);
if (!problems.isEmpty())
throw new ProblemParsingCSSRuntimeException(problems);
if (css != null)
return css;
return EMPTY_CSS_DOCUMENT;
}
}
/**
* Create a cache key for {@code CSSDocumentCache} that references
* a CSS file in a SWC.
*
* @param swc SWC file
* @param cssFileName CSS file name
* @return Key for {@code CSSDocumentCache}.
*/
public static CacheStoreKeyBase createKey(final ISWC swc, final String cssFileName)
{
return new CSSDocumentCacheKey(swc, cssFileName);
}
/**
* Create a cache key for {@code CSSDocumentCache} that references
* a CSS file on disk.
*
* @param cssFileName CSS file name
* @return Key for {@code CSSDocumentCache}.
*/
public static CacheStoreKeyBase createKey(final String cssFileName)
{
return new CSSDocumentCacheKey2(cssFileName);
}
@Override
protected ICSSDocument createEntryValue(CacheStoreKeyBase key)
{
assert key instanceof CSSDocumentCacheKeyBase : "Expected 'CSSDocumentCacheKeyBase' but got " + key.getClass().getSimpleName();
final CSSDocumentCacheKeyBase cacheKey = (CSSDocumentCacheKeyBase)key;
ICSSDocument result = EMPTY_CSS_DOCUMENT;
try
{
result = cacheKey.parse();
}
catch (IOException e)
{
// Ignore exception and return dummy value.
}
return result;
}
public static String[] ALL_DEFAULTS_CSS_FILENAMES = {"defaults.css", "defaults-3.0.0.css" };
/**
* Get the compatible-mode default CSS filename.
*
* @param version Compatible version.
* @return Defaults CSS filename.
*/
private static String getCompatibleModeCSSFilename(final Integer version)
{
if (version == null)
return "defaults.css";
else if (version <= Configuration.MXML_VERSION_3_0)
return "defaults-3.0.0.css";
else
return "defaults.css";
}
/**
* Get the "default" CSS model in a SWC library. If
* {@code compatibility-version=3} is set, this method will try to get
* "defaults-3.0.0.css" first. If the compatibility version isn't present,
* it will fall back to "defaults.css".
*
* @param swc SWC file.
* @param compatibilityVersion Compatibility version, or null if the
* compiler is not under compatibility mode.
* @return "defaults" CSS model or null if not found
*/
public ICSSDocument getDefaultsCSS(final ISWC swc, final Integer compatibilityVersion)
{
final CacheStoreKeyBase key;
final String cssFilename = getCompatibleModeCSSFilename(compatibilityVersion);
key = createKey(swc, cssFilename);
final ICSSDocument css = this.get(key);
assert css != null : "ConcurrentCacheStoreBase never caches null value.";
if (css == CSSDocumentCache.EMPTY_CSS_DOCUMENT)
{
if (compatibilityVersion != null)
{
// If compatible CSS is not present, fall back to "defaults.css".
return getDefaultsCSS(swc, null);
}
else
{
return null;
}
}
else
{
return css;
}
}
}