| /* | |
| * Copyright 2009 Google Inc. | |
| * | |
| * Licensed 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 com.google.gwt.uibinder.rebind.model; | |
| import com.google.gwt.core.ext.TreeLogger; | |
| import com.google.gwt.core.ext.UnableToCompleteException; | |
| import com.google.gwt.core.ext.impl.ResourceLocatorImpl; | |
| import com.google.gwt.core.ext.typeinfo.JClassType; | |
| import com.google.gwt.dev.resource.ResourceOracle; | |
| import com.google.gwt.resources.css.ExtractClassNamesVisitor; | |
| import com.google.gwt.resources.css.GenerateCssAst; | |
| import com.google.gwt.resources.css.ast.CssStylesheet; | |
| import com.google.gwt.resources.ext.ResourceGeneratorUtil; | |
| import com.google.gwt.resources.gss.ClassNamesCollector; | |
| import com.google.gwt.resources.rg.GssResourceGenerator; | |
| import com.google.gwt.thirdparty.common.css.SourceCode; | |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree; | |
| import com.google.gwt.thirdparty.common.css.compiler.ast.GssParser; | |
| import com.google.gwt.thirdparty.common.css.compiler.ast.GssParserException; | |
| import com.google.gwt.uibinder.attributeparsers.CssNameConverter; | |
| import com.google.gwt.uibinder.rebind.MortalLogger; | |
| import java.io.BufferedWriter; | |
| import java.io.File; | |
| import java.io.FileWriter; | |
| import java.io.IOException; | |
| import java.net.MalformedURLException; | |
| import java.net.URISyntaxException; | |
| import java.net.URL; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| /** | |
| * Models a method returning a CssResource on a generated ClientBundle. | |
| */ | |
| public class ImplicitCssResource { | |
| private static Set<String> getCssClassNames(String fileName, String cssSource, | |
| Set<JClassType> imports, TreeLogger logger) throws UnableToCompleteException { | |
| SourceCode sourceCode = new SourceCode(fileName, cssSource); | |
| try { | |
| CssTree tree = new GssParser(sourceCode).parse(); | |
| return new ClassNamesCollector().getClassNames(tree, imports); | |
| } catch (GssParserException e) { | |
| logger.log(TreeLogger.ERROR, "Unable to parse CSS", e); | |
| throw new UnableToCompleteException(); | |
| } | |
| } | |
| private static final CssNameConverter nameConverter = new CssNameConverter(); | |
| private final String packageName; | |
| private final String className; | |
| private final String name; | |
| private final List<String> sources; | |
| private final JClassType extendedInterface; | |
| private final String body; | |
| private final MortalLogger logger; | |
| private final Set<JClassType> imports; | |
| private final ResourceOracle resourceOracle; | |
| private final boolean gss; | |
| private File generatedFile; | |
| private Set<String> cssClassNames; | |
| private Set<String> normalizedCssClassNames; | |
| /** | |
| * Visible for testing only, get instances from {@link ImplicitClientBundle}. | |
| */ | |
| public ImplicitCssResource(String packageName, String className, String name, | |
| String[] source, JClassType extendedInterface, String body, | |
| MortalLogger logger, Set<JClassType> importTypes, boolean gss, | |
| ResourceOracle resourceOracle) { | |
| this.packageName = packageName; | |
| this.className = className; | |
| this.name = name; | |
| this.extendedInterface = extendedInterface; | |
| this.body = body; | |
| this.logger = logger; | |
| this.imports = Collections.unmodifiableSet(importTypes); | |
| this.resourceOracle = resourceOracle; | |
| this.gss = gss; | |
| sources = Arrays.asList(source); | |
| } | |
| /** | |
| * Returns the name of the CssResource interface. | |
| */ | |
| public String getClassName() { | |
| return className; | |
| } | |
| /** | |
| * Returns the set of CSS classnames in the underlying css or gss files. | |
| * | |
| * @throws UnableToCompleteException if the user has called for a css/gss file we | |
| * can't find. | |
| */ | |
| public Set<String> getCssClassNames() throws UnableToCompleteException { | |
| List<URL> urls = getExternalCss(); | |
| if (cssClassNames == null) { | |
| final File bodyFile = getGeneratedFile(); | |
| if (bodyFile != null) { | |
| try { | |
| urls.add(bodyFile.toURI().toURL()); | |
| } catch (MalformedURLException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| assert urls.size() > 0; | |
| if (gss) { | |
| String gssContent = GssResourceGenerator.concatCssFiles(urls, logger.getTreeLogger()); | |
| String fileName = bodyFile != null ? bodyFile.getName() : name; | |
| return getCssClassNames(fileName, gssContent, imports, logger.getTreeLogger()); | |
| } else { | |
| CssStylesheet sheet = GenerateCssAst.exec(logger.getTreeLogger(), | |
| urls.toArray(new URL[urls.size()])); | |
| cssClassNames = ExtractClassNamesVisitor.exec(sheet, | |
| imports.toArray(new JClassType[imports.size()])); | |
| } | |
| } | |
| return cssClassNames; | |
| } | |
| /** | |
| * Returns the public interface that this CssResource implements. | |
| */ | |
| public JClassType getExtendedInterface() { | |
| return extendedInterface; | |
| } | |
| /** | |
| * Returns the set of CssResource types whose scopes are imported. | |
| */ | |
| public Set<JClassType> getImports() { | |
| return imports; | |
| } | |
| /** | |
| * Returns the name of this resource. This is both its method name in the | |
| * owning {@link ImplicitClientBundle} and its ui:field name. | |
| */ | |
| public String getName() { | |
| return name; | |
| } | |
| /** | |
| * Returns css class names with dashed-names normalized like so: dashedNames. | |
| */ | |
| public Set<String> getNormalizedCssClassNames() | |
| throws UnableToCompleteException { | |
| if (normalizedCssClassNames == null) { | |
| Set<String> rawNames = getCssClassNames(); | |
| normalizedCssClassNames = new HashSet<String>(); | |
| for (String rawName : rawNames) { | |
| normalizedCssClassNames.add(nameConverter.convertName(rawName)); | |
| } | |
| } | |
| return normalizedCssClassNames; | |
| } | |
| /** | |
| * Returns the package in which the generated CssResource interface should | |
| * reside. | |
| */ | |
| public String getPackageName() { | |
| return packageName; | |
| } | |
| /** | |
| * Returns the name of the generated type. | |
| */ | |
| public String getQualifiedSourceName() { | |
| if (packageName.length() == 0) { | |
| return name; | |
| } | |
| return String.format("%s.%s", packageName, className); | |
| } | |
| /** | |
| * Returns the name of the css or gss file(s), separate by white space. | |
| */ | |
| public Collection<String> getSource() { | |
| if (body.length() == 0) { | |
| return Collections.unmodifiableCollection(sources); | |
| } | |
| List<String> rtn = new ArrayList<String>(sources); | |
| rtn.add(getBodyFileName()); | |
| return rtn; | |
| } | |
| private String getBodyFileName() { | |
| String bodyFileName = String.format("uibinder.%s.%s.%s", packageName, className, | |
| getCssFileExtension()); | |
| // To verify that the resulting file can be retrieved out of zip files using a URL reference. | |
| assert isValidUrl("file:/" + bodyFileName); | |
| return bodyFileName; | |
| } | |
| private List<URL> getExternalCss() throws UnableToCompleteException { | |
| /* | |
| * TODO(rjrjr,bobv) refactor ResourceGeneratorUtil.findResources so we can | |
| * find them the same way ClientBundle does. For now, just look relative to | |
| * this package | |
| */ | |
| String path = packageName.replace(".", "/"); | |
| List<URL> urls = new ArrayList<URL>(); | |
| for (String s : sources) { | |
| String resourcePath = path + '/' + s; | |
| // Try to find the resource relative to the package. | |
| URL found = ResourceLocatorImpl.tryFindResourceUrl(logger.getTreeLogger(), resourceOracle, | |
| resourcePath); | |
| /* | |
| * If we didn't find the resource relative to the package, assume it | |
| * is absolute. | |
| */ | |
| if (found == null) { | |
| found = ResourceLocatorImpl.tryFindResourceUrl(logger.getTreeLogger(), resourceOracle, s); | |
| } | |
| if (found == null) { | |
| logger.die("Unable to find resource: " + resourcePath); | |
| } | |
| urls.add(found); | |
| } | |
| return urls; | |
| } | |
| private File getGeneratedFile() { | |
| if (body.length() == 0) { | |
| return null; | |
| } | |
| if (generatedFile == null) { | |
| try { | |
| File f = File.createTempFile(String.format("uiBinder_%s_%s", packageName, className), | |
| "." + getCssFileExtension()); | |
| f.deleteOnExit(); | |
| BufferedWriter out = new BufferedWriter(new FileWriter(f)); | |
| out.write(body); | |
| out.close(); | |
| generatedFile = f; | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| ResourceGeneratorUtil.addNamedFile(getBodyFileName(), generatedFile); | |
| } | |
| return generatedFile; | |
| } | |
| private boolean isValidUrl(String urlString) { | |
| try { | |
| new URL(urlString).toURI(); | |
| } catch (MalformedURLException e) { | |
| return false; | |
| } catch (URISyntaxException e) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private String getCssFileExtension() { | |
| return gss ? "gss" : "css"; | |
| } | |
| } |