Eliminate custom SimpleDateFormat instances
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / repo / util / FilesystemSchemaSourceCache.java
index 3478953a0607b7e02e2eb6b4646fbda0557ee1e9..e2d7d5d2b74fe85a95a4ed1704c79b66a0e9f05f 100644 (file)
@@ -7,14 +7,16 @@
  */
 package org.opendaylight.yangtools.yang.model.repo.util;
 
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.FileVisitResult;
@@ -23,19 +25,24 @@ import java.nio.file.Path;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.text.DateFormat;
+import java.text.ParseException;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
-import org.opendaylight.yangtools.yang.model.util.repo.FilesystemSchemaCachingProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,10 +54,15 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
     private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
 
     // Init storage adapters
-    private static final Map<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> storageAdapters =
-            Collections.<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> singletonMap(
+    private static final Map<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> STORAGE_ADAPTERS =
+            Collections.singletonMap(
                     YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());
 
+    private static final Pattern CACHED_FILE_PATTERN =
+            Pattern.compile(
+                    "(?<moduleName>[^@]+)" +
+                    "(@(?<revision>" + SourceIdentifier.REVISION_PATTERN + "))?");
+
     private final Class<T> representation;
     private final File storageDirectory;
 
@@ -62,7 +74,7 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
 
         checkSupportedRepresentation(representation);
 
-        if(!storageDirectory.exists()) {
+        if (!storageDirectory.exists()) {
             Preconditions.checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
         }
         Preconditions.checkArgument(storageDirectory.exists());
@@ -74,21 +86,16 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
     }
 
     private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
-        for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : storageAdapters.keySet()) {
-            if(supportedRepresentation.isAssignableFrom(representation)) {
+        for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : STORAGE_ADAPTERS.keySet()) {
+            if (supportedRepresentation.isAssignableFrom(representation)) {
                 return;
             }
         }
 
        throw new IllegalArgumentException(String.format(
-                "This cache does not support representation: %s, supported representations are: %s", representation, storageAdapters.keySet()));
+                "This cache does not support representation: %s, supported representations are: %s", representation, STORAGE_ADAPTERS.keySet()));
     }
 
-    private static final Pattern CACHED_FILE_PATTERN =
-            Pattern.compile(
-                    "(?<moduleName>[^@]+)" +
-                    "(@(?<revision>" + FilesystemSchemaCachingProvider.REVISION_PATTERN + "))?");
-
     /**
      * Restore cache state
      */
@@ -109,22 +116,22 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
 
     @Override
     public synchronized CheckedFuture<? extends T, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
-        final File file = FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(sourceIdentifier), storageDirectory);
-        if(file.exists() && file.canRead()) {
+        final File file = sourceIdToFile(sourceIdentifier, storageDirectory);
+        if (file.exists() && file.canRead()) {
             LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
-            final SchemaSourceRepresentation restored = storageAdapters.get(representation).restore(sourceIdentifier, file);
+            final SchemaSourceRepresentation restored = STORAGE_ADAPTERS.get(representation).restore(sourceIdentifier, file);
             return Futures.immediateCheckedFuture(representation.cast(restored));
         }
 
         LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
-        return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
+        return Futures.immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
     }
 
     @Override
     protected synchronized void offer(final T source) {
         LOG.trace("Source {} offered to cache", source.getIdentifier());
         final File file = sourceIdToFile(source);
-        if(file.exists()) {
+        if (file.exists()) {
             LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
             return;
         }
@@ -135,15 +142,69 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
     }
 
     private File sourceIdToFile(final T source) {
-        return FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(source.getIdentifier()), storageDirectory);
+        return sourceIdToFile(source.getIdentifier(), storageDirectory);
     }
 
-    private void storeSource(final File file, final T schemaRepresentation) {
-        storageAdapters.get(representation).store(file, schemaRepresentation);
+    static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
+        final String rev = identifier.getRevision();
+        final File file;
+        if (Strings.isNullOrEmpty(rev)) {
+            file = findFileWithNewestRev(identifier, storageDirectory);
+        } else {
+            file = new File(storageDirectory, identifier.toYangFilename());
+        }
+        return file;
     }
 
-    private static org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier toLegacy(final SourceIdentifier identifier) {
-        return new org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier(identifier.getName(), Optional.fromNullable(identifier.getRevision()));
+    private static File findFileWithNewestRev(final SourceIdentifier identifier, final File storageDirectory) {
+        File[] files = storageDirectory.listFiles(new FilenameFilter() {
+            final Pattern p = Pattern.compile(Pattern.quote(identifier.getName()) + "(\\.yang|@\\d\\d\\d\\d-\\d\\d-\\d\\d.yang)");
+
+            @Override
+            public boolean accept(final File dir, final String name) {
+                return p.matcher(name).matches();
+            }
+        });
+
+        if (files.length == 0) {
+            return new File(storageDirectory, identifier.toYangFilename());
+        }
+        if (files.length == 1) {
+            return files[0];
+        }
+
+        File file = null;
+        TreeMap<Date, File> map = new TreeMap<>();
+        for (File sorted : files) {
+            String fileName = sorted.getName();
+            Matcher m = SourceIdentifier.REVISION_PATTERN.matcher(fileName);
+            if (m.find()) {
+                String revStr = m.group();
+                /*
+                 * FIXME: Consider using string for comparison.
+                 * String is comparable, pattern check tested format
+                 * so comparing as ASCII string should be sufficient
+                 */
+                DateFormat df = SimpleDateFormatUtil.getRevisionFormat();
+                try {
+                    Date d = df.parse(revStr);
+                    map.put(d, sorted);
+                } catch (final ParseException e) {
+                    LOG.info("Unable to parse date from yang file name {}", fileName);
+                    map.put(new Date(0L), sorted);
+                }
+
+            } else {
+                map.put(new Date(0L), sorted);
+            }
+        }
+        file = map.lastEntry().getValue();
+
+        return file;
+    }
+
+    private void storeSource(final File file, final T schemaRepresentation) {
+        STORAGE_ADAPTERS.get(representation).store(file, schemaRepresentation);
     }
 
     private static abstract class StorageAdapter<T extends SchemaSourceRepresentation> {
@@ -164,10 +225,6 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
 
         protected abstract void storeAsType(final File file, final T cast);
 
-        public Class<T> getSupportedType() {
-            return supportedType;
-        }
-
         public T restore(final SourceIdentifier sourceIdentifier, final File cachedSource) {
             Preconditions.checkArgument(cachedSource.isFile());
             Preconditions.checkArgument(cachedSource.exists());
@@ -186,8 +243,8 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
 
         @Override
         protected void storeAsType(final File file, final YangTextSchemaSource cast) {
-            try {
-                Files.copy(cast.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+            try (final InputStream castStream = cast.openStream()) {
+                Files.copy(castStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
             } catch (final IOException e) {
                 throw new IllegalStateException("Cannot store schema source " + cast.getIdentifier() + " to " + file, e);
             }
@@ -198,7 +255,7 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
             return new YangTextSchemaSource(sourceIdentifier) {
 
                 @Override
-                protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
+                protected MoreObjects.ToStringHelper addToStringAttributes(final MoreObjects.ToStringHelper toStringHelper) {
                     return toStringHelper;
                 }
 
@@ -220,7 +277,7 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
             fileName = com.google.common.io.Files.getNameWithoutExtension(fileName);
 
             final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
-            if(si.isPresent()) {
+            if (si.isPresent()) {
                 LOG.trace("Restoring cached file {} as {}", file, si.get());
                 cachedSchemas.add(si.get());
             } else {
@@ -229,12 +286,12 @@ public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentat
             return fileVisitResult;
         }
 
-        private Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
+        private static Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
             final Matcher matcher = CACHED_FILE_PATTERN.matcher(fileName);
-            if(matcher.matches()) {
+            if (matcher.matches()) {
                 final String moduleName = matcher.group("moduleName");
                 final String revision = matcher.group("revision");
-                return Optional.of(new SourceIdentifier(moduleName, Optional.fromNullable(revision)));
+                return Optional.of(RevisionSourceIdentifier.create(moduleName, Optional.fromNullable(revision)));
             }
             return Optional.absent();
         }