diff options
author | Gautam Shah <gautams@amdocs.com> | 2018-06-06 16:02:45 +0530 |
---|---|---|
committer | Avi Gaffa <avi.gaffa@amdocs.com> | 2018-08-10 07:51:32 +0000 |
commit | 7e0f45ab7839fda19459f790e0384c3a6c98e2bf (patch) | |
tree | fbafe7289b677f52990118d84a4818189214ca92 /openecomp-be/tools/compile-helper-plugin/src/main | |
parent | 9de469a6617bd0e53829d032fc871fdbecf6f4b1 (diff) |
Onboarding backend build optimization.
Enhancing Logic for referring/not-referring local build state repo
Change-Id: Ic2f85b20aed45cda7011ed1facb8ee39fdf54919
Issue-ID: SDC-1189
Signed-off-by: GAUTAMS <gautams@amdocs.com>
Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
Diffstat (limited to 'openecomp-be/tools/compile-helper-plugin/src/main')
2 files changed, 125 insertions, 104 deletions
diff --git a/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildHelper.java b/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildHelper.java index f042a7ca55..8d1941169e 100644 --- a/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildHelper.java +++ b/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildHelper.java @@ -16,8 +16,13 @@ package org.openecomp.sdc.onboarding; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.project.MavenProject; +import static org.openecomp.sdc.onboarding.Constants.CHECKSUM; +import static org.openecomp.sdc.onboarding.Constants.COLON; +import static org.openecomp.sdc.onboarding.Constants.DOT; +import static org.openecomp.sdc.onboarding.Constants.JAR; +import static org.openecomp.sdc.onboarding.Constants.JAVA_EXT; +import static org.openecomp.sdc.onboarding.Constants.SHA1; +import static org.openecomp.sdc.onboarding.Constants.UNICORN; import java.io.ByteArrayInputStream; import java.io.File; @@ -35,7 +40,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -43,41 +47,45 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; -import java.util.stream.Collectors; - -import static org.openecomp.sdc.onboarding.Constants.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; class BuildHelper { - private static Map<String, String> store = new HashMap<>(); + private static final Map<String, String> STORE = new HashMap<>(); + private static final Logger LOG = Logger.getAnonymousLogger(); private BuildHelper() { - // donot remove. + // do not remove. } static String getSnapshotSignature(File snapshotFile, String moduleCoordinate, String version) { String key = moduleCoordinate + ":" + version; - String signature = store.get(key); + String signature = STORE.get(key); if (signature != null) { return signature; } try { signature = new String(fetchSnapshotSignature(snapshotFile, version)); if (version.equals(signature)) { - signature = getSHA1For(snapshotFile, Paths.get(snapshotFile.getParentFile().getAbsolutePath(), + signature = getSha1For(snapshotFile, Paths.get(snapshotFile.getParentFile().getAbsolutePath(), moduleCoordinate.substring(moduleCoordinate.indexOf(':') + 1) + "-" + version + DOT + JAR + DOT + SHA1).toFile()); } - store.put(key, signature); + STORE.put(key, signature); return signature; } catch (IOException | NoSuchAlgorithmException e) { + LOG.log(Level.FINE, e.getMessage(), e); return version; } } - private static String getSHA1For(File file, File signatureFile) throws IOException, NoSuchAlgorithmException { + private static String getSha1For(File file, File signatureFile) throws IOException, NoSuchAlgorithmException { if (signatureFile.exists()) { return new String(Files.readAllBytes(signatureFile.toPath())); } @@ -111,14 +119,19 @@ class BuildHelper { private static Map<String, List<String>> readSources(File file, String fileType) throws IOException { Map<String, List<String>> source = new HashMap<>(); - if (file.exists()) { - List<File> list = Files.walk(Paths.get(file.getAbsolutePath())) - .filter(JAVA_EXT.equals(fileType) ? BuildHelper::isRegularJavaFile : - Files::isRegularFile).map(Path::toFile).collect(Collectors.toList()); + + if (!file.exists()) { + return source; + } + + try (Stream<Path> pathStream = Files.walk(Paths.get(file.getAbsolutePath()))) { + File[] selectedFiles = pathStream.filter( + JAVA_EXT.equals(fileType) ? BuildHelper::isRegularJavaFile : Files::isRegularFile) + .map(Path::toFile).toArray(File[]::new); source.putAll(ForkJoinPool.commonPool() - .invoke(new FileReadTask(list.toArray(new File[0]), file.getAbsolutePath()))); + .invoke(new FileReadTask(selectedFiles, file.getAbsolutePath()))); + return source; } - return source; } private static boolean isRegularJavaFile(Path path) { @@ -128,9 +141,9 @@ class BuildHelper { private static class FileReadTask extends RecursiveTask<Map<String, List<String>>> { - private Map<String, List<String>> store = new HashMap<>(); - File[] files; - String pathPrefix; + private final Map<String, List<String>> store = new HashMap<>(); + final File[] files; + final String pathPrefix; private static final int MAX_FILES = 10; FileReadTask(File[] files, String pathPrefix) { @@ -141,13 +154,7 @@ class BuildHelper { private static List<String> getData(File file) throws IOException { List<String> coll = Files.readAllLines(file.toPath(), StandardCharsets.ISO_8859_1); if (file.getAbsolutePath().contains(File.separator + "generated-sources" + File.separator)) { - Iterator<String> itr = coll.iterator(); - while (itr.hasNext()) { - String s = itr.next(); - if (s == null || s.trim().startsWith("/") || s.trim().startsWith("*")) { - itr.remove(); - } - } + coll.removeIf(s -> s == null || s.trim().startsWith("/") || s.trim().startsWith("*")); } return coll; } @@ -180,9 +187,9 @@ class BuildHelper { static Optional<String> getArtifactPathInLocalRepo(String repoPath, MavenProject project, byte[] sourceChecksum) throws MojoFailureException { - store.put(project.getGroupId() + COLON + project.getArtifactId() + COLON + project.getVersion(), + STORE.put(project.getGroupId() + COLON + project.getArtifactId() + COLON + project.getVersion(), new String(sourceChecksum)); - URI uri = null; + URI uri; try { uri = new URI(repoPath + (project.getGroupId().replace('.', '/')) + '/' + project.getArtifactId() + '/' + project.getVersion()); @@ -205,14 +212,16 @@ class BuildHelper { } private static byte[] fetchSnapshotSignature(File file, String version) throws IOException { + byte[] data = Files.readAllBytes(file.toPath()); - try (ByteArrayInputStream bais = new ByteArrayInputStream(data); - JarInputStream jis = new JarInputStream(bais)) { - JarEntry entry = null; - while ((entry = jis.getNextJarEntry()) != null) { + try (ByteArrayInputStream byteInputStream = new ByteArrayInputStream(data); + JarInputStream jarInputStream = new JarInputStream(byteInputStream)) { + + JarEntry entry; + while ((entry = jarInputStream.getNextJarEntry()) != null) { if (entry.getName().endsWith(UNICORN + DOT + CHECKSUM)) { byte[] sigStore = new byte[1024]; - return new String(sigStore, 0, jis.read(sigStore, 0, 1024)).getBytes(); + return new String(sigStore, 0, jarInputStream.read(sigStore, 0, 1024)).getBytes(); } } } @@ -220,10 +229,12 @@ class BuildHelper { } static <T> Optional<T> readState(String fileName, Class<T> clazz) { + try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); - ObjectInputStream ois = new ObjectInputStream(is)) { + ObjectInputStream ois = new ObjectInputStream(is)) { return Optional.of(clazz.cast(ois.readObject())); - } catch (Exception ignored) { + } catch (Exception e) { + LOG.log(Level.FINE, e.getMessage(), e); return Optional.empty(); } } diff --git a/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildState.java b/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildState.java index 2382b170aa..89adf203b5 100644 --- a/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildState.java +++ b/openecomp-be/tools/compile-helper-plugin/src/main/java/org/openecomp/sdc/onboarding/BuildState.java @@ -22,9 +22,7 @@ import static org.openecomp.sdc.onboarding.Constants.ANSI_YELLOW; import static org.openecomp.sdc.onboarding.Constants.FULL_BUILD_DATA; import static org.openecomp.sdc.onboarding.Constants.FULL_RESOURCE_BUILD_DATA; import static org.openecomp.sdc.onboarding.Constants.JAR; -import static org.openecomp.sdc.onboarding.Constants.MODULE_BUILD_DATA; import static org.openecomp.sdc.onboarding.Constants.RESOURCES_CHANGED; -import static org.openecomp.sdc.onboarding.Constants.RESOURCE_BUILD_DATA; import static org.openecomp.sdc.onboarding.Constants.SKIP_MAIN_SOURCE_COMPILE; import java.io.File; @@ -42,7 +40,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; - +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.maven.artifact.Artifact; import org.apache.maven.project.MavenProject; @@ -51,23 +50,25 @@ public class BuildState { private static final String SHUTDOWN_TIME = "shutdownTime"; private static final String VERSION = "version"; - private static Map<String, Map> compileDataStore = new HashMap<>(); - private static Map<String, Object> moduleBuildData = new HashMap<>(); - private static Map<String, Object> resourceBuildData = new HashMap<>(); - private static Map<String, Artifact> artifacts = new HashMap<>(); - private static Set<String> executeTestsIfDependsOnStore = new HashSet<>(); - private static Set<String> pmdExecutedInRun = new HashSet<>(); + private static HashMap<String, Map> compileDataStore = new HashMap<>(); + private static final Map<String, Object> MODULE_BUILD_DATA = new HashMap<>(); + private static final Map<String, Object> RESOURCE_BUILD_DATA = new HashMap<>(); + private static final Map<String, Artifact> ARTIFACTS = new HashMap<>(); + private static final Set<String> EXECUTE_TESTS_IF_DEPENDS_ON_STORE = new HashSet<>(); + private static final Set<String> PMD_EXECUTED_IN_RUN = new HashSet<>(); private static File stateFileLocation = new File(Paths.get(System.getProperties().getProperty("java.io.tmpdir")).toFile(), "compileState.dat"); private static File compileStateFile; + private static final Logger LOG = Logger.getAnonymousLogger(); private MavenProject project; private String compileStateFilePath; static { initializeStore(); Optional<HashMap> masterStore = readState("compile.dat", HashMap.class); - compileDataStore = masterStore.isPresent() ? masterStore.get() : compileDataStore; + //noinspection unchecked + compileDataStore = (HashMap) masterStore.orElseGet(() -> compileDataStore); String version = String.class.cast(compileDataStore.get(VERSION)); if (version != null) { stateFileLocation = new File(Paths.get(System.getProperties().getProperty("java.io.tmpdir")).toFile(), @@ -75,7 +76,7 @@ public class BuildState { } if (stateFileLocation.exists()) { HashMap dat = loadState(stateFileLocation); - if (swapStates((HashMap<?, ?>) compileDataStore, dat)) { + if (swapStates(compileDataStore, dat)) { compileDataStore = dat; } } @@ -83,10 +84,10 @@ public class BuildState { void init() { - artifacts.clear(); + ARTIFACTS.clear(); for (Artifact artifact : project.getArtifacts()) { if (artifact.isSnapshot() && JAR.equals(artifact.getType())) { - artifacts.put(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact); + ARTIFACTS.put(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact); } } if (compileStateFile == null) { @@ -100,47 +101,47 @@ public class BuildState { } static void initializeStore() { - compileDataStore.put(FULL_BUILD_DATA, new HashMap<>()); - compileDataStore.put(FULL_RESOURCE_BUILD_DATA, new HashMap<>()); - compileDataStore.put(MODULE_BUILD_DATA, new HashMap<>()); - compileDataStore.put(RESOURCE_BUILD_DATA, new HashMap<>()); + compileDataStore.put(Constants.FULL_BUILD_DATA, new HashMap<>()); + compileDataStore.put(Constants.FULL_RESOURCE_BUILD_DATA, new HashMap<>()); + compileDataStore.put(Constants.MODULE_BUILD_DATA, new HashMap<>()); + compileDataStore.put(Constants.RESOURCE_BUILD_DATA, new HashMap<>()); } static void recordPMDRun(String moduleCoordinates) { - pmdExecutedInRun.add(moduleCoordinates); + PMD_EXECUTED_IN_RUN.add(moduleCoordinates); } - static boolean isPMDRun(String moduleCoordintes) { - return pmdExecutedInRun.contains(moduleCoordintes); + static boolean isPMDRun(String moduleCoordinates) { + return PMD_EXECUTED_IN_RUN.contains(moduleCoordinates); } private void writeCompileState() throws IOException { writeState(compileStateFile, compileDataStore); } - private void writeState(File file, Map store) throws IOException { + private void writeState(File file, HashMap store) throws IOException { try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(store); } } - private File getCompileStateFile(String moduleCoordinate, MavenProject proj) { - return getStateFile(moduleCoordinate, proj, compileStateFilePath); + private File getCompileStateFile(String moduleCoordinate, MavenProject mavenProject) { + return getStateFile(moduleCoordinate, mavenProject, compileStateFilePath); } - private File getStateFile(String moduleCoordinate, MavenProject proj, String filePath) { - return new File(getTopParentProject(moduleCoordinate, proj).getBasedir(), + private File getStateFile(String moduleCoordinate, MavenProject mavenProject, String filePath) { + return new File(getTopParentProject(moduleCoordinate, mavenProject).getBasedir(), filePath.substring(filePath.indexOf('/') + 1)); } - MavenProject getTopParentProject(String moduleCoordinate, MavenProject proj) { - if (getModuleCoordinate(proj).equals(moduleCoordinate) || proj.getParent() == null) { - return proj; + MavenProject getTopParentProject(String moduleCoordinate, MavenProject mavenProject) { + if (getModuleCoordinate(mavenProject).equals(moduleCoordinate) || mavenProject.getParent() == null) { + return mavenProject; } else { - return getTopParentProject(moduleCoordinate, proj.getParent()); + return getTopParentProject(moduleCoordinate, mavenProject.getParent()); } } @@ -149,7 +150,8 @@ public class BuildState { } void addModuleBuildTime(String moduleCoordinates, Long buildTime) { - Long lastTime = Long.class.cast(compileDataStore.get(FULL_BUILD_DATA).put(moduleCoordinates, buildTime)); + Map fullBuildData = compileDataStore.get(FULL_BUILD_DATA); + @SuppressWarnings("unchecked") Long lastTime = (Long) fullBuildData.put(moduleCoordinates, buildTime); try { if (lastTime == null || !lastTime.equals(buildTime)) { boolean skipMainCompile = project.getProperties().containsKey(SKIP_MAIN_SOURCE_COMPILE); @@ -157,41 +159,47 @@ public class BuildState { writeCompileState(); } } - } catch (IOException ignored) { - // ignored. No need to handle. System will take care. + } catch (IOException e) { + LOG.log(Level.FINE, e.getMessage(), e); } } void addResourceBuildTime(String moduleCoordinates, Long buildTime) { + Map fullResourceBuildData = compileDataStore.get(FULL_RESOURCE_BUILD_DATA); if (project.getProperties().containsKey(RESOURCES_CHANGED) - || compileDataStore.get(FULL_RESOURCE_BUILD_DATA).get(moduleCoordinates) == null) { + || fullResourceBuildData.get(moduleCoordinates) == null) { try { - compileDataStore.get(FULL_RESOURCE_BUILD_DATA).put(moduleCoordinates, buildTime); + //noinspection unchecked + fullResourceBuildData.put(moduleCoordinates, buildTime); writeCompileState(); - } catch (IOException ignored) { - // ignored. No need to handle. System will take care. + } catch (IOException e) { + LOG.log(Level.FINE, e.getMessage(), e); } } } void addModuleBuildData(String moduleCoordinates, Map moduleBuildDependencies) { - moduleBuildData.put(moduleCoordinates, moduleBuildDependencies); + MODULE_BUILD_DATA.put(moduleCoordinates, moduleBuildDependencies); } - Map<String, Object> readModuleBuildData() { - return HashMap.class.cast(compileDataStore.get(MODULE_BUILD_DATA).get(getModuleCoordinate(project))); + HashMap readModuleBuildData() { + return (HashMap) compileDataStore.get(Constants.MODULE_BUILD_DATA).get(getModuleCoordinate(project)); } void saveModuleBuildData(String moduleCoordinate) { - if (moduleBuildData.get(moduleCoordinate) != null) { - compileDataStore.get(MODULE_BUILD_DATA).put(moduleCoordinate, moduleBuildData.get(moduleCoordinate)); + if (MODULE_BUILD_DATA.get(moduleCoordinate) != null) { + Map buildData = compileDataStore.get(Constants.MODULE_BUILD_DATA); + //noinspection unchecked + buildData.put(moduleCoordinate, MODULE_BUILD_DATA.get(moduleCoordinate)); } saveCompileData(); } void saveResourceBuildData(String moduleCoordinate) { - if (resourceBuildData.get(moduleCoordinate) != null) { - compileDataStore.get(RESOURCE_BUILD_DATA).put(moduleCoordinate, resourceBuildData.get(moduleCoordinate)); + if (RESOURCE_BUILD_DATA.get(moduleCoordinate) != null) { + Map buildData = compileDataStore.get(Constants.RESOURCE_BUILD_DATA); + //noinspection unchecked + buildData.put(moduleCoordinate, RESOURCE_BUILD_DATA.get(moduleCoordinate)); } saveCompileData(); } @@ -201,48 +209,49 @@ public class BuildState { } void markTestsMandatoryModule(String moduleCoordinates) { - executeTestsIfDependsOnStore.add(moduleCoordinates); + EXECUTE_TESTS_IF_DEPENDS_ON_STORE.add(moduleCoordinates); } private void saveBuildData(File file, Object dataToSave) { file.getParentFile().mkdirs(); if (dataToSave != null) { try (FileOutputStream fos = new FileOutputStream(file); - ObjectOutputStream ois = new ObjectOutputStream(fos)) { + ObjectOutputStream ois = new ObjectOutputStream(fos)) { ois.writeObject(dataToSave); - } catch (IOException ignored) { - // ignored. No need to handle. System will take care. + } catch (IOException e) { + LOG.log(Level.FINE, e.getMessage(), e); } } } - Map<String, Object> readResourceBuildData() { - return HashMap.class.cast(compileDataStore.get(RESOURCE_BUILD_DATA).get(getModuleCoordinate(project))); + HashMap readResourceBuildData() { + return (HashMap) compileDataStore.get(Constants.RESOURCE_BUILD_DATA).get(getModuleCoordinate(project)); } void addResourceBuildData(String moduleCoordinates, Map currentModuleResourceBuildData) { - resourceBuildData.put(moduleCoordinates, currentModuleResourceBuildData); + RESOURCE_BUILD_DATA.put(moduleCoordinates, currentModuleResourceBuildData); } Long getBuildTime(String moduleCoordinates) { - Long buildTime = Long.class.cast(compileDataStore.get(FULL_BUILD_DATA).get(moduleCoordinates)); + Long buildTime = (Long) compileDataStore.get(FULL_BUILD_DATA).get(moduleCoordinates); return buildTime == null ? 0 : buildTime; } Long getResourceBuildTime(String moduleCoordinates) { - Long resourceBuildTime = Long.class.cast(compileDataStore.get(FULL_RESOURCE_BUILD_DATA).get(moduleCoordinates)); + Long resourceBuildTime = (Long) compileDataStore.get(FULL_RESOURCE_BUILD_DATA).get(moduleCoordinates); return resourceBuildTime == null ? 0 : resourceBuildTime; } boolean isCompileMust(String moduleCoordinates, Collection<String> dependencies) { for (String d : dependencies) { - if (artifacts.containsKey(d) && JAR.equals(artifacts.get(d).getType())) { - boolean versionEqual = artifacts.get(d).getVersion().equals(project.getVersion()); + if (ARTIFACTS.containsKey(d) && JAR.equals(ARTIFACTS.get(d).getType())) { + boolean versionEqual = ARTIFACTS.get(d).getVersion().equals(project.getVersion()); if (versionEqual && getBuildTime(d) == 0) { - System.err.println(ANSI_YELLOW + "[WARNING:]" + "You have module[" + d - + "] not locally compiled even once, please compile your project once daily from root to have reliable build results." - + ANSI_COLOR_RESET); + LOG.warning(ANSI_YELLOW + "[WARNING:]" + "You have module[" + d + + "] not locally compiled even once, please compile your project once daily " + + "from root to have reliable build results." + + ANSI_COLOR_RESET); return true; } } @@ -251,8 +260,8 @@ public class BuildState { } boolean isTestExecutionMandatory() { - for (String d : artifacts.keySet()) { - if (executeTestsIfDependsOnStore.contains(d)) { + for (String d : ARTIFACTS.keySet()) { + if (EXECUTE_TESTS_IF_DEPENDS_ON_STORE.contains(d)) { return true; } } @@ -261,16 +270,16 @@ public class BuildState { boolean isTestMust(String moduleCoordinates) { return getBuildTime(moduleCoordinates) > getResourceBuildTime(moduleCoordinates) || isMust( - this::getResourceBuildTime, moduleCoordinates, artifacts.keySet()); + this::getResourceBuildTime, moduleCoordinates, ARTIFACTS.keySet()); } - private boolean isMust(Function<String, Long> funct, String moduleCoordinates, Collection<String> dependencies) { - Long time = funct.apply(moduleCoordinates); + private boolean isMust(Function<String, Long> function, String moduleCoordinates, Collection<String> dependencies) { + Long time = function.apply(moduleCoordinates); if (time == null || time == 0) { return true; } for (String module : dependencies) { - Long buildTime = funct.apply(module); + Long buildTime = function.apply(module); if (buildTime >= time) { return true; } @@ -280,15 +289,16 @@ public class BuildState { private static HashMap loadState(File file) { try (InputStream is = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(is)) { - return HashMap.class.cast(ois.readObject()); + return (HashMap) ois.readObject(); } catch (Exception e) { + LOG.log(Level.FINE, e.getMessage(), e); return new HashMap<>(); } } private static boolean swapStates(HashMap repo, HashMap last) { - Long repoTime = repo.get(SHUTDOWN_TIME) == null ? 0 : (Long) repo.get(SHUTDOWN_TIME); - Long lastTime = last.get(SHUTDOWN_TIME) == null ? 0 : (Long) last.get(SHUTDOWN_TIME); + long repoTime = repo.get(SHUTDOWN_TIME) == null ? 0 : (Long) repo.get(SHUTDOWN_TIME); + long lastTime = last.get(SHUTDOWN_TIME) == null ? 0 : (Long) last.get(SHUTDOWN_TIME); String repoVersion = repo.get(VERSION) == null ? "" : (String) repo.get(VERSION); String lastVersion = last.get(VERSION) == null ? "" : (String) last.get(VERSION); long repoBuildNumber = repoTime % 1000; @@ -299,7 +309,7 @@ public class BuildState { if (!repoVersion.equals(lastVersion)) { return false; } - return Long.compare(repoTime, lastTime) < 0; + return repoTime < lastTime; } |