Miscellaneous.java

/*
 * *********************************************************************************************************************
 *
 * blueMarine II: Semantic Media Centre
 * http://tidalwave.it/projects/bluemarine2
 *
 * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * 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.
 *
 * *********************************************************************************************************************
 *
 * git clone https://bitbucket.org/tidalwave/bluemarine2-src
 * git clone https://github.com/tidalwave-it/bluemarine2-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.bluemarine2.util;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.text.Normalizer;
//import java.util.regex.Pattern;
import java.util.Objects;
import java.util.stream.Stream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static java.util.stream.Collectors.toList;
import static java.text.Normalizer.Form.*;
//import java.util.regex.Matcher;
import static lombok.AccessLevel.PRIVATE;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
@NoArgsConstructor(access = PRIVATE) @Slf4j
public final class Miscellaneous
  {
    private static final Normalizer.Form NATIVE_FORM;

//    private static final Pattern PATTERN_EXTENSION = Pattern.compile("(\\.[^.]+)$");

    static
      {
        final String osName = System.getProperty("os.name").toLowerCase();

        switch (osName)
          {
            case "linux":
                NATIVE_FORM = NFC;
                break;

            case "mac os x":
                NATIVE_FORM = NFD;
                break;

            case "windows":
                NATIVE_FORM = NFD; // FIXME: just guessing
                break;

            default:
                throw new ExceptionInInitializerError("Unknown o.s.: " + osName);
          }

        log.info(">>>> Charset normalizer form: {}", NATIVE_FORM);
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @Nullable
    public static String normalizedToNativeForm (@Nullable final String string)
      {
        return (string == null) ? null : Normalizer.normalize(string, NATIVE_FORM);
      }

    /*******************************************************************************************************************
     *
     * Takes a path, and in case it can't be resolved, it tries to replace with an equivalent representation of an
     * existing path, with the native form of character encoding (i.e. the one used by the file system).
     * If there is no normalized path to replace with, the original path is returned.
     * Note that this method is I/O heavy, as it must access the file system.
     * FIXME: what about using a cache?
     *
     * See http://askubuntu.com/questions/533690/rsync-with-special-character-files-not-working-between-mac-and-linux
     *
     * @param   path    the path
     * @return          the normalized path
     *
     ******************************************************************************************************************/
    @Nonnull
    public static Path normalizedPath (@Nonnull final Path path)
      throws IOException
      {
//        log.trace("normalizedPath({}", path);

        if (Files.exists(path))
          {
            return path;
          }

        Path pathSoFar = Paths.get("/");

        for (final Path element : path)
          {
//            log.trace(">>>> pathSoFar: {} element: {}", pathSoFar, element);
            final Path resolved = pathSoFar.resolve(element);

            if (Files.exists(resolved))
              {
                pathSoFar = resolved;
              }
            else
              {
                // FIXME: refactor with lambdas
                try (final Stream<Path> stream = Files.list(pathSoFar))
                  {
                    boolean found = false;

                    for (final Path child : stream.collect(toList()))
                      {
                        final Path childName = child.getFileName();
                        found = Objects.equals(normalizedToNativeForm(element.toString()),
                                               normalizedToNativeForm(childName.toString()));
//                        log.trace(">>>> original: {} found: {} same: {}", element, childName, found);

                        if (found)
                          {
                            pathSoFar = pathSoFar.resolve(childName);
                            break;
                          }
                      }

                    if (!found)
                      {
                        return path; // fail
                      }
                  }
              }
          }

        return pathSoFar;
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    public static File toFileBMT46 (@Nonnull final Path path)
      throws IOException
      {
        File file = path.toFile();

        if (probeBMT46(path))
          {
            file = File.createTempFile("bmt46-", "." + extensionOf(path));
            file.deleteOnExit();
            log.warn("Workaround for BMT-46: copying {} to temporary file: {}", path, file);
            Files.copy(path, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
          }

        return file;
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    private static String extensionOf (@Nonnull final Path path)
      {
        final int i = path.toString().lastIndexOf('.');
        return (i < 0) ? "" : path.toString().substring(i + 1);
//        final Matcher matcher = PATTERN_EXTENSION.matcher(path.toString()); TODO
//        return matcher.matches() ? matcher.group(0) : "";
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private static boolean probeBMT46 (@Nonnull final Path path)
      {
        return Files.exists(path) && !path.toFile().exists();
      }
  }