From 4014d1929d1cc24d67a78584a3a781b51b64f4ce Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 26 Jul 2019 15:47:26 +0300 Subject: [PATCH] Add rewrite HtmlImports step (#6109) * Rename the goal * Flatten frontend directory * Correct the logic to search for themed files inside node_modules * Add IT test for client side imported components theming * Revert "Add IT test for client side imported components theming" This reverts commit 0cfb7233d3eb377109af91c7fbbdeaaa7ffc5f0b. * Revert "Correct the logic to search for themed files inside node_modules" This reverts commit 8e65792952e9192ae442d2e81ff351d2157b3366. * Rewrite HtmlImports to JsModules Fixes #6092 * Rewrite the content of the file even if it doesn't match the declaration pattern. * Add parameter for html import rewrite strategy * Add unit tests for rewrite HtmlImports step. * Fix code review comments * Add javadocs and fix minor things in code * Apply better replacement suggestion by code review --- flow-maven-plugin/pom.xml | 26 +- .../vaadin/flow/plugin/maven/MigrateMojo.java | 62 +++- .../migration/AbstractCopyResourcesStep.java | 8 + .../migration/RewriteHtmlImportsStep.java | 279 ++++++++++++++++++ .../migration/CopyResourcesStepTest.java | 33 ++- .../migration/RewriteHtmlImportsStepTest.java | 232 +++++++++++++++ .../ClassUnitWithNonPublicClass.java | 47 +++ .../migration/samplecode/Component1.java | 34 +++ .../migration/samplecode/Component2.java | 24 ++ .../migration/samplecode/Component3.java | 24 ++ .../EnclosingClassWithNestedClass.java | 31 ++ .../samplecode/GenericComponent.java | 22 ++ 12 files changed, 811 insertions(+), 11 deletions(-) create mode 100644 flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStep.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStepTest.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/ClassUnitWithNonPublicClass.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component1.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component2.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component3.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/EnclosingClassWithNestedClass.java create mode 100644 flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/GenericComponent.java diff --git a/flow-maven-plugin/pom.xml b/flow-maven-plugin/pom.xml index 642826d2213..c1eb56695f9 100644 --- a/flow-maven-plugin/pom.xml +++ b/flow-maven-plugin/pom.xml @@ -1,4 +1,5 @@ - 4.0.0 @@ -108,5 +109,28 @@ + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-resource + generate-resources + + add-test-resource + + + + + src/test/java + + + + + + + diff --git a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/MigrateMojo.java b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/MigrateMojo.java index d5460010a9d..6b8cebf7879 100644 --- a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/MigrateMojo.java +++ b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/MigrateMojo.java @@ -37,11 +37,15 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import com.vaadin.flow.component.dependency.HtmlImport; +import com.vaadin.flow.plugin.common.FlowPluginFrontendUtils; import com.vaadin.flow.plugin.migration.CopyMigratedResourcesStep; import com.vaadin.flow.plugin.migration.CopyResourcesStep; import com.vaadin.flow.plugin.migration.CreateMigrationJsonsStep; +import com.vaadin.flow.plugin.migration.RewriteHtmlImportsStep; import com.vaadin.flow.server.frontend.FrontendUtils; import elemental.json.Json; @@ -54,11 +58,19 @@ * @author Vaadin Ltd * */ -@Mojo(name = "migrate", defaultPhase = LifecyclePhase.PROCESS_RESOURCES) +@Mojo(name = "migrate-to-p3", requiresDependencyResolution = ResolutionScope.COMPILE, defaultPhase = LifecyclePhase.PROCESS_CLASSES) public class MigrateMojo extends AbstractMojo { private static final String DEPENDENCIES = "dependencies"; + /** + * The strategy to rewrite {@link HtmlImport} annotations. + * + */ + public static enum HtmlImportsRewriteStrategy { + ALWAYS, SKIP, SKIP_ON_ERROR; + } + /** * A list of directories with files to migrate. */ @@ -96,6 +108,24 @@ public class MigrateMojo extends AbstractMojo { @Parameter(defaultValue = "true") private boolean ignoreModulizerErrors; + /** + * Allows to specify the strategy to use to rewrite {@link HtmlImport} + * annotations in Java files. + *

+ * Three values are available: + *

+ */ + @Parameter(defaultValue = "ALWAYS") + private HtmlImportsRewriteStrategy htmlImportsRewrite; + @Override public void execute() throws MojoExecutionException, MojoFailureException { prepareMigrationDirectory(); @@ -195,6 +225,20 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (!modulizerHasErrors && !keepOriginal) { removeOriginalResources(paths); } + + switch (htmlImportsRewrite) { + case SKIP: + break; + case ALWAYS: + rewrite(); + break; + case SKIP_ON_ERROR: + if (!modulizerHasErrors) { + rewrite(); + } + break; + } + } private void prepareMigrationDirectory() { @@ -371,14 +415,18 @@ private void cleanUp(File dir) throws IOException { private String[] getResources() { if (resources == null) { File webApp = new File(project.getBasedir(), "src/main/webapp"); - File frontend = new File(webApp, "frontend"); - if (frontend.exists() && webApp.listFiles().length == 1) { - resources = new String[] { frontend.getPath() }; - } else { - resources = new String[] { webApp.getPath() }; - } + resources = new String[] { webApp.getPath() }; } return resources; } + private void rewrite() { + RewriteHtmlImportsStep step = new RewriteHtmlImportsStep( + new File(project.getBuild().getOutputDirectory()), + FlowPluginFrontendUtils.getClassFinder(project), + project.getCompileSourceRoots().stream().map(File::new) + .collect(Collectors.toList())); + step.rewrite(); + } + } diff --git a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/AbstractCopyResourcesStep.java b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/AbstractCopyResourcesStep.java index e2f017fd157..0bb06e7ec12 100644 --- a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/AbstractCopyResourcesStep.java +++ b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/AbstractCopyResourcesStep.java @@ -116,6 +116,14 @@ List getVisitedPaths() { private Path getTarget(Path source) { Path relativePath = sourceRoot.relativize(source); + String topLevel = relativePath.getName(0).toString(); + if ("frontend".equals(topLevel)) { + if (relativePath.getNameCount() == 1) { + return targetRoot; + } + relativePath = relativePath.subpath(1, + relativePath.getNameCount()); + } return targetRoot.resolve(relativePath); } } diff --git a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStep.java b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStep.java new file mode 100644 index 00000000000..cc81d4edd61 --- /dev/null +++ b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStep.java @@ -0,0 +1,279 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.dependency.HtmlImport; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.plugin.common.ClassPathIntrospector; +import com.vaadin.flow.plugin.common.FlowPluginFileUtils; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.shared.ApplicationConstants; + +/** + * Rewrites {@link HtmlImport} annotation to corresponding {@link JsModule} + * annotation. + * + * @author Vaadin Ltd + * + */ +public class RewriteHtmlImportsStep extends ClassPathIntrospector { + + private static final String HTML_EXTENSION = ".html"; + private final URL compiledClassesURL; + private final Collection sourceRoots; + + private static final String CLASS_DECLARATION_PATTERN = "(\\s|public|final|abstract|private|static|protected)*\\s+class\\s+%s(|<|>|\\?|\\w|\\s|,|\\&)*\\s+((extends\\s+(\\w|<|>|\\?|,)+)|(implements\\s+(|<|>|\\?|\\w|\\s|,)+( ,(\\w|<|>|\\?|\\s|,))*))?\\s*\\{"; + + private final Map, Pattern> compiledPatterns = new HashMap<>(); + + /** + * Creates a new instance using the {@code compiledClassesDir} directory (to + * filter out classes which belongs to project only, not dependencies), + * class {@code finder} instance and collection of project source roots to + * search Java source files. + * + * @param compiledClassesDir + * directory with compiled classes + * @param finder + * a class finder + * @param sourceRoots + * project source root directories + */ + public RewriteHtmlImportsStep(File compiledClassesDir, ClassFinder finder, + Collection sourceRoots) { + super(finder); + compiledClassesURL = FlowPluginFileUtils + .convertToUrl(compiledClassesDir); + this.sourceRoots = sourceRoots; + } + + /** + * Search for java files in the project and replace {@link HtmlImport} to + * {@link JsModule} annotation with updated value. + */ + public void rewrite() { + Class annotationInProjectContext = loadClassInProjectClassLoader( + HtmlImport.class.getName()); + Stream> classes = getAnnotatedClasses( + annotationInProjectContext); + Map, Collection> imports = new HashMap<>(); + classes.forEach(clazz -> handleClass(clazz, imports)); + + imports.forEach(this::rewriteImports); + } + + private void handleClass(Class clazz, + Map, Collection> imports) { + Class annotationInProjectContext = loadClassInProjectClassLoader( + HtmlImport.class.getName()); + URL location = clazz.getProtectionDomain().getCodeSource() + .getLocation(); + if (!compiledClassesURL.toExternalForm() + .equals(location.toExternalForm())) { + return; + } + Annotation[] annotationsByType = clazz + .getAnnotationsByType(annotationInProjectContext); + for (Annotation annotation : annotationsByType) { + String path = invokeAnnotationMethod(annotation, "value") + .toString(); + Collection paths = imports.computeIfAbsent(clazz, + cls -> new ArrayList<>()); + paths.add(path); + } + } + + private void rewriteImports(Class clazz, Collection paths) { + Collection javaFiles = findJavaSourceFiles(clazz); + if (javaFiles.isEmpty()) { + LoggerFactory.getLogger(RewriteHtmlImportsStep.class).debug( + "Could not find Java source code for class '{}'", clazz); + } else { + javaFiles.forEach(javaFile -> rewrite(javaFile, clazz, paths)); + } + } + + private Collection findJavaSourceFiles(Class clazz) { + String packageName = clazz.getPackage().getName(); + String pckgPath = packageName.replace(".", "/"); + Collection pkgDirs = new ArrayList<>(); + Collection result = new ArrayList<>(); + for (File sourceRoot : sourceRoots) { + File pkgFolder = new File(sourceRoot, pckgPath); + if (!pkgFolder.exists()) { + continue; + } + pkgDirs.add(pkgFolder); + Class topLevelClass = getTopLevelEnclosingClass(clazz); + File mayBeJavaFile = new File(pkgFolder, + topLevelClass.getSimpleName() + ".java"); + if (mayBeJavaFile.exists()) { + result.add(mayBeJavaFile); + break; + } + } + if (result.isEmpty()) { + // the class doesn't have to be in its own java file, we need to + // scan all files for its declaration... + for (File pkg : pkgDirs) { + result.addAll(findClassFiles(pkg, clazz)); + } + + } + return result; + } + + /** + * Find {@code clazz} declaration in files inside the {@code pkgDir}. + *

+ * The way which is used to find the class is not exact, so there may be + * several matching files. + */ + private Collection findClassFiles(File pkgDir, Class clazz) { + Collection result = new ArrayList<>(); + for (File file : pkgDir.listFiles()) { + if (!file.isFile() || !file.getName().endsWith(".java")) { + continue; + } + String content = readFile(file); + if (content == null) { + continue; + } + Pattern classDeclarationPattern = getClassDeclarationPattern(clazz); + compiledPatterns.put(clazz, classDeclarationPattern); + if (classDeclarationPattern.matcher(content).find()) { + result.add(file); + } + } + return result; + } + + private String readFile(File file) { + try { + return FileUtils.readFileToString(file, StandardCharsets.UTF_8); + } catch (IOException e) { + getLogger().warn("Could not read source code from file '{}'", file); + return null; + } + } + + private void rewrite(File javaFile, Class clazz, + Collection paths) { + String content = readFile(javaFile); + Pattern classDeclarationPattern = compiledPatterns.get(clazz); + if (classDeclarationPattern == null) { + classDeclarationPattern = getClassDeclarationPattern(clazz); + } + Matcher matcher = classDeclarationPattern.matcher(content); + int classDeclarationStart = content.length(); + if (matcher.find()) { + classDeclarationStart = matcher.start(); + } else { + getLogger().debug( + "Implementation issue: unable to find class declaration inside {} java source file", + javaFile); + } + + String beforeClassDeclaration = content.substring(0, + classDeclarationStart); + + String rewritten = beforeClassDeclaration.replaceAll("\\bHtmlImport\\b", + "JsModule"); + for (String path : paths) { + rewritten = rewritten.replaceAll( + String.format("\"%s\"", Pattern.quote(path)), + String.format("\"%s\"", rewritePath(path))); + } + rewritten = rewritten + content.substring(classDeclarationStart); + try { + FileUtils.write(javaFile, rewritten, StandardCharsets.UTF_8); + } catch (IOException e) { + getLogger().warn("Could not write source code back to file '{}'", + javaFile); + } + } + + private String rewritePath(String path) { + String rewritten = path; + if (rewritten.endsWith(HTML_EXTENSION)) { + rewritten = rewritten.substring(0, + rewritten.length() - HTML_EXTENSION.length()) + ".js"; + } + rewritten = removePrefix(rewritten, + ApplicationConstants.BASE_PROTOCOL_PREFIX); + rewritten = removePrefix(rewritten, + ApplicationConstants.FRONTEND_PROTOCOL_PREFIX); + rewritten = removePrefix(rewritten, + ApplicationConstants.CONTEXT_PROTOCOL_PREFIX); + + if (rewritten.startsWith(AbstractCopyResourcesStep.BOWER_COMPONENTS)) { + rewritten = rewritten.substring( + AbstractCopyResourcesStep.BOWER_COMPONENTS.length()); + if (rewritten.startsWith("/vaadin")) { + rewritten = "@vaadin" + rewritten; + } else { + getLogger().warn("Don't know how to resolve Html import '{}'", + path); + } + } else if (rewritten.startsWith("/")) { + rewritten = "." + rewritten; + } else if (!rewritten.startsWith("./")) { + rewritten = "./" + rewritten; + } + return rewritten; + } + + private String removePrefix(String path, String prefix) { + if (path.startsWith(prefix)) { + return path.substring(prefix.length()); + } + return path; + } + + private Pattern getClassDeclarationPattern(Class clazz) { + return Pattern.compile(String.format(CLASS_DECLARATION_PATTERN, + clazz.getSimpleName())); + } + + private Class getTopLevelEnclosingClass(Class clazz) { + Class enclosingClass = clazz.getEnclosingClass(); + if (enclosingClass == null) { + return clazz; + } + return getTopLevelEnclosingClass(enclosingClass); + } + + private Logger getLogger() { + return LoggerFactory.getLogger(RewriteHtmlImportsStep.class); + } +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/CopyResourcesStepTest.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/CopyResourcesStepTest.java index 4d3e9f70345..fbe77f1177c 100644 --- a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/CopyResourcesStepTest.java +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/CopyResourcesStepTest.java @@ -162,15 +162,42 @@ public void copyResources_copyFileAndReturnBowerComponents() Assert.assertTrue(bowerComponents.contains("vaadin-text-field")); } + @Test + public void copyResources_moveFrontendToRoot_copyFileAndReturnBowerComponents() + throws IOException { + File file1 = new File(source1, "foo.html"); + File file2 = new File(source1, "frontend/bar.html"); + file2.getParentFile().mkdirs(); + + Files.write(file1.toPath(), Collections.singletonList( + "")); + Files.write(file2.toPath(), Collections.singletonList( + "")); + + Map> resources = step.copyResources(); + + assertCopiedResources(resources, source1, "bar.html", "foo.html"); + + Set bowerComponents = step.getBowerComponents(); + Assert.assertEquals(2, bowerComponents.size()); + Assert.assertTrue(bowerComponents.contains("vaadin-button")); + Assert.assertTrue(bowerComponents.contains("vaadin-text-field")); + + Assert.assertTrue(new File(target, "foo.html").exists()); + Assert.assertTrue(new File(target, "bar.html").exists()); + } + private String readFile(File file) throws IOException { return Files.readAllLines(file.toPath()).stream() .collect(Collectors.joining("\n")); } private void assertCopiedResources(Map> copied, - File source, String path) { + File source, String... path) { List list = copied.get(source.getPath()); - Assert.assertEquals(1, list.size()); - Assert.assertEquals(path, list.get(0)); + Assert.assertEquals(path.length, list.size()); + for (int i = 0; i < path.length; i++) { + Assert.assertEquals(path[i], list.get(i)); + } } } diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStepTest.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStepTest.java new file mode 100644 index 00000000000..caf1fa99c54 --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/RewriteHtmlImportsStepTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import org.apache.commons.io.FileUtils; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; + +import com.vaadin.flow.component.dependency.HtmlImport; +import com.vaadin.flow.plugin.migration.samplecode.ClassUnitWithNonPublicClass; +import com.vaadin.flow.plugin.migration.samplecode.Component1; +import com.vaadin.flow.plugin.migration.samplecode.Component2; +import com.vaadin.flow.plugin.migration.samplecode.Component3; +import com.vaadin.flow.plugin.migration.samplecode.EnclosingClassWithNestedClass; +import com.vaadin.flow.plugin.migration.samplecode.EnclosingClassWithNestedClass.NestedComponent; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; + +public class RewriteHtmlImportsStepTest { + + private RewriteHtmlImportsStep step; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private File compiledClassesDir; + + private ClassFinder finder = Mockito.mock(ClassFinder.class); + + private File sourceRoot1; + private File sourceRoot2; + + @Before + public void setUp() + throws IOException, ClassNotFoundException, URISyntaxException { + compiledClassesDir = new File(RewriteHtmlImportsStepTest.class + .getProtectionDomain().getCodeSource().getLocation().toURI()); + + sourceRoot1 = temporaryFolder.newFolder(); + sourceRoot2 = temporaryFolder.newFolder(); + step = new RewriteHtmlImportsStep(compiledClassesDir, finder, + Arrays.asList(sourceRoot1, sourceRoot2)); + + Mockito.doAnswer(invocation -> Class + .forName(invocation.getArgumentAt(0, String.class))) + .when(finder).loadClass(Mockito.any(String.class)); + } + + @SuppressWarnings("unchecked") + @Test + public void fileIsInOneSourceRoot_classIsInCompiledClassesDir_htmlImportsAreRewritten() + throws IOException { + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)) + .thenReturn(Collections.singleton(Component1.class)); + File sourceFile = makeSourceJavaFile(sourceRoot1, Component1.class); + step.rewrite(); + + String content = FileUtils.readFileToString(sourceFile, + StandardCharsets.UTF_8); + Assert.assertThat(content, CoreMatchers.allOf( + CoreMatchers.containsString( + "import com.vaadin.flow.component.dependency.JsModule;"), + CoreMatchers.containsString("@JsModule(\"./foo.js\")"), + CoreMatchers.containsString("@JsModule(\"./foo1.js\")"), + CoreMatchers.containsString("@JsModule(\"./foo2.js\")"), + CoreMatchers.containsString("@JsModule(\"./bar.js\")"), + CoreMatchers.containsString("@JsModule(\"./bar1.js\")"), + CoreMatchers.containsString("@JsModule(\"./src/baz.js\")"), + CoreMatchers.containsString( + "@JsModule(\"@vaadin/vaadin-button/src/vaadin-button.js\")"), + CoreMatchers.containsString( + "@JsModule(\"@vaadin/vaadin-text-field/src/vaadin-text-field.js\")"))); + } + + @Test + public void fileIsInOneSourceRoot_classIsNotInCompiledClassesDir_nothingIsDone() + throws IOException, URISyntaxException { + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)) + .thenReturn(Collections.singleton(Component1.class)); + + compiledClassesDir = temporaryFolder.newFolder(); + + step = new RewriteHtmlImportsStep(compiledClassesDir, finder, + Arrays.asList(sourceRoot1, sourceRoot2)); + + File sourceFile = makeSourceJavaFile(sourceRoot1, Component1.class); + step.rewrite(); + + String content = FileUtils.readFileToString(sourceFile, + StandardCharsets.UTF_8); + Assert.assertEquals( + FileUtils.readFileToString(getSourceFile(Component1.class), + StandardCharsets.UTF_8), + content); + } + + @Test + public void filesAreInBothSourceRoots_classedAreInCompiledClassesDir_htmlImportsAreRewritten() + throws IOException { + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)) + .thenReturn(new HashSet<>( + Arrays.asList(Component2.class, Component3.class))); + File sourceFile1 = makeSourceJavaFile(sourceRoot1, Component2.class); + File sourceFile2 = makeSourceJavaFile(sourceRoot2, Component3.class); + step.rewrite(); + + Assert.assertThat( + FileUtils.readFileToString(sourceFile1, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./foo.js\")")); + + Assert.assertThat( + FileUtils.readFileToString(sourceFile2, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./bar.js\")")); + } + + @Test + public void nonPublicClassDeclaredInForeignJavaFile_filesAreInBothSourceRoots_classedAreInCompiledClassesDir_htmlImportsAreRewritten() + throws IOException, ClassNotFoundException { + // Fake a case when the same non public class is declared in foreign + // Java file but it exists in both source roots (improbable situation + // since it has to be only in one file, but this allows to check that + // every possible file which contains class declaration (in source) is + // rewritten + + Class nonPublicClass = Class + .forName(ClassUnitWithNonPublicClass.NON_PUBLIC_CLASS_NAME); + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)).thenReturn( + new HashSet<>(Arrays.asList(nonPublicClass, Component3.class))); + File sourceFile1 = makeSourceJavaFile(sourceRoot1, + ClassUnitWithNonPublicClass.class); + File sourceFile2 = makeSourceJavaFile(sourceRoot2, Component3.class); + // append source code from sourceFile1 to sourceFile2 + + Files.write(sourceFile2.toPath(), + Collections.singletonList(FileUtils + .readFileToString(sourceFile1, StandardCharsets.UTF_8)), + StandardOpenOption.APPEND); + step.rewrite(); + + // Correct class source code: non public class is defined in foreign + // Java file, the import is rewritten + Assert.assertThat( + FileUtils.readFileToString(sourceFile1, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./src/foo/bar.js\")")); + + // Incorrect class source code: we have appended to the correct Java + // class source code the content of another class. But it still should + // be rewritten correctly + Assert.assertThat( + FileUtils.readFileToString(sourceFile2, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./src/foo/bar.js\")")); + } + + @Test + public void nestedClass_fileIsInOneSourceRoot_classIsInCompiledClassesDir_htmlImportsAreRewritten() + throws IOException { + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)) + .thenReturn(Collections.singleton(NestedComponent.class)); + File sourceFile = makeSourceJavaFile(sourceRoot1, + EnclosingClassWithNestedClass.class); + step.rewrite(); + + Assert.assertThat( + FileUtils.readFileToString(sourceFile, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./foo.js\")")); + } + + @Test + public void nestedClassInsideNonPublicClassDeclaredInForeignJavaFile_fileIsInOneSourceRoot_classIsInCompiledClassesDir_htmlImportsAreRewritten() + throws IOException, ClassNotFoundException { + Class nestedClas = Class.forName( + ClassUnitWithNonPublicClass.NESTEDCLASS_INSIDE_NON_PUBLIC_CLASS_NAME); + Mockito.when(finder.getAnnotatedClasses(HtmlImport.class)) + .thenReturn(Collections.singleton(nestedClas)); + File sourceFile = makeSourceJavaFile(sourceRoot1, + ClassUnitWithNonPublicClass.class); + step.rewrite(); + + Assert.assertThat( + FileUtils.readFileToString(sourceFile, StandardCharsets.UTF_8), + CoreMatchers.containsString("@JsModule(\"./baz.js\")")); + } + + private File makeSourceJavaFile(File root, Class clazz) + throws IOException { + String folder = clazz.getPackage().getName().replace(".", "/"); + File sourceFolder = new File(root, folder); + sourceFolder.mkdirs(); + String name = clazz.getSimpleName() + ".java"; + + File sourceFile = new File(sourceFolder, name); + + InputStream inputStream = clazz.getResourceAsStream(name); + Files.copy(inputStream, sourceFile.toPath()); + return sourceFile; + } + + private File getSourceFile(Class clazz) throws URISyntaxException { + String name = clazz.getSimpleName() + ".java"; + + return new File(clazz.getResource(name).toURI()); + } + +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/ClassUnitWithNonPublicClass.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/ClassUnitWithNonPublicClass.java new file mode 100644 index 00000000000..ec09154d6d3 --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/ClassUnitWithNonPublicClass.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import java.io.Serializable; +import java.util.List; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.dependency.HtmlImport; +import com.vaadin.flow.plugin.migration.samplecode.NonPublicClassWithNestedClass.NestedClassInsideNonPublicClass; + +public class ClassUnitWithNonPublicClass { + + public static final String NON_PUBLIC_CLASS_NAME = NonPublicClassWithinForeignUnitFile.class + .getName(); + public static final String NESTEDCLASS_INSIDE_NON_PUBLIC_CLASS_NAME = NestedClassInsideNonPublicClass.class + .getName(); + +} + +@HtmlImport("src/foo/bar.html") +class NonPublicClassWithinForeignUnitFile & Serializable, U extends Object> + extends GenericComponent { + +} + +abstract class NonPublicClassWithNestedClass + implements List> { + + @HtmlImport("baz.html") + public static class NestedClassInsideNonPublicClass extends Component { + + } +} \ No newline at end of file diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component1.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component1.java new file mode 100644 index 00000000000..7b3329e9cb0 --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component1.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import java.io.Serializable; +import java.util.List; + +import com.vaadin.flow.component.dependency.HtmlImport; + +@HtmlImport("frontend://foo.html") +@HtmlImport("base://foo1.html") +@HtmlImport("context://foo2.html") +@HtmlImport("bar.html") +@HtmlImport("/bar1.html") +@HtmlImport("src/baz.html") +@HtmlImport("frontend://bower_components/vaadin-button/src/vaadin-button.html") +@HtmlImport("bower_components/vaadin-text-field/src/vaadin-text-field.html") +public class Component1 & Serializable> + extends GenericComponent { + +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component2.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component2.java new file mode 100644 index 00000000000..4b40df69eeb --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component2.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.dependency.HtmlImport; + +@HtmlImport("frontend://foo.html") +public class Component2 extends Component { + +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component3.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component3.java new file mode 100644 index 00000000000..cf0ab628fab --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/Component3.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.dependency.HtmlImport; + +@HtmlImport("bar.html") +public class Component3 extends Component { + +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/EnclosingClassWithNestedClass.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/EnclosingClassWithNestedClass.java new file mode 100644 index 00000000000..f1e8627a64c --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/EnclosingClassWithNestedClass.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import java.io.Serializable; +import java.util.List; + +import com.vaadin.flow.component.dependency.HtmlImport; + +public class EnclosingClassWithNestedClass { + + @HtmlImport("./foo.html") + public abstract static class NestedComponent & Serializable, U extends Object> + extends GenericComponent { + + } + +} diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/GenericComponent.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/GenericComponent.java new file mode 100644 index 00000000000..bdf884a04ff --- /dev/null +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/migration/samplecode/GenericComponent.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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.vaadin.flow.plugin.migration.samplecode; + +import com.vaadin.flow.component.Component; + +public class GenericComponent extends Component { + +}