/* * * 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.flex.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.flex.compiler.config.Configuration; import org.apache.flex.compiler.css.ICSSDocument; import org.apache.flex.compiler.css.ICSSFontFace; import org.apache.flex.compiler.css.ICSSNamespaceDefinition; import org.apache.flex.compiler.css.ICSSNode; import org.apache.flex.compiler.css.ICSSRule; import org.apache.flex.compiler.internal.css.CSSDocument; import org.apache.flex.compiler.internal.css.CSSModelTreeType; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.swc.ISWC; import org.apache.flex.swc.io.SWCReader; import org.apache.flex.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> { /** * 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; } } }